```html

Implementing Autonomous Marketing Campaign Management: Architecture for Multi-Platform JADA Outreach

This post documents the technical infrastructure built to automate marketing campaign orchestration across multiple platforms while maintaining strict brand governance and data hygiene. The implementation spans Python automation, Google Apps Script integrations, macOS LaunchAgents scheduling, and AWS infrastructure with CloudFront caching and SES suppression list management.

The Problem: Manual Campaign Coordination at Scale

Managing outreach campaigns for event venues, wedding services, and charter platforms required coordinating:

  • Email template rendering with dynamic boat/venue imagery
  • Contact list deduplication against SES suppression lists
  • Staggered send cadences (initial outreach, follow-ups at T+7 and T+14, breakup emails)
  • Platform-specific integrations (GetMyBoat, WeddingWire, Viator)
  • Brand consistency enforcement across all outward-facing communications

The previous manual approach was error-prone and didn't scale. We needed deterministic, auditable automation.

Technical Architecture Overview

Core Blast Engine: jada_blast.py

Located at /Users/cb/Documents/repos/tools/jada_blast.py, this is the orchestration layer for all email campaigns. The script:

  • Loads contact CSV files from /Users/cb/Documents/repos/tools/contacts/
  • Renders Jinja2 email templates with variable substitution (venue names, boat images, personalization fields)
  • Deduplicates against AWS SES suppression lists via list-suppressed-destinations API calls
  • Generates MIME multipart messages with inline image references
  • Executes sends through boto3 SES client with configurable rate limiting
  • Logs transaction IDs and bounce/complaint tracking to audit database

The implementation supports both synchronous test sends and asynchronous production batches. Rate limiting respects AWS SES sandbox quotas (1 message/second in development, burst up to 14 msgs/sec in production).

Scheduled Execution: LaunchAgents for Cadence Management

Four plist files in /Users/cb/Library/LaunchAgents/ define the campaign cadence:

  • com.jada.hotel-outreach-initial.plist — Triggers initial contact sequence
  • com.jada.hotel-outreach-followup1.plist — 7-day post-initial follow-up
  • com.jada.hotel-outreach-followup2.plist — 14-day post-initial follow-up
  • com.jada.hotel-outreach-breakup.plist — Cleanup/final message

Each plist invokes send_hotel_outreach.py (at /Users/cb/Documents/repos/tools/send_hotel_outreach.py) with environment variables specifying the campaign stage. The StartCalendarInterval directives are set to run at 06:00 UTC on designated days, ensuring consistent send times and avoiding weekend/holiday conflicts.

Email Template Infrastructure

HTML email templates are stored in S3 at s3://queen-of-san-diego-assets/email-templates/ and served through CloudFront distribution d2abc123def456.cloudfront.net (exact ID anonymized for security). The template at /tmp/sdcc-hotel-outreach-2026.html includes:

  • Inline CSS for Outlook compatibility
  • Dynamic image URLs pointing to S3 objects with CloudFront cache headers (3600s TTL)
  • Responsive layout using media queries for mobile clients
  • Unsubscribe links with encoded contact IDs for bounce tracking

After template modifications, we execute CloudFront cache invalidation via aws cloudfront create-invalidation --distribution-id d2abc123def456 --paths "/*" to ensure updated templates propagate within 60 seconds globally.

Contact Management and Suppression List Hygiene

SES suppression lists are maintained at three levels:

  • Bounce List: Hard bounces (permanent delivery failures) automatically added by SES
  • Complaint List: ISP feedback loops and user spam reports
  • Unsubscribe List: Contacts explicitly requesting removal via email footer links

Before each campaign, we execute aws sesv2 list-suppressed-destinations --reason BOUNCE --reason COMPLAINT and cross-reference against contact CSVs. Suppressed addresses are written to a quarantine file (contacts_suppressed.csv) and excluded from send operations. This prevents SES from penalizing our sending reputation and reduces operational noise in bounce logs.

The suppression list export is logged with timestamps to /Users/cb/Documents/repos/logs/suppression_exports/, creating an audit trail for compliance and troubleshooting.

Google Apps Script Integrations

Three Google Apps Script projects handle platform-specific workflows:

CrewDispatch.gs

Manages crew scheduling and dispatch notifications. Modified to:

  • Query Google Sheets for crew availability
  • Format scheduled assignments into email notifications
  • Invoke SES via authenticated HTTP POST to a Lambda function
  • Log dispatch confirmations back to Sheets with timestamps

FuneralOutreach.gs

Handles event-specific outreach for funeral/memorial services. Uses UrlFetchApp.fetch() to POST personalized contact data to a backend API endpoint, which then triggers jada_blast with funeral-specific templates.

ViatorApiFollowUp.gs

Integrates with Viator API to fetch completed tour bookings and trigger automated follow-up emails within 24 hours of tour completion. Implements exponential backoff retry logic for API failures and caches response data in Sheets for manual review before send authorization.

Brand Governance: The "Never Use My Name" Directive

A critical requirement emerged: ensure no personal names appear in outward-facing marketing materials. We implemented a multi-layer enforcement strategy:

  • Template-level: All HTML templates use branded language ("The JADA Team", "Queen of San Diego Charter", "Event Coordination Team") instead of individual names
  • Code-level: Grep filters in pre-send validation check for prohibited patterns (grep -r "C\.B\." contacts/ templates/)
  • Deployment-level: CloudFront cache invalidation after S3 template updates ensures no stale branded content is served
  • Audit-level: A memory file at /Users/cb/.claude/projects/-Users-cb-Documents-repos/memory/feedback_no_cb_name_in_marketing.md documents this requirement and surfaces it to all subsequent work sessions

Static demo sites (dangerouscentaur portfolio pages, progress dashboard) were audited and updated to remove personal name references, then redeployed to S3 and invalidated through