Skip to main content

Flow — Cold Outreach (Funeral)

Status: DRAFTED Owner: john@funeralwiseai.com Last verified against prod: 2026-05-04 (initial draft — not yet run end-to-end) Related skill: none (new vertical — no skill exists yet)


1. Purpose

Drive funeral home prospects from first awareness to demo booking to paid setup. Source: scraped funeral home listings (741 prospects as of 2026-05-04, 0 conversions). This is the first commercial non-church vertical. The pipeline mirrors the church outreach pipeline but uses funeral-coded copy, secular tone, and routes bookings to the funeral-specific Cal.com event type.


2. Trigger

Triggered by the weekly scrape cron or a manual founder action.

  • Type: cron (weekly) + manual override
  • Source: /api/cron/outreach-scrape/route.ts (with vertical = 'funeral' param)
  • Schedule: Scrape Mondays 6am ET; send Wednesdays 9am ET (same pipeline, different vertical)
  • Founder UI: /founder/[token]/outreach-engine → filter by vertical=funeral

3. Preconditions

  • outreach_contacts row exists with vertical = 'funeral', status = 'provisioned'
  • preview_url populated (must use funeral preview template — NOT church template)
  • outreach_contacts.email_do_not_contact = false (checked at row level, not churches table)
  • outreach_contacts.status != 'dnc' and != 'bounced' and != 'converted'
  • tenant_voice_agents FK constraint DROPPED (Stream A — must be resolved before funeral provisioning works)
  • Funeral preview templates deployed (Stream A — funeral template variant in /preview/[slug])
  • RESEND_API_KEY active and Resend domain verified for funeralwiseai.com
  • Physical address confirmed in email footer template — BLOCKER for first send
  • Cal.com funeralwiseai-demo event type exists (founder creates manually)

4. Steps

Step 1 — Scrape

What happens: Funeral home listings (scraped from public directories — not PewSearch) are loaded into outreach_contacts with vertical = 'funeral'.

Where: Scrape source TBD — likely custom scraper targeting NFDA directory, Yellow Pages funeral category, or Google Places API. See founder for approved scrape source.

Verifications:

  • db: SELECT count(*) FROM outreach_contacts WHERE vertical='funeral' AND created_at >= now() - interval '7 days' → >0 after scrape run
  • db: SELECT name, email, website FROM outreach_contacts WHERE vertical='funeral' LIMIT 5 → spot-check quality (no churches, no empty emails)

Failure signal: Count doesn't grow → scraper blocked or rate-limited. Email field empty on all rows → source doesn't expose emails (need LinkedIn Sales Nav or manual enrichment).


Step 2 — Provision (Personalized Demo Preview)

What happens: provision.ts scrapes the funeral home website, generates funeral-specific Q&A via Haiku API, creates a demo tenant row, and populates preview_url on outreach_contacts.

Where: churchwiseai-web/src/lib/outreach/provision.tsrequires funeral vertical path (currently blocked by church_voice_agents_church_id_fkey — Stream A must fix this first)

Verifications:

  • db: SELECT preview_url, status FROM outreach_contacts WHERE id = '<prospect_id>'preview_url NOT NULL, status = 'provisioned'
  • render: GET {preview_url} → HTTP 200, uses funeral template (NOT Protestant/Catholic/Community church template), page title contains funeral home name
  • db: Demo tenant row created with correct vertical = 'funeral'

Failure signal: status = 'provision_failed' → check for FK error in provision logs (Stream A issue). preview_url uses church template → template dispatcher not checking vertical.


Step 3 — Send Email 1 (Day 0)

What happens: Send Email 1 ("What happens when a family calls [Funeral Home Name] at 2am?") via Resend. FROM address: hello@funeralwiseai.com (not churchwiseai.com).

Where: /api/cron/outreach-send/route.tscold-outreach-emails-funeral.md templates

Verifications:

  • db: SELECT status, email_1_sent_at FROM outreach_contacts WHERE id = '<prospect_id>'status = 'email_1_sent', email_1_sent_at NOT NULL
  • delivered: Gmail search to:<prospect_email> subject:"What happens when a family calls" within 15 min
  • render: Email from address is hello@funeralwiseai.com, NOT hello@churchwiseai.com
  • render: No theological language in email body (no "pastor," "congregation," "church")
  • render: CASL footer present with physical address and unsubscribe link

Failure signal: FROM address wrong → template not using FUNERAL_FROM constant. Email contains church language → copy not using funeral template.


Step 4 — Click → Preview Page

What happens: Prospect clicks demo link → /p/[token] stamps pro_website_clicked_at → 302 to /preview/[slug]?ref=outreach_{token}.

Where: churchwiseai-web/src/app/p/[token]/route.ts

Verifications:

  • db: SELECT pro_website_clicked_at, status FROM outreach_contacts WHERE id = '<prospect_id>' → NOT NULL, status = 'clicked'
  • render: Preview page loads funeral template — NOT church template. Verify: no "Your congregation" language, uses "families" not "members," no cross/steeple imagery

Failure signal: Preview shows church template → vertical routing broken in PreviewClient.tsx.


Step 5 — Chatbot Interaction (Demo)

What happens: Funeral director types questions into the demo chatbot. AI responds with funeral-home-appropriate information.

Where: /api/chatbot/stream → demo tenant row → funeral knowledge base

Verifications:

  • render: Type "What happens after someone passes away?" → chatbot describes intake process, not a church response
  • render: Type "Do you offer direct cremation?" → chatbot responds with appropriate funeral-context answer (not "we don't offer that at our church")
  • render: Type "I need grief support" → AI responds with composure and appropriate resources, does NOT use HEAR protocol theological language

Failure signal: Chatbot responds with church-coded language → vertical not isolated in chatbot stream route.


