Auditing a Stalled Funeral Home Outreach Campaign: Infrastructure, Data Gaps, and Next Steps

We recently conducted a comprehensive audit of the funeral home email outreach system for Burial at Sea San Diego. What we discovered was a classic case of partial implementation: two separate codebases, zero emails actually sent, and a prospect list sitting cold for weeks. This post walks through the technical findings, the infrastructure we surveyed, and the decisions we made about how to move forward.

The Problem: Two Incomplete Implementations

When we started investigating, we found two distinct outreach systems, neither of which was working:

  • Google Apps Script (GAS) in the sheets repo: A weekly cadence system at /Users/cb/Documents/repos/sites/queenofsandiego.com/FuneralOutreach.gs that reads a Google Sheet (1ADx_0L6...rg38, tab Contacts) and sends via AWS SES. The trigger was never installed, so it has never fired.
  • EC2 cron job: A one-off shell script at /home/ubuntu/repos/tools/send_funeral_blast.sh set to run once on April 24 only. The underlying Python daemon (jada_blast.py) never completed because the send never appeared in the campaign ledger at s3://progress.queenofsandiego.com/blast-campaigns.json.

The result: 25 funeral home prospects loaded into the sheet, zero emails sent, zero data on effectiveness.

What We Audited: The Complete Data Flow

We traced the entire system to understand what was supposed to happen:

Google Sheet Schema (Incomplete)

The sheet has 9 columns today but the GAS code references 14. The columns present are:

  • FuneralHome (name)
  • ContactEmail
  • Phone
  • City
  • InitialSent (empty for all 25 rows)
  • F1Sent, F2Sent, Replied (all empty)

Missing columns referenced in the script header: OOO detection, bounce tracking, F3 followup, and suppression flags. The funeralOutreachSetup() function—which should have initialized these—was never run.

GAS Implementation Details

The script at FuneralOutreach.gs contains:

  • SES authentication: AWS SigV4 signing directly in the script, pulling credentials from a bound secret store.
  • Sender: admin@queenofsandiego.com (not the mandated outreach@burialsatseasandiego.com).
  • Cadence: Weekly trigger on Wednesday 9 AM PT, with per-row delays of 1→4→10→19 days for the followup sequence.
  • State tracking: Sheet columns for `InitialSent` timestamp, bounce reason, and reply detection.

The trigger itself was never installed via PropertiesService.getScriptProperties().setProperty() and ScriptApp.newTrigger(), so runFuneralOutreach() has never fired.

EC2 Cron & Python Daemon

The backup system uses:

  • Cron entry: 5 16 24 4 * (April 24 only—a one-time send, not recurring).
  • Script: send_funeral_blast.sh, which invokes jada_blast.py send --campaign funeral-outreach-2026.
  • Source data: CSV at tools/contacts/funeral-homes-sd.csv with 8 hand-entered prospects (including a self-test address).
  • Template: tools/templates/funeral-outreach.html.
  • Ledger: Campaign metadata written to s3://progress.queenofsandiego.com/blast-campaigns.json.

We checked the ledger and found zero entry for funeral-outreach-2026. The cron job likely hit an approval gate or authentication error and failed silently.

Gmail & SES Audit: Proof of Zero Sends

We performed a 180-day Gmail audit on jadasailing@gmail.com:

  • 0 sent emails to any of the 25 funeral home addresses in the sheet (greenwoodsd.com, lavistamemorialpark.com, neptunesandiego.com, etc.).
  • 0 sends from outreach@burialsatseasandiego.com or admin@queenofsandiego.com.
  • 0 inbound replies from any prospect domain.

We also checked AWS SES SendStatistics and bounce logs via the CLI—no funeral campaign entries in the last 90 days.

Infrastructure We Surveyed

As part of the broader audit, we verified the existing burial-at-sea infrastructure:

  • S3 buckets: burialsatseasandiego.com (primary static content).
  • CloudFront distribution: ID E3DVKQH...EXAMPLE serving the burial site.
  • ACM certificate: Issued for *.burialsatseasandiego.com and burialsatseasandiego.com.
  • Route53: Zone for burialsatseasandiego.com with A-record alias to CloudFront.
  • DNS nameservers: AWS Route53 authoritative nameservers confirmed.

We created staging infrastructure for testing and validation by cloning the production CloudFront + bucket pattern.

Key Decisions & Next Steps

Given the zero-send state, we identified two paths forward:

Option A (Quick Start): Install the GAS trigger immediately and let it fire Wednesday as-is from admin@queenofsandiego.com. This generates data quickly but violates the standing rule requiring sends from outreach@burialsatseasandiego.com and the daily-10-prospects cadence.

Option B (Clean Foundation, ~20 min setup): Stand up outreach@burialsatseasandiego.com in AWS SES with proper warm-up, update the GAS sender to use the new address, migrate to a daily-10 cadence instead of weekly, and complete the missing columns in the sheet schema before launching. This produces trustworthy data from day one.

From a 7-figure digital marketer's perspective: you build