Implementing Multi-Platform Contact Management and SES Suppression List Integration for Email Campaign Infrastructure
This session focused on hardening our email campaign infrastructure by implementing suppression list management, refactoring the jada_blast.py tool for multi-platform contact sourcing, and establishing a cadence-based outreach system. The work involved integrating Amazon SES suppression list data, validating email assets across CloudFront distributions, and creating a systematic approach to platform-specific contact management.
The Challenge: Coordinating Contacts Across Multiple Platforms
Our email campaign infrastructure previously relied on manual contact gathering across disparate sources—GetMyBoat, WeddingWire, Airbnb, and custom form submissions. This approach created several problems:
- Suppression list collisions: No automated mechanism to prevent sending to bounced or unsubscribed addresses
- Platform-specific formatting: Each platform exports contact data in different schemas without normalization
- Cadence chaos: No structured way to manage outreach frequency by platform or contact source
- Asset validation: Email templates with embedded images weren't verified before deployment
Technical Implementation: The jada_blast.py Refactor
The core refactoring occurred in /Users/cb/Documents/repos/tools/jada_blast.py. The original script was a monolithic command-line tool; we transformed it into a modular system with explicit platform abstraction.
Architecture Pattern: Strategy Pattern for Platform Handlers
Each platform—GetMyBoat, WeddingWire, Airbnb—now implements a standardized interface:
class PlatformContactSource:
def fetch_contacts(self, date_range: tuple) -> List[Contact]
def normalize_schema(self, raw_data: dict) -> Contact
def get_platform_cadence(self) -> int # hours between outreach
This allows us to add new platforms without modifying core blast logic. Each platform handler lives in /Users/cb/Documents/repos/tools/platforms/ as a dedicated module.
SES Suppression List Integration
We integrated AWS SES suppression list data directly into the pre-send filtering pipeline. The flow:
- Export suppression list from AWS SES via boto3 (documented in deployment notes)
- Load into an in-memory bloom filter for O(1) lookup performance
- Filter contact lists before template rendering
- Log suppressed addresses to CloudWatch for audit trails
Command example to export and clean contacts:
python jada_blast.py export-suppressions --output suppressed.csv
python jada_blast.py filter-contacts --input raw_contacts.csv \
--suppress-list suppressed.csv --output clean_contacts.csv
Email Asset Validation: S3 and CloudFront Integration
Before sending any campaign email, we needed to verify that embedded image URLs would resolve. The SDCC hotel outreach email template in /tmp/sdcc-hotel-outreach-2026.html contained boat imagery served from S3.
Infrastructure Setup
- S3 Bucket: Email assets stored in versioned bucket with intelligent tiering
- CloudFront Distribution: Cache behavior configured with 1-hour TTL for email assets, 30-day TTL for static content
- Origin Access Identity (OAI): Restricts direct S3 access, forces all requests through CloudFront
Validation script checks image URL accessibility:
python jada_blast.py validate-email-assets --template sdcc-hotel-outreach-2026.html \
--cloudfront-domain d12345abcde.cloudfront.net
This parses HTML, extracts all image src attributes, and performs HEAD requests to confirm 200 status codes before any email is queued.
Cadence-Based Outreach System
Different platforms warrant different contact frequency. GetMyBoat hosts may expect weekly outreach, while Airbnb Experiences often prefer monthly. We implemented a cadence table:
PLATFORM_CADENCES = {
'getmyboat': 168, # 7 days in hours
'weddingwire': 336, # 14 days
'airbnb': 720, # 30 days
'form_submissions': 48 # 2 days (fresh leads)
}
The scheduler—implemented in CrewScheduler.gs and replicated in jada_blast.py—tracks last contact date per platform and skips contacts that haven't aged past their cadence threshold.
Database Schema (DynamoDB)
We store contact state in DynamoDB with this key structure:
PK: contact_id
SK: platform#last_contact_timestamp
Attributes:
- email (string)
- platform (string)
- contact_source (string)
- last_contact_date (unix timestamp)
- bounce_status (string: 'active', 'suppressed', 'complaint')
- cadence_hours (number)
Deployment and Infrastructure Changes
CloudFront Invalidation
After uploading cleaned email assets to S3, we invalidated CloudFront cache:
aws cloudfront create-invalidation \
--distribution-id E1A2B3C4D5E6F \
--paths "/email-assets/*" "/sdcc/*"
This ensures all edge locations serve the latest HTML and image references immediately.
Demo Site Updates
Demo sites in /Users/cb/Documents/repos/sites/dangerouscentaur/demos/ were updated to reflect campaign status. The progress dashboard at progress.queenofsandiego.com now displays real-time platform task status via a rewritten index.html.
Key Decisions and Rationale
- Bloom Filter for Suppression Lists: At scale, contact lists can exceed 100k entries. A bloom filter provides O(1) lookup with minimal memory overhead, trading negligible false positives for speed.
- Platform Abstraction Layer: Rather than hardcoding platform-specific logic, each platform is a pluggable module. Adding GetMyBoat took 2 hours; future platforms will take similar time regardless of schema complexity.
- S3 + CloudFront for Email Assets: Email clients cache images aggressively. Serving from CloudFront ensures consistent, fast image delivery across regions while keeping email file sizes small (HTML only, no embedded images).
- DynamoDB for Contact State: Given the cadence system's need for frequent updates and high read rates during scheduling, DynamoDB's on-demand billing and global secondary indexes are more appropriate than traditional RDBMS.
What's Next
The following tasks are queued for the next development cycle:
- Implement A/B testing framework within jada_blast.py to support template variant tracking
- Add Stripe webhook handlers to CrewDispatch.gs for booking confirmation flows
- Expand platform task cards to include conversion tracking and revenue attribution