Step 6 — Demo Voice Call (Optional)

What happens: Prospect dials demo number, hears funeral-coded voice agent greeting.

Where: LiveKit voice agent → tenant_voice_agents demo row with vertical = 'funeral'

Verifications:

  • code: Voice agent greeting: "Thank you for calling [Funeral Home Name]" — NOT "Thank you for calling [Funeral Home Church]"
  • code: Agent tone is composed and dignified — no upbeat/enthusiastic tone
  • db: voice_call_logs row created after call with correct tenant_id

Failure signal: Wrong vertical in voice greeting → routing broken. Voice agent is energetic/enthusiastic → tone prompt not applied for funeral vertical.


Step 7 — Email 2 (Day 3) and Email 3 (Day 7)

What happens: Follow-up emails sent per schedule. All use funeral-coded copy.

Where: /api/cron/outreach-followup/route.ts

Verifications:

  • db: SELECT email_2_sent_at, email_3_sent_at FROM outreach_contacts WHERE id = '<prospect_id>'
  • render: Email 2 subject: "The call that came in at 11:47pm" — verify no church language
  • code: DNC gate: outreach_contacts.email_do_not_contact = true blocks all follow-ups

Step 8 — Reply / Book / Convert

What happens: Prospect clicks "Book a 20-min call" → cal.com/john-moelker/funeralwiseai-demo. Webhook records booking. Converted prospect moves to manual sales process (no self-serve checkout without setup call for funeral vertical).

Where: Cal.com webhook → outreach_contacts.status = 'booked'

Verifications:

  • db: SELECT status, booked_at FROM outreach_contacts WHERE id = '<prospect_id>'status = 'booked', booked_at NOT NULL
  • code: Cal.com event type exists: funeralwiseai-demo — verify at cal.com/john-moelker before first campaign

Failure signal: Booking not recorded → Cal.com webhook not configured. Self-serve checkout at /funeralwiseai/checkout is a parallel path — verify it routes correctly.


Step 9 — Email 4 Break-Up (Day 14)

What happens: Break-up email sent. Reply "remove me" → outreach_contacts.email_do_not_contact = true.

Where: /api/cron/outreach-followup/route.ts

Verifications:

  • db: SELECT email_4_sent_at FROM outreach_contacts WHERE id = '<prospect_id>' → NOT NULL at day 14
  • render: Email 4 subject: "Last note from me" — confirm no church language, secular close

5. What the Recipient Sees

Email 1: Subject: "What happens when a family calls [Funeral Home Name] at 2am?"

  • From: hello@funeralwiseai.com
  • Dignified, secular tone throughout
  • CTA: "[HEAR [FUNERAL HOME NAME]'S DEMO →]" → preview page with funeral template
  • Footer: CASL footer with physical address, unsubscribe link, source disclosure

Preview page: /preview/[slug] — funeral branded (no cross, no theological language), demo chatbot, demo voice number, "Book a Demo" CTA to Cal.com


6. Compliance & Unsubscribe

  • Regime: CASL (Canadian prospects) + CAN-SPAM (US prospects)
  • Unsubscribe link: Every email footer → funeralwiseai.com/unsubscribe?token={{token}}
  • DNC gate: outreach_contacts.email_do_not_contact = true — checked before every send
  • Physical address footer: Required. Must be confirmed with founder before first send.
  • Source disclosure: "You're receiving this because [Funeral Home Name] appears in public business listings"
  • Note: This vertical does NOT use churches.email_do_not_contact — it has its own DNC state on outreach_contacts

7. Failure Modes

FailureSignalAlerting path
FK constraint on tenant_voice_agentsProvision fails at step 2Logged in provision.ts — Stream A blocker
Preview uses church templateVisual verification failsFix vertical routing in PreviewClient.tsx
FROM address is churchwiseai.comEmail reputation riskFix FUNERAL_FROM constant in send route
Theological language in emailBrand/tone violationCopy review before first send
Cal.com event type missingBooking link 404sFounder creates event type manually
Physical address missingCASL non-complianceBLOCKER — do not send without address

8. Verification Manifest

flow: cold-outreach-funeral
verifications:
- step: 1
verb: db
command: SELECT count(*) FROM outreach_contacts WHERE vertical='funeral' AND created_at >= now() - interval '7 days'
expect: ">0 after scrape run"
- step: 2
verb: render
command: "GET {preview_url}"
expect: "HTTP 200, funeral template, home name in title, no church imagery"
- step: 3
verb: db
command: SELECT status, email_1_sent_at FROM outreach_contacts WHERE id = '<id>'
expect: "email_1_sent, timestamp NOT NULL"
- step: 3
verb: render
command: "Inspect email source — FROM, body, footer"
expect: "FROM = hello@funeralwiseai.com, no theological language, CASL footer present"
- step: 5
verb: render
command: "Type 'What happens after someone passes away?' in demo chatbot"
expect: "Funeral-appropriate response, no church language"
- step: 8
verb: db
command: SELECT status, booked_at FROM outreach_contacts WHERE id = '<id>'
expect: "booked, booked_at NOT NULL after Cal.com booking"

9. Open Questions / Known Gaps

  • Physical address for CASL footer not confirmed — BLOCKER
  • Funeral scrape source not finalized — where are the 741 existing prospects scraped from? Confirm with founder
  • tenant_voice_agents FK constraint — Stream A must resolve before funeral provisioning works
  • Funeral preview template — Stream A must ship before this flow can run end-to-end
  • Cal.com funeralwiseai-demo event type — founder creates manually (see knowledge/marketing/cal-com-event-types.md)
  • Resend domain verification for funeralwiseai.com — check Resend dashboard
  • outreach_contacts.email_do_not_contact column — verify it exists (rule #18: check before using)