Building a Scalable Email Outreach Engine for Funeral Services: Infrastructure, Testing, and SEO Integration

What Was Done

We built an automated email discovery and outreach system to drive qualified traffic to burialsatseasandiego.com by systematically harvesting contact information from 76 funeral homes, 31 churches/hospices, and cremation services across San Diego County. The system extracts emails from target websites, manages suppression lists, sends personalized outreach via AWS SES, and integrates SEO improvements to the website itself.

Technical Details: Email Harvesting

The core of the system is harvest_emails.py, which performs intelligent web scraping across funeral-home, church, and hospice websites. The harvester implements several sophisticated techniques:

  • Cloudflare Email Obfuscation Handling: Many websites protect email addresses using Cloudflare's data-cfemail encoding. We decode these by implementing Cloudflare's decryption algorithm, allowing us to extract emails that would otherwise be invisible to standard scrapers.
  • Multi-Method Contact Discovery: The harvester doesn't just look for mailto links. It follows on-site contact pages, parses footer text, and extracts emails from "About Us" and "Contact" sections.
  • HTTP Status Validation: Before attempting to harvest a site, we probe HTTP status codes and redirect chains. Sites that return 403/404 or block user-agents are skipped, preventing wasted cycles on inaccessible targets.
  • Graceful Degradation: Sites using JavaScript-heavy contact forms or CAPTCHA protection are flagged as "no_email_found" rather than causing the harvester to fail.

The harvester exports results to CSV format with fields for organization name, category (funeral_home, church, hospice, cremation_society, etc.), email address, website URL, and harvest status. From 76 funeral homes, we extracted 20 valid email addresses. From 31 churches and hospice organizations, we identified 4+ valid contacts, with additional rows pending final processing.

Outreach System: Send Pipeline

The outreach sender lives in /Users/cb/icloud-jada-ops/bssd-crm/send_bssd_outreach.py and implements a TDD-first approach. The test suite at /Users/cb/icloud-repos/sites/burialsatseasandiego.com/tests/test_bssd_outreach.py validates:

  • Suppression list enforcement (emails in /Users/cb/icloud-jada-ops/email-lists/suppression/burialsatseasandiego.csv are never sent to)
  • Template rendering with dynamic variables (organization name, contact person)
  • AWS SES integration and rate limiting
  • Dry-run mode for testing without sending live emails

The sender respects opt-out and suppression lists before any email is queued. This prevents bounces, complaint spikes, and protects our SES sending reputation—critical because AWS SES sandboxes new accounts and throttles senders with high bounce/complaint rates.

Scheduling and Automation

To run outreach on a predictable schedule, we created a launchd agent at /Users/cb/Library/LaunchAgents/com.jada.bssd-outreach.plist. This plist defines:

  • Label: com.jada.bssd-outreach (unique identifier for launchctl)
  • ProgramArguments: Path to Python executable and the send script
  • StartInterval: Cron-like scheduling (in seconds)
  • StandardOutPath / StandardErrorPath: Log files for monitoring success and diagnosing failures

The agent can be loaded with launchctl load ~/Library/LaunchAgents/com.jada.bssd-outreach.plist and monitored via launchctl list | grep bssd-outreach.

Infrastructure: S3, CloudFront, and Website Deployment

The website itself is hosted in S3 with CloudFront distribution in front. During this session, we:

  • Updated the homepage footer at /Users/cb/icloud-repos/sites/burialsatseasandiego.com/index.html to add internal links to service pages, improving SEO crawl depth
  • Deployed changes to S3 staging bucket, verified with HTTP requests, then synced to production
  • Verified Google Analytics tracking tag is present (critical for measuring outreach campaign conversions)
  • Confirmed Route53 DNS for burialsatseasandiego.com points to CloudFront distribution

Changes flow through a two-phase deployment: first validate on staging bucket via CloudFront, then push to production bucket. This prevents broken deployments from immediately hitting live traffic.

Email List Management

The suppression system works in tandem with outreach. We maintain /Users/cb/icloud-jada-ops/email-lists/README.md documenting all list sources and purposes:

  • Suppression lists: Previous opt-outs, complaints, and invalid addresses
  • Target lists: Funeral homes, churches, hospices (split into chunks for parallel processing)
  • Found emails: Results from web harvesting, merged via merge_found_emails.py

Lists are formatted as CSV with consistent field names (name, email, organization, source) to simplify downstream processing.

Key Decisions

  • Why web harvesting instead of buying lists? Purchased email lists for funeral services are stale and generic. Direct harvesting from target websites gives us real decision-makers at active businesses, plus we control opt-out behavior.
  • Why launchd instead of cron? launchd is macOS-native, integrates with system logs, and handles job dependencies and failure retries better than traditional cron.
  • Why separate staging and production S3 buckets? Staging allows us to validate CloudFront cache behavior and verify Google Analytics tracking before touching production. A bad footer link in staging doesn't affect live visitors.
  • Why test the sender before deploying? The test suite runs in dry-run mode against real CSV data, catching template errors, suppression list bugs, and SES configuration issues before live sends.

What's Next

The current system has identified 20+ emailable funeral homes and 4+ church contacts with additional rows being processed. The next phase involves:

  • Running the full send campaign with personalized templates (organization name, service offerings)
  • Monitoring SES bounce and complaint metrics to ensure sender reputation stays healthy
  • Tracking which outreach recipients convert to bookings via UTM parameters in landing page links
  • Iterating email content based on engagement data (open rates, click-through rates)
  • Scaling the harvester to adjacent geographies (Orange County, Riverside County) as San Diego saturates

The foundation is now in place: a testable, repeatable system to discover qualified prospects and maintain our sending reputation while driving SEO improvements to the website itself.