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.
Phase status
| Phase | What | Status |
|---|---|---|
| 0–3 | Demo engine: /s/{slug} business demos, per-trade theming, demo chat with full safety harness | DONE — PR #799 merged 2026-06-07, live demos verified (plumbing/roofing/vet) |
| Visuals/polish | Per-trade hero/separator imagery, 15 trades in C:\dev\lb-assets\ | DONE (founder shooting custom hero MP4s later) |
| 4 | Lead sourcing + master campaign_members send path + CASL unsub | DONE — PR #808 merged; send route live on prod |
| 5 | $0-setup cold email | DONE — campaign ee27023a (Home Services, London/Oxford): 70/70 SENT 2026-06-09, founder-pressed (5 bounces parked skipped) |
| 6 | Voice on prospect demos | Demo 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) readslocal_businesses+local_business_setup_profiles, shims into the PremiumChurch shape, renders viasrc/components/templates/HomeServicesDemo.tsx(on theServiceBusinessTemplateseam funeral uses). - Per-trade config:
src/lib/local-business/demo-config.ts(palette/labels/persona) +business-video-library.ts(hero/slideshow/separator media keyed bymetadata.category). - Demo chat:
src/app/api/chatbot/demo/route.tshandleBusinessDemo()— 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(democapture_leadtagsmetadata.is_demo=true, NO owner-notify; endpointPOST /api/voice/local-business-leadskips notify whenis_demo). PaidLocalBusinessCoordinatorAgentuntouched. - Provisioning:
src/lib/outreach/provision-local-business.tsprovisionLocalBusinessDemo()— generates services/faqs/hours/staff viaclaude -pCLI (Rule #5 hygiene: deletesANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN/CLAUDE_CODE_ENTRYPOINT/CLAUDECODEfrom 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"andprovision-batch.mjs. - Data model:
leads/lead_organizations/lead_events/campaign_members(NOT the churchoutreach_contactsmodel). Suppression spine:email_unsubscribers→unsub_synctrigger →leads.is_suppressed. Resend webhook matchescampaign_membersand bounces flipleads.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 inbusiness-video-library.ts.
How to run a batch
- Source leads to a JSONL in
C:\dev\lb-leads\(seelb-leads\SCHEMA.md), then stage:scripts/business-prospects/stage-campaign.mjs— upsertsleads/lead_organizations(origin_property='wiseaiagency',origin_source='cold_scrape'), creates the campaign +campaign_members; suppressed leads are skipped. - Provision demos:
scripts/business-prospects/provision-one.mjs(orprovision-batch.mjs). Demos render atchurchwiseai.com/s/{slug}. - Draft emails via the single-source renderer
src/lib/email.ts:renderBusinessProspectEmailHtml(re-render withscripts/business-prospects/rerender-drafts.mjs). NEVER SQL-patchdraft_body. Golden guard:src/lib/outreach/business-golden.ts(forbids church prices$49.95/$54.95;TRADE_NOUNSfixes "plumbings" → "plumbers"). - NeverBounce:
scripts/verify-campaign-members-emails.mjs --apply(writesleads.nb_result/nb_checked_at). Deliverable =valid|catchall|unknown; move bounces + seeds tostatus='skipped'+skip_reason— the preflight is a whole-batch blocker, so un-sendable rows must LEAVE the drafted set first. - Founder preview:
scripts/preview-campaign-drafts.mjs(HTML previews, e.g.C:\dev\lb-leads\real-draft-previews\) and/orscripts/business-prospects/send-founder-preview.mjs. - Dry-run on prod:
POST https://churchwiseai.com/api/founder/campaigns/<id>/send?dry=1&token=<FOUNDER_TOKEN>(SSE; expectpreflighteventpass:true; writes/sends nothing). Pull the token viavercel env pull; never print it. - FOUNDER presses send: same POST without
?dry=1. Throttle 30–60s, 520s wall budget → ~12–15 sends per call, returnspartial/willContinue— re-fire untilcompleteandpartial:false. Idempotent: each member is stampedsent_at+resend_message_id, re-fires never double-send. From address:WISEAIAGENCY_OUTREACH_FROM(john@wiseaiagency.com, Vercel prod). NOTE: the outreach-engine UI readsoutreach_contactsand 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-rendereddraft_subject/draft_bodyVERBATIM. - Every LLM step in batch scripts uses
claude -pCLI — never the Anthropic/OpenAI API and never/api/chatbot/*endpoints — withANTHROPIC_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
UnifiedTemplateor church/funeral/vet voice paths.moderation.pyis 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) showspass:true, sendable:N, all check counts 0. - After send: members stamped
sent_at+resend_message_id; bounces/complaints flow via the Resend webhook intoleads.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 (readsoutreach_contactsonly) — wire to the new model or UNION. - Manual-unsubscribe route/UI covers only the old
outreach_contacts/churchesmodel; LB unsubs are done via SQL insert intoemail_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 onmainbefore drafting a new batch (re-render + golden check will catch it either way). - WiseAI Agency pricing not in
PRICING.md(source remainspackage-config.ts). - Prospect browser voice calls land with empty classification
metadata(non-blocking).