Diagnosing a Stalled Funeral Home Outreach Campaign: Why Zero Emails Sent After Weeks of Setup
We recently inherited a funeral home prospecting system that was supposed to be running on autopilot—identifying new prospects daily and sending a multi-touch drip campaign. After auditing the codebase, infrastructure, and email logs, we discovered zero emails have been sent despite weeks of partial implementation. Here's what we found, why it failed, and how we're fixing it.
The Architecture We Inherited
Two separate implementations exist, neither complete:
- Google Apps Script (GAS):
/Users/cb/Documents/repos/sites/queenofsandiego.com/FuneralOutreach.gs— reads a prospect sheet, queues emails via AWS SES, tracks opens and replies. - EC2-based cron:
/home/ubuntu/repos/tools/send_funeral_blast.sh— one-shot shell script that calls a Python daemon to blast emails from a static CSV.
The prospect list itself lives in a Google Sheet (`1ADx_0L6...rg38`, tab `Contacts`) with 25 funeral homes manually entered. Expected columns: prospect name, email, domain, initial send timestamp, follow-up send timestamps, reply detection, OOO/bounce flags. Actual columns filled: just name and email. Everything else is empty.
Root Cause #1: The GAS Trigger Was Never Installed
The Google Apps Script has all the logic to send emails via SES, track deliverability, and schedule follow-ups. It includes OOO detection and bounce handling. But the trigger—the scheduled execution that should fire weekly—was never created in the Apps Script dashboard.
We confirmed this by checking:
- Sheet audit: All 25 prospect rows have empty
InitialSent,F1Sent,F2Sent, andRepliedcolumns. If the script had run even once, these would be populated with timestamps. - Gmail audit: Searched
jadasailing@gmail.comfor the past 180 days with queries liketo:greenwoodsd.com,to:lavistamemorialpark.com, etc. Zero matches. Zero outbound emails to any of the 25 prospects. - Setup function incomplete: The script header documents a 14-column schema (including OOO, bounce, F3 tracking, etc.), but
funeralOutreachSetup()—which prepares the sheet—was never executed. The sheet is only 9 columns wide.
Root Cause #2: The EC2 Cron Job Ran Once, Failed Silently
The second implementation is a one-off cron line on an EC2 instance:
5 16 24 4 * /home/ubuntu/repos/tools/send_funeral_blast.sh
This translates to: April 24, 4:05 PM UTC, run the blast script. The script reads from /home/ubuntu/repos/tools/contacts/funeral-homes-sd.csv (8 prospects), uses the template at /home/ubuntu/repos/tools/templates/funeral-outreach.html, and calls:
python3 tools/jada_blast.py send --campaign funeral-outreach-2026
We traced the send ledger at s3://progress.queenofsandiego.com/blast-campaigns.json. It contains 2,649 emails across 9 campaigns—but funeral-outreach-2026 is not listed. We also checked /home/ubuntu/repos/tools/logs/ for any funeral_blast_*.log files: none exist.
Conclusion: The cron job likely executed, but the send request failed at an approval gate or validation step and exited without logging. The email daemon expects a signed task ID in a specific SQS queue; if that task never appeared or was rejected, the campaign silently died.
Why Neither System Was Running the Daily 10-New-Prospects Requirement
The standing rule called for:
- Daily execution (not weekly).
- 10 new prospects per day (harvested from Google Maps or a similar data source).
- Sender address:
outreach@burialsatseasandiego.com(verified in SES).
What we built instead:
- GAS script sends from
admin@queenofsandiego.com(wrong domain, not burial-branded). - GAS trigger (if it existed) would fire weekly Wednesday at 9 AM PT, not daily.
- EC2 script reads a static 8-prospect CSV, never harvests new prospects.
- No prospect harvester exists anywhere in the codebase (no Google Maps API integration, no Yelp scraper, nothing).
The Data Problem: Zero Effectiveness Metrics
Because no emails have been sent, we have 0/0 effectiveness. There's no click-through data, no reply rate, no open rate—nothing to optimize against. The standing rule assumed we'd be generating data by now, allowing us to measure reply rates, adjust cadence, and refine copy. Instead, we're starting from cold.
How We're Fixing This
We're taking a two-phase approach:
Phase 1 (Immediate): Stand up SES sender infrastructure properly.
- Verify
outreach@burialsatseasandiego.comin AWS SES (if not already done). - Create a new GAS trigger for the existing script, but change the sender to
outreach@burialsatseasandiego.comand adjust cadence to weekly (matching what's already built) to get initial data flowing. - Enable SES event publishing to SNS/CloudWatch for bounce and complaint tracking.
Phase 2 (Next Sprint): Build the prospect harvester and shift to daily 10-prospect cadence.
- Implement a Google Maps API integration or Yelp scraper to harvest 10 new funeral home prospects daily.
- Enrich with email lookup (Hunter.io or similar API).
- Shift GAS trigger to daily execution.
- Add prospect deduplication logic against the existing sheet.
Key Decision: Why We're Not Waiting for Phase 2
25 prospects already exist in the sheet. Even if we're not harvesting new ones yet, we should start sending to them immediately. This gives us:
- Reply rate data to validate the copy and timing.
- Bounce/OOO detection to clean the list.
- Proof that the infrastructure works before we scale.
Running Phase 1 takes ~20 minutes; Phase 2 adds another 2-3 weeks of development. There's no value in waiting.
What's Next
We