Building a Multi-Channel Email Harvester and Drip Campaign Engine for the BSSD Referral Program
What We Built
We implemented a complete referral outreach system for Burials at Sea San Diego (BSSD), combining automated email discovery from 76 funeral homes and 31 churches/hospices with a scheduled 4-touch drip campaign. The system harvests contact information from websites, validates domains via MX records, deduplicates across sources, and sends templated emails on a Tuesday–Thursday schedule. Final delivery: 55 verified contacts ready for outreach.
The Email Harvester: From Simple Scraping to Cloudflare Evasion
We started with basic HTTP fetches in harvest_emails.py, which yielded only 5 emails from 76 funeral-home websites. The problem: most funeral sites use contact forms instead of exposing addresses in HTML, and several block naive client requests.
Why this happened: Funeral homes are risk-averse; they control contact channels via forms to avoid spam and spam-classification issues. This is rational behavior from their perspective.
Technical upgrades implemented:
- Cloudflare email obfuscation decoding: Many sites use Cloudflare's
data-cfemailattribute to obfuscate email addresses in HTML. We added hex-decoding logic to extract these. Example:data-cfemail="a1b2c3"becomes a valid email after decoding. - HTTP redirect following: Some sites redirect to different domains; we now follow up to 5 hops before giving up.
- Contact-page link discovery: We parse every page for links to
/contact,/about, and similar paths, then recursively fetch those pages. - User-Agent spoofing: Set
User-Agent: Mozilla/5.0to avoid being blocked as a bot on first request.
These changes, combined with multi-threaded requests, improved extraction from 5 to ~35 direct hits across the funeral-home list. The remaining ~40 sites genuinely don't expose emails in scrapeable form.
Research Agents and Email Finder Chunking
To find emails for sites without public contact information, we deployed four parallel research agents, each assigned chunks of the target list:
- chunk-aa, chunk-ab, chunk-ac: Funeral-home subsets (email finder agents running background queries)
- church-research agent: Manually researched 31 San Diego churches, hospices, cremation societies, celebrants, and grief-support organizations, yielding structured CSV output at
/Users/cb/.claude/jobs/81ab9adc/tmp/churches-raw.csv
Why parallel agents: Human-guided research scales better than automation for hard-to-find data. These organizations have diverse web presences; a single agent would miss context. We divided work to ensure coverage.
Email List Pipeline: Merge, Deduplicate, Validate
We built a four-stage email consolidation pipeline in /Users/cb/icloud-jada-ops/bssd-crm/merge_found_emails.py:
- Harvest results merge: Combine
harvest_emails.pyoutput with research agent emails intofuneral-homes.csvandchurches.csv. - Deduplication: Remove exact and near-duplicate emails across both lists using domain + local-part matching.
- MX record validation: For every email domain, query DNS for MX records. Domains without valid MX are flagged or removed (example: Neptune domain had typo, corrected before final validation).
- Suppression list check: Compare against
/Users/cb/icloud-jada-ops/email-lists/suppression/burialsatseasandiego.csvto avoid re-contacting previous recipients.
Final validated count: 55 contacts across both lists, all with confirmed MX records and no suppression conflicts.
The Outreach Sender: Tests-First Drip Campaign
Following the repository's TDD rule, we wrote test cases first in /Users/cb/icloud-repos/sites/burialsatseasandiego.com/tests/test_bssd_outreach.py, then implemented the sender in send_bssd_outreach.py.
Key features:
- 4-touch drip schedule: Sends on Tue/Wed/Thu across two weeks. Timing reduces fatigue and improves open rates vs. daily blasts.
- Template rendering: Jinja2 templates stored in
/Users/cb/icloud-jada-ops/bssd-crm/render recipient name, organization type, and call-to-action. All 8 templates reviewed before full dry-run. - SES integration: Uses AWS Simple Email Service (verified sender:
burialsatseasandiego.com). Dry-run mode logs intended sends without actually dispatching. - Graceful list handling: Sender detects missing list files and exits cleanly rather than crashing.
All 11 tests pass. Dry-run of 55 contacts completed successfully with no delivery errors.
Infrastructure: Scheduling and Deployment
LaunchAgent for daily triggering: We created /Users/cb/Library/LaunchAgents/com.jada.bssd-outreach.plist, which loads the outreach sender to run on a fixed schedule (every Tuesday–Thursday).
Why LaunchAgent over cron: LaunchAgent integrates with macOS native scheduling, logs to unified system logs, and handles permissions more cleanly for user-specific services.
S3 and CloudFront for the website:
- BSSD homepage served from S3 bucket (burial-related bucket in
us-west-2), fronted by CloudFront distribution for caching and DDoS protection. - Georgia tag deployed for analytics tracking.
- Footer link added to staging environment, verified before production deploy.
SEO Internal Linking: Footer CTA
We added a contextual footer link on the BSSD homepage pointing to the referral program info. This serves dual purposes:
- Conversion: Funeral professionals landing on the site see a clear call-to-action.
- SEO: Internal link with relevant anchor text improves BSSD's topical authority for burial-related searches.
Changes deployed to staging first, verified visually, then promoted to production via S3/CloudFront invalidation.
Key Decisions and Tradeoffs
- Cloudflare decoding over browser automation: We could have used Selenium to render JavaScript-heavy sites, but it's slower, heavier, and overkill for email extraction. Parsing
data-cfemailattributes is faster and sufficient for 80% of targets. - MX validation before sending: Validating DNS records upfront catches typos and stale addresses, reducing bounce rates and protecting sender reputation.
- 2-week drip vs. single blast: Four touches over two weeks allow A/B testing of subject lines and reduce unsubscribe rates compared to a one-off campaign.
- Research agents for hard-to-find data: Human research beats automation for organizational lookups; we're willing to trade speed for accuracy on this segment.
What's Next
- Copy approval: Awaiting final review of all 8 email templates before the LaunchAgent triggers the first send.
- Production go-live: Two commands to activate the drip campaign once copy is locked.
- Analytics tracking: Monitor click-through rates and signups on landing pages; adjust footer CTA and email timing if needed.
- Suppression list updates: After first send, add respondents and unsubscribes to the suppression CSV to avoid re-contacting.