```html

Multi-Tenant Email Campaign Automation: Building JADA's Platform-Agnostic Blast System with Cadence Suppression

What Was Done

We implemented a sophisticated email campaign automation system for JADA's multi-platform outreach, focusing on three critical infrastructure improvements:

  • Enhanced jada_blast.py with widening-gap cadence logic to prevent recipient fatigue across multiple contact lists
  • Integrated Amazon SES suppression list filtering into the blast pipeline to eliminate bounced addresses before sending
  • Refactored marketing materials to remove personal attribution, ensuring team-based branding across all public-facing templates and demo sites
  • Created platform-specific task tracking for non-live listing platforms (GetMyBoat, WeddingWire, etc.) with standardized credential management

Technical Architecture: The Widening-Gap Cadence Pattern

The core innovation here is the widening-gap cadence—a contact suppression pattern designed to prevent message fatigue while maintaining engagement velocity. Rather than blasting all recipients simultaneously, we space communications using progressive delays.

In /Users/cb/Documents/repos/tools/jada_blast.py, we implemented a tiered scheduling system:

# Cadence thresholds (in days between contact attempts)
cadence_tiers = {
    'tier_1': 3,    # High-intent contacts: 3-day gap
    'tier_2': 7,    # Medium-intent: 7-day gap
    'tier_3': 14    # Soft-intent: 14-day gap
}

This approach segmented recipients based on historical engagement (open rates, click-through rates, and reply velocity). The system queries the SES suppression list before each send, filtering out bounce/complaint addresses in real-time rather than post-facto analysis.

SES Suppression List Integration

Previously, the blast tool relied on manual cleanup of bounced addresses. We automated this by:

  1. Exporting the SES Suppression List (Bounce + Complaint entries) via AWS CLI
  2. Cross-referencing contact CSVs against suppressed addresses
  3. Creating a deduplicated, cleaned contact manifest before template rendering
  4. Logging suppression metrics to CloudWatch for monitoring campaign hygiene

The contact cleaning process:

aws sesv2 get-suppressed-destination-attributes \
  --email-address [contact-address] \
  --region us-west-2

This is called iteratively within the Python script's contact validation phase. We cache the suppression list for 24 hours to avoid rate-limiting on repeated runs.

Marketing Material Refactoring: Team-Based Branding

A critical decision was made to remove all personal name attribution from public-facing materials. This affected:

  • Email templates: /tmp/sdcc-hotel-outreach-2026.html — replaced "C.B. Ladd, Owner of JADA" with "The JADA Team"
  • Google Apps Script files: FuneralOutreach.gs, CrewDispatch.gs, CrewScheduler.gs, WorshipRsvp.gs, ViatorApiFollowUp.gs — removed personal attribution from email signatures and dispatch confirmations
  • Demo sites: /Users/cb/Documents/repos/sites/dangerouscentaur/demos/3028fiftyfirststreet.92105.dangerouscentaur.com/index.html and demo.dangerouscentaur.com/index.html — rebranded to team-centric copy
  • Progress dashboard: /Users/cb/Documents/repos/sites/progress.queenofsandiego.com/index.html — updated footer attribution

This was a deliberate architectural choice: personal attribution creates reputational risk in high-volume outreach, and team branding scales better across delegation and future expansion.

Infrastructure: S3, CloudFront, and Deployment Pipeline

All updated marketing materials were deployed to S3 with CloudFront invalidation:

# SDCC email preview deployment
aws s3 cp /tmp/sdcc-hotel-outreach-2026.html \
  s3://queenofsandiego-marketing-assets/templates/sdcc-2026.html \
  --cache-control "max-age=3600"

# CloudFront cache invalidation
aws cloudfront create-invalidation \
  --distribution-id [DIST_ID_SDCC] \
  --paths "/templates/sdcc-2026.html"

Similarly, dangerouscentaur demo sites were pushed to their respective S3 buckets with invalidations issued for the CloudFront distribution serving dangerouscentaur.com.

The update pipeline now includes a pre-deployment validation step that scans all outward-facing files for personal name references using regex patterns, preventing future attribution drift.

Platform Task Tracking System

We created a standardized task framework in the dashboard for managing non-live listing platforms. Each platform task includes:

  • Platform name: GetMyBoat, WeddingWire, The Knot, etc.
  • Credential reference: Pointer to repos.env (credentials stored in environment, never in task descriptions)
  • API endpoints: Documentation of platform-specific listing APIs
  • Sync requirements: Frequency and field mappings for inventory synchronization
  • Contact templates: Which jada_blast.py templates apply to each platform's inquiry workflow

This decouples platform management from the core blast tool, allowing parallel development on multiple integrations without blocking email campaign execution.

Key Decisions & Why They Matter

Widening-gap over concurrent sends: Rather than batch-and-send all recipients at once (creating obvious marketing patterns), the cadence system spaces sends across weeks. This improves deliverability, reduces complaint rates, and provides natural segmentation for A/B testing.

Suppression list caching: Calling SES API for every single contact would incur significant latency and costs. We cache suppression data with a 24-hour TTL, balancing freshness against performance.

Team branding over personal attribution: In high-volume outreach, personal names become liabilities—spam complaints, SEO penalties, and reputation concentration risk. Team branding distributes reputation risk and feels more professional to recipients.

Platform task decoupling: Rather than hard-coding platform integrations into the blast tool, we treat each platform as a discrete task with standardized credential and API documentation. This allows junior engineers to implement new platforms without modifying core blast logic.

What's Next

The immediate priorities are:

  • Implement platform-specific inquiry handlers for GetMyBoat and WeddingWire APIs
  • Add hyper-local personalization logic—dynamically inserting location-specific content based on recipient geography
  • Build monitoring dashboards in CloudWatch tracking suppression list