Auditing and Rebuilding a Stalled Funeral Home Outreach Campaign: Infrastructure, Data, and Process Gaps

Executive Summary

During a recent infrastructure audit of our funeral home outreach initiative, we discovered a critical gap: zero emails have been sent despite two partial implementations existing in the codebase. This post details the technical investigation, root causes, and the architectural decisions needed to resurrect the campaign.

What We Found: Two Incomplete Implementations

Implementation #1 – Google Apps Script (GAS)

Located at /repos/sites/queenofsandiego.com/FuneralOutreach.gs, this implementation:

  • Reads prospect data from Google Sheet 1ADx_0L6...rg38, tab Contacts (25 prospects manually loaded)
  • Sends via AWS SES using in-script SigV4 signing from admin@queenofsandiego.com
  • Implements a widening-gap cadence: Day 1 → Day 4 → Day 10 → Day 19 follow-ups
  • Tracks opens, bounces, OOO responses, and replies across 14 columns
  • Critical issue: The trigger was never installed. No InitialSent timestamps exist across all 25 rows.
  • Schema mismatch: The sheet has only 9 columns, but funeralOutreachSetup() expects 14. The initialization function was never executed.

Implementation #2 – EC2 Cron Daemon

A secondary system exists at /home/ubuntu/repos/tools/send_funeral_blast.sh:

  • Originally scheduled as a one-off cron: 5 16 24 4 * (April 24 only)
  • Invokes jada_blast.py send --campaign funeral-outreach-2026 against tools/contacts/funeral-homes-sd.csv (8 hand-entered prospects)
  • Uses template at tools/templates/funeral-outreach.html
  • Critical issue: No evidence of execution. The campaign ledger at s3://progress.queenofsandiego.com/blast-campaigns.json contains 2,649 emails across 9 campaigns, but funeral-outreach-2026 is absent.
  • No log files in tools/logs/ matching the expected funeral_blast_*.log pattern, suggesting the cron either never ran or failed silently at the approval gate.

Root Cause Analysis

Why No Emails Sent?

  • GAS trigger missing: The script exists but has no time-driven trigger installed. In Google Apps Script, code doesn't execute without an explicit trigger (time-driven, form submission, etc.).
  • Schema incomplete: funeralOutreachSetup()` was never called, leaving the sheet in an inconsistent state relative to the script's expectations.
  • Sender mismatch: The GAS implementation sends from admin@queenofsandiego.com, but the standing requirement is to use outreach@burialsatseasandiego.com via SES. This domain transition was never completed.
  • EC2 cron one-off: The cron was a single-fire job on April 24, not a recurring task. Even if it had executed, it would have been a one-time blast, not a daily 10-prospect harvester as specified in the standing rule.
  • Missing prospect harvester: Neither implementation includes logic to discover new prospects daily. The GAS version relies on manual sheet updates; the EC2 version uses a static CSV.

Data Validation

Gmail audit (last 180 days, jadasailing@gmail.com):

  • 0 sends to any of the 25 funeral home addresses
  • 0 sends from outreach@ or info@burialsatseasandiego.com
  • 0 inbound replies from prospect domains (greenwoodsd.com, lavistamemorialpark.com, neptunesandiego.com, etc.)

This confirms: the campaign never launched.

Technical Decisions for Resurrection

Path A: Quick Start (20 minutes)

Install the GAS trigger as-is, let it fire Wednesday at 9 AM PT from admin@queenofsandiego.com

Path B: Proper Implementation (recommended, ~2 hours)

Before sending a single email, complete these steps:

  1. Provision outreach@burialsatseasandiego.com in AWS SES
    • Create a new SES verified identity for the domain
    • Request production access (required for high-volume sends)
    • Configure DKIM signing
    • Store SES credentials in Secrets Manager (not in code)
  2. Extend the Google Sheet schema
    • Add missing columns to match the 14-column model in FuneralOutreach.gs: InitialSent, F1Sent, F2Sent, F3Sent, Replied, Bounce, OOO, Unsubscribe, Notes
    • Call funeralOutreachSetup() to initialize tracking columns
    • This ensures that when the script runs, it can write send timestamps and response flags back to the sheet
  3. Update FuneralOutreach.gs` to use the correct sender
    • Replace admin@queenofsandiego.com with the SES-verified outreach@burialsatseasandiego.com
    • Update the SES region endpoint if needed (default: us-west-2)
    • Validate that the sender is whitelisted in SES before trigger deployment
  4. Change cadence from weekly to daily-10
    • Install a daily time-driven trigger at 9 AM PT instead of weekly Wednesday
    • Update runFuneralOutreach()` to exit after processing max 10 new rows per day
    • This matches the standing rule and prevents accidental bulk sends
  5. Decommission the EC2 cron
    • Comment