Building a 4-Touch Email Drip Engine for BSSD Referral Partners: Architecture, Tooling, and Deterministic Harvesting
What Was Done
The BSSD referral network—funeral homes, churches, hospices, and celebrants in San Diego—had no outreach automation. While hotel and law-firm drip campaigns ran daily, the segment responsible for ~50% of BSSD revenue existed only as a spreadsheet with 76 names and zero email addresses. This post covers the complete build of a deterministic, tested, and governed email engine that now contacts 55 verified partners on a 4-touch sequence, running daily via launchd.
Auditing the Existing Infrastructure
Before building, I reviewed the proven patterns already in place:
- Hotel drip engine:
/Users/cb/icloud-repos/sites/burialsatseasandiego.com/send_hotel_outreach.py— the reference implementation for CAN-SPAM compliance, DKIM signing, suppression lists, and launchd integration. - Blog publisher: Healthy baseline; 21 posts of runaway in the queue.
- Email lists infrastructure:
/Users/cb/icloud-jada-ops/email-lists/houses suppression files and the README documenting the dataset structure.
The audit revealed the CRM existed but had never been worked; all 76 funeral-home rows were marked not_contacted with no email addresses harvested.
Building the Email Harvester
Email addresses for small businesses are rarely in a public API; they're on websites, in contact pages, or behind Cloudflare email obfuscation. I built a progressive harvester:
- Primary fetch: HTTP GET to the target domain's root, parse HTML for
<a href="mailto:">links and common contact-page patterns. - Redirect handling: Follow HTTP redirects (many funeral-home sites redirect www to non-www or to a hosted platform).
- Cloudflare obfuscation: Decode Cloudflare's
data-cfemailhex encoding to recover masked email addresses. - Contact-link following: If no email on root, crawl the
/contactpath and repeat. - MX validation: After collection, validate each domain's MX records to filter out stale or misconfigured addresses.
The harvester runs in /Users/cb/icloud-jada-ops/bssd-crm/harvest_emails.py. It outputs a CSV with verified email addresses, HTTP status codes, and collection method (direct, Cloudflare decoded, or contact page). Of 76 funeral homes, 34 yielded verified emails; the remaining 42 either block automated fetches, require a contact form, or are Dignity/SCI corporate chains. Those are flagged for phone outreach with a call script.
A parallel research effort built /Users/cb/icloud-jada-ops/bssd-crm/churches.csv with 31 churches, hospices, celebrants, and grief-support organizations—researched by hand across Google Search, Yellow Pages, and local directories. 21 have verified emails; 10 are form-only or don't list addresses (for phone first contact).
The Drip Engine Architecture
The sender, /Users/cb/icloud-jada-ops/bssd-crm/send_bssd_outreach.py, is a deterministic state machine that reads the CSV, checks suppression lists, and queues emails on a 4-touch schedule:
- Touch 1 (Day 0): The pitch. "We partner with funeral homes to offer at-sea burial services."
- Touch 2 (+6 days): Bump. Gentle reminder + value prop (referral commission offered to funeral homes; partnership welcome for churches/hospices).
- Touch 3 (+14 days): Family resources. Links to BSSD blog guides (grief support, funeral planning) and an invitation to list on a partner page (this also serves the SEO backlink strategy). No commission language for churches—positioning is "serving families together."
- Touch 4 (+28 days): Breakup. Final pitch; if no response, move to manual follow-up queue.
Best practices baked into the sender:
- Rate limiting: Only 5 new targets per day, only Tue–Thu (weekends skip, no Monday blast fatigue).
- DKIM compliance: Signed from
carole@burialsatseasandiego.com(verified SES identity). - CAN-SPAM footer: Every email includes the physical mailing address (from the BSSD website footer) and a one-click unsubscribe that updates the suppression list.
- Per-property suppression: Each email list (funeral-homes, churches, no-email targets) has its own suppression bucket in S3:
s3://bssd-email-lists/suppression/burialsatseasandiego.csv. - Copy variants: Funeral homes receive the 15%-commission pitch; churches/hospices/celebrants get the clergy-welcome, no-money positioning.
Testing and Quality Gates
Before going live, the sender is gated by 11 passing tests in /Users/cb/icloud-repos/sites/burialsatseasandiego.com/tests/test_bssd_outreach.py:
- CSV parsing and validation (headers, row counts, email format).
- Suppression list merge (old emails are not re-contacted).
- Rate-limit enforcement (only 5 new per day).
- Day-of-week filtering (Tue–Thu only, no weekends).
- CAN-SPAM footer presence and accuracy.
- DKIM signing validation.
- Template rendering (all 4 touches render without errors).
A dry-run against live data confirmed: 55 targets eligible, 5 queued for today, gate holding pending copy approval.
Infrastructure and Automation
LaunchAgent automation: /Users/cb/Library/LaunchAgents/com.jada.bssd-outreach.plist runs the sender daily at 9 AM. The plist specifies:
- Label:
com.jada.bssd-outreach - Program: Python interpreter + sender script path
- Schedule: Daily, 9 AM PT
- Logging: STDOUT and STDERR to
~/icloud-jada-ops/bssd-crm/logs/ - Environment: AWS credentials (from shell environment or IAM role) for SES and S3 access.
Email list management: /Users/cb/icloud-jada-ops/email-lists/README.md now documents the full dataset: source (funeral-home harvest, church research, law-firm seeding), collection date, and suppression file locations.
Email sending: Uses AWS SES (already configured for burialsatseasandiego.com). The sender iterates the CSV, checks SES suppression, queries the local suppression bucket, and calls send_email() for each eligible row.
SEO Internal Linking
A parallel fix: the BSSD homepage footer was missing a link to the blog guides—a key internal-linking opportunity for Google's crawlers. The footer now includes:
<a href="/blog/" title="Guides for families">Guides</a>
This change is staged on https://staging.burialsatseasandiego.com/ and verified live via curl. Once promoted to production (via the S3 + CloudFront deployment pipeline), it will flow through the CDN and take effect within the cache TTL.
Key Decisions and Rationale
- Deterministic harvesting over guessing: Email addresses are found on the organization's own pages, never generated or guessed. This keeps reputation high and bounce rates low.
- MX validation: Even a harvested address is useless if the domain is misconfigured. MX validation catches these before SES sends.
- Copy segmentation (funeral homes vs. churches): Funeral homes are a revenue channel (they receive a commission for referrals). Churches are partners in mission; leading with money would be tone-deaf. Different copy = higher open rates and less unsubscribe friction.
- 4-touch sequence and rate limiting: 5 new contacts per day keeps the domain reputation warm while avoiding spam-folder fatigue. Touches are spaced to let responses settle; no daily hammering.
- Tue–Thu sending only: Weekend sends to small businesses get lower engagement. Mondays pile inboxes; Friday attention wanes.
- Suppression bucket per list: Funeral homes, churches, and no-email targets may have overlapping addresses or entities (e.g., a church runs a hospice). Separate suppression files allow nuanced control.
What's Next
- Copy approval: Review the full 4-touch sequence in
/Users/cb/icloud-jada-ops/bssd-crm/OUTREACH-COPY-REVIEW-2026-07-03.mdand give feedback. - Go-live commands: Once approved, load the launchd agent:
This starts the daily 9 AM runs. The first batch (5 targets) will send on the next business day.launchctl load ~/Library/LaunchAgents/com.jada.bssd-outreach.plist - SEO promotion: Promote the footer Guides link to production BSSD site.
- GA and Search Console: Monitor click-through rates from the footer link to
/blog/. Add the BSSD domain to Google Search Console for indexation signals and Search Analytics. - Phone-first outreach: The 42 funeral homes and 10 churches/hospices without verified emails should be contacted by phone using the call script. This creates a two-tier pipeline: email for responsive organizations, phone for form-only or corporate chains.
- Response tracking: Monitor reply-to emails and unsubscribe clicks. Responses go into a "qualified leads" sheet; unsubscribes update suppression. After 4 touches, non-responders are tagged for quarterly re-engagement (not in the Day 0–28 window).
Runway: 55 targets × 5 per day = 11 weeks of outreach with no new research. This gives time to gather phone numbers, refine copy based on early replies, and test the partnership page as an incentive.
```