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) andPRICING.mdboth state that number; onmainthe 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
| 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).