Skip to main content

Local Business Cold-Email Engine — Operator Runbook

Verified: 2026-06-11

What this is​

The church cold-email demo mechanism (personalized demo website + AI chat/voice baked in per prospect, founder-pressed email with the demo link) extended to LOCAL SERVICE BUSINESSES — plumbing, roofing, vet, hvac, salon, etc. — under the WiseAI Agency brand. It is a generic engine built ALONGSIDE the church one: the church UnifiedTemplate and church voice paths are never touched. Offer (founder-locked 2026-06-07): AI Front Desk, $0 setup ($1,695 setup waived), then $395/mo (CAD), no free months. Pricing source is src/lib/local-business/package-config.ts + currency.ts — NOT PRICING.md (known gap). All paths below are in C:\dev\churchwiseai-web unless noted.

STALE PRICE ON THIS PAGE, flagged 2026-10-02: the offer line above and the draft check under Verification ($0 setup, $1,695 waived, $395/mo CAD) are the interim June launch figures. The founder flattened the AI Front Desk to $995 setup + $195/mo on 2026-07-10. The deployed src/lib/local-business/package-config.ts (ai_front_desk: priceSetup: 995, priceMonthly: 195) and PRICING.md both state that number; on main the figure $395 survives only in a code comment calling it the "interim launch line". The 2026-10-02 pricing decision (knowledge/decisions/2026-10-02-one-number-pricing.md) keeps the same number and puts the funeral, vet and agency AI Front Desk on ONE Stripe product. It is the same offer at the same price, not two products. Quote $995 setup + $195/mo. Never quote $395.

Phase status​

PhaseWhatStatus
0–3Demo engine: /s/{slug} business demos, per-trade theming, demo chat with full safety harnessDONE — PR #799 merged 2026-06-07, live demos verified (plumbing/roofing/vet)
Visuals/polishPer-trade hero/separator imagery, 15 trades in C:\dev\lb-assets\DONE (founder shooting custom hero MP4s later)
4Lead sourcing + master campaign_members send path + CASL unsubDONE — PR #808 merged; send route live on prod
5$0-setup cold emailDONE — campaign ee27023a (Home Services, London/Oxford): 70/70 SENT 2026-06-09, founder-pressed (5 bounces parked skipped)
6Voice on prospect demosDemo voice capture works — capture_lead fix shipped (PR #832 + voice deploy fjPBWSPgPuQX, verified end-to-end 2026-06-09). Demo-leads surface + founder alert shipped PR #868 (2026-06-11)

Architecture​

  • Demo render: src/app/s/[slug]/page.tsx → getLocalBusinessDemoData(slug) (fallback chain church → funeral → local-business) reads local_businesses + local_business_setup_profiles, shims into the PremiumChurch shape, renders via src/components/templates/HomeServicesDemo.tsx (on the ServiceBusinessTemplate seam funeral uses).
  • Per-trade config: src/lib/local-business/demo-config.ts (palette/labels/persona) + business-video-library.ts (hero/slideshow/separator media keyed by metadata.category).
  • Demo chat: src/app/api/chatbot/demo/route.ts handleBusinessDemo() — additive branch reusing the FULL church safety harness (pre-LLM crisis patterns → 988, injection refusal, leak canary). Church path byte-identical.
  • Demo voice: voice-agent-livekit/verticals/local_business/ + main.py:_build_local_business_prospect_path → LocalBusinessProspectCoordinatorAgent (demo capture_lead tags metadata.is_demo=true, NO owner-notify; endpoint POST /api/voice/local-business-lead skips notify when is_demo). Paid LocalBusinessCoordinatorAgent untouched.
  • Provisioning: src/lib/outreach/provision-local-business.ts provisionLocalBusinessDemo() — generates services/faqs/hours/staff via claude -p CLI (Rule #5 hygiene: deletes ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN/CLAUDE_CODE_ENTRYPOINT/CLAUDECODE from spawn env, prompt via stdin not shell arg, greeting-marker guard, quality gate + category-default fallback + rollback). Runners: scripts/business-prospects/provision-one.mjs "Name" trade "City" "ON" and provision-batch.mjs.
  • Data model: leads / lead_organizations / lead_events / campaign_members (NOT the church outreach_contacts model). Suppression spine: email_unsubscribers → unsub_sync trigger → leads.is_suppressed. Resend webhook matches campaign_members and bounces flip leads.is_suppressed (PR #866).
  • Asset intake: founder drops images into C:\dev\lb-assets\<trade>\ (hero ~1.78 AR, separator ~2.5 AR) → optimize via ffmpeg → public/templates/<trade>/ → register in business-video-library.ts.

How to run a batch​

  1. Source leads to a JSONL in C:\dev\lb-leads\ (see lb-leads\SCHEMA.md), then stage: scripts/business-prospects/stage-campaign.mjs — upserts leads/lead_organizations (origin_property='wiseaiagency', origin_source='cold_scrape'), creates the campaign + campaign_members; suppressed leads are skipped.
  2. Provision demos: scripts/business-prospects/provision-one.mjs (or provision-batch.mjs). Demos render at churchwiseai.com/s/{slug}.
  3. Draft emails via the single-source renderer src/lib/email.ts:renderBusinessProspectEmailHtml (re-render with scripts/business-prospects/rerender-drafts.mjs). NEVER SQL-patch draft_body. Golden guard: src/lib/outreach/business-golden.ts (forbids church prices $49.95/$54.95; TRADE_NOUNS fixes "plumbings" → "plumbers").
  4. NeverBounce: scripts/verify-campaign-members-emails.mjs --apply (writes leads.nb_result/nb_checked_at). Deliverable = valid|catchall|unknown; move bounces + seeds to status='skipped' + skip_reason — the preflight is a whole-batch blocker, so un-sendable rows must LEAVE the drafted set first.
  5. Founder preview: scripts/preview-campaign-drafts.mjs (HTML previews, e.g. C:\dev\lb-leads\real-draft-previews\) and/or scripts/business-prospects/send-founder-preview.mjs.
  6. Dry-run on prod: POST https://churchwiseai.com/api/founder/campaigns/<id>/send?dry=1&token=<FOUNDER_TOKEN> (SSE; expect preflight event pass:true; writes/sends nothing). Pull the token via vercel env pull; never print it.
  7. FOUNDER presses send: same POST without ?dry=1. Throttle 30–60s, 520s wall budget → ~12–15 sends per call, returns partial/willContinue — re-fire until complete and partial:false. Idempotent: each member is stamped sent_at + resend_message_id, re-fires never double-send. From address: WISEAIAGENCY_OUTREACH_FROM (john@wiseaiagency.com, Vercel prod). NOTE: the outreach-engine UI reads outreach_contacts and shows these campaigns EMPTY — send via the route.

Send gates & guardrails (absolute house policy)​

  • Outbound sends are founder-pressed ONLY. NO cron or auto-send, ever. DB writes / NeverBounce / re-renders are fine without blessing; sends always need founder preview + blessing.
  • Send route: src/app/api/founder/campaigns/[id]/send/route.ts (+ src/lib/outreach/{send-members,campaign-members-preflight,business-golden}.ts). Founder-token POST only (GET → 405); mandatory NeverBounce-required preflight; sends pre-rendered draft_subject/draft_body VERBATIM.
  • Every LLM step in batch scripts uses claude -p CLI — never the Anthropic/OpenAI API and never /api/chatbot/* endpoints — with ANTHROPIC_API_KEY (and auth-token/entrypoint vars) deleted from the spawn env, prompt via stdin. Batch >100 prospects with LLM → ask founder with a cost estimate first.
  • Business signature is "John Moelker / WiseAI Agency" (NOT the church pastor signature); CASL footer + ChurchWiseAI LTD address + unsubscribe required; pricing must be WiseAI Agency pricing (golden check enforces).
  • Strictly additive: never touch church UnifiedTemplate or church/funeral/vet voice paths. moderation.py is LIFE-SAFETY — untouched.

Verification​

  • Demo live: open churchwiseai.com/s/{slug} (Playwright, not curl) — populated services/staff/imagery, chat answers in-trade.
  • Drafts: SQL over campaign_members — every draft contains $1,695 + $395/mo + $0 setup, zero church prices, golden-required present / forbidden absent.
  • Preflight: prod dry-run (?dry=1) shows pass:true, sendable:N, all check counts 0.
  • After send: members stamped sent_at+resend_message_id; bounces/complaints flow via the Resend webhook into leads.is_suppressed.
  • Demo engagement: local_business_leads WHERE metadata->>'is_demo'='true' / GCC /founder/[token]/growth/demo-leads.

Open work​

  • Phase 6 (full voice productization) for paid LB customers beyond the demo path — pending.
  • Outreach-engine UI can't display/send campaign_members-model campaigns (reads outreach_contacts only) — wire to the new model or UNION.
  • Manual-unsubscribe route/UI covers only the old outreach_contacts/churches model; LB unsubs are done via SQL insert into email_unsubscribers (trigger does the rest).
  • Code-hygiene patch C:\dev\lb-leads\email-pricing-fix.patch (renderer offerLine + golden guard) — DB drafts corrected in place 2026-06-07; confirm the patch landed on main before drafting a new batch (re-render + golden check will catch it either way).
  • WiseAI Agency pricing not in PRICING.md (source remains package-config.ts).
  • Prospect browser voice calls land with empty classification metadata (non-blocking).