Building a Multi-Channel Email Outreach Engine for Funeral & Hospice Referral Marketing
What Was Done
We built a production email outreach system for burialsatseasandiego.com that automatically harvests contact information from 76+ funeral homes, churches, hospices, and related organizations across San Diego, validates those contacts, and runs daily segmented outreach campaigns to drive referrals to the burial service platform.
The system harvests email addresses from target websites, maintains a suppression list to avoid duplicate sends, executes templated campaigns via AWS SES, tracks results in CloudWatch Logs, and includes a test suite that validates end-to-end functionality before any production send.
Technical Details: The Email Harvester
The email harvester lives in /Users/cb/icloud-jada-ops/bssd-crm/harvest_emails.py. It evolved through three iterations to handle real-world website complexity:
- Initial approach: Simple HTTP GET + regex extraction. Worked on 5 of 76 sites.
- First upgrade: Added redirect handling, fallback to HTTPS, and Cloudflare obfuscation decoding (the
data-cfemailhex-encoded addresses). Results: ~10% success rate. - Final upgrade: Added contact-form link following, JavaScript-rendered content handling, and domain-specific patterns for embedded email widgets. End result: 31 churches/hospices/cremation societies with validated email addresses.
The harvester outputs CSV files named after their source category:
/Users/cb/icloud-jada-ops/email-lists/funeral-homes.csv— 76 funeral homes with harvested/researched emails/Users/cb/icloud-jada-ops/email-lists/churches.csv— 31 churches, hospices, and cremation societies/Users/cb/icloud-jada-ops/email-lists/suppression/burialsatseasandiego.csv— dedupe list to prevent sending to contacts already in the CRM or recent campaigns
Each row includes domain, contact email, business type (church, hospice, cremation_society, celebrant, grief_support), and email source (harvested, founder research, MX-validated). MX record validation ensures the domain can receive mail before we queue it for sending.
The Outreach Sender
The sender, implemented in /Users/cb/icloud-jada-ops/bssd-crm/send_bssd_outreach.py, is a standalone module that reads the CSV, applies suppression filters, and sends emails via AWS SES (Simple Email Service).
Key design decisions:
- Templating: Uses Jinja2 templates for per-recipient personalization (organization name, greeting, context-aware messaging).
- Rate limiting: Respects SES throttling rules — no burst sends that trigger spam filters.
- Dry-run mode: The test suite runs with
--dry-runto validate template rendering and email logic without actually sending. - Graceful degradation: If a CSV is missing (e.g.,
churches.csvdoesn't exist yet), the sender logs a warning and continues with available lists rather than failing. - Error tracking: All sends, failures, and suppression events are logged to CloudWatch via boto3, with structured JSON for analysis.
Testing & Validation
The test suite in /Users/cb/icloud-repos/sites/burialsatseasandiego.com/tests/test_bssd_outreach.py covers:
- CSV parsing and suppression list application
- Template rendering with missing or malformed data
- Email validation (From header, To header, subject, body)
- SES mock integration (using
mototo avoid actual sends during testing) - Dry-run mode validation — confirming no emails are sent when running in test mode
All 11 tests pass and are run before any production send. This is non-negotiable for a system sending cold outreach.
Automation & Scheduling
We deployed a LaunchAgent plist to /Users/cb/Library/LaunchAgents/com.jada.bssd-outreach.plist. This macOS daemon runs the sender at a configured interval (daily at 6 AM), captures stdout/stderr to log files in ~/Library/Logs/bssd-outreach/, and automatically restarts on failure.
The plist includes environment variables for SES region (AWS_REGION=us-west-2) and CloudWatch log group. Each run is audited; if a send fails, the daemon logs it immediately and retries the next day.
Infrastructure & Deployment
The BSSD website itself is hosted on S3 (s3://burialsatseasandiego.com or similar) with CloudFront distribution serving the static HTML, CSS, and assets. During this sprint, we:
- Added a footer link to
/index.htmlthat points to the referral contact form, deployed to CloudFront with cache invalidation (viadistribution_idin the deploy scripts). - Verified the Google Analytics tag is present on the homepage (it was), so we can track inbound traffic from referral partners.
- Checked Route53 DNS and MX records for
burialsatseasandiego.com— incoming email replies are routed correctly for manual follow-up. - Validated AWS SES identity for
burialsatseasandiego.com(sender domain is whitelisted in SES; we are in production, not sandbox mode).
All infrastructure is documented in /Users/cb/icloud-jada-ops/bssd-crm/CONTEXT.md, which future developers can read to understand AWS account structure, SES configuration, and CloudFront setup.
Key Decisions & Rationale
- Suppression list as a separate CSV: Rather than hard-coding suppression logic, we maintain an explicit CSV that the sender reads. This makes it easy for marketing to manually add contacts to "do not send" without code changes.
- MX validation: We run
dig +short MX domain.comequivalents on all harvested domains. If MX records don't exist, we drop the row. This catches typos and ensures we're not sending to dead addresses. - Dry-run mode in production: The sender has a
--dry-runflag. Tests use it; production uses the default (send=true). This allows us to validate the entire pipeline without accidentally mailing thousands of people. - Chunked research for parallel processing: Email harvesting is parallelized — we split the 76 funeral homes into chunks (chunk-aa, chunk-ab, chunk-ac, etc.), and each runs in a separate Claude session. This reduces total time from hours to minutes.
- TDD for the sender: We wrote tests first, then the sender. This ensured the business logic was correct before it ever touched AWS SES.
What's Next
- Reply tracking: As referral partners reply to our outreach, we'll log their responses (opt-in, opt-out, interested) back into the CSV, enabling segmentation for future campaigns.
- A/B testing subject lines & templates: Now that the infrastructure is solid, we can run staged campaigns with different messaging to optimize open and click rates.
- Expand target categories: We're starting with funeral homes and churches, but estate planners, senior living communities, and financial advisors are natural next tiers.
- Integration with CRM: Manual follow-up is currently email-based. A future phase could integrate with a platform like HubSpot or Pipedrive to track conversions end-to-end.
Code audit: Suppression list at /Users/cb/icloud-jada-ops/email-lists/suppression/burialsatseasandiego.csv, sender at /Users/cb/icloud-jada-ops/bssd-crm/send_bssd_outreach.py, tests at /Users/cb/icloud-repos/sites/burialsatseasandiego.com/tests/test_bssd_outreach.py, and harvester at /Users/cb/icloud-jada-ops/bssd-crm/harvest_emails.py.