Skip to main content

Knowledge > Runbooks > Business Ops > Launch a New Vertical

Launch a New Vertical

End-to-end playbook for launching a new WiseAI Agency vertical (funeral home, vet clinic, law firm, auto shop, etc.). All verticals share one churchwiseai-web codebase, one Supabase instance, and one Stripe account — differentiated by hostname rewrite, per-vertical tenant tables, and a vertical column on shared-core tables.

This runbook is the canonical playbook. It was last validated by the FuneralWiseAI launch on 2026-04-21/22. Follow it in order. Every numbered step has been done in production at least once.


0. Prerequisites

  • Founder has approved the vertical + launch timeline (per narrative/strategy.md vertical expansion sequencing)
  • Domain is acquired at Porkbun (funeralwiseai.com, veterinarywiseai.com, legalwiseai.com, shopwiseai.com — all held as of 2026-04-22)
  • Vertical is added to C:/dev/FEATURE_REGISTRY.md
  • Budget confirmed for new Stripe products + Telnyx number (if voice demo needed) + LiveKit secrets
  • Read the architecture decision doc: decisions/2026-04-21-multi-vertical-tenant-architecture.md

Canonical plan-key convention

Plan keys are globally unique across verticals:

VerticalPrefixExample keys
ChurchWiseAIcwa_cwa_starter_chat, cwa_pro_website, cwa_demo_prospect
FuneralWiseAIfwa_fwa_starter, fwa_demo_prospect
VetWiseAIvwa_(planned)
LegalWiseAIlwa_(planned)
ShopWiseAIswa_(planned)

Enforced via CHECK constraint on each per-vertical premium_* table (plan ~ '^fwa_' etc.). Accidental church-plan writes into a funeral table are DB-level rejected.


Phase 1: Per-vertical tables (DDL migration)

Every vertical needs three new tables. Name pattern: <vertical>s, premium_<vertical>s, <vertical>_knowledge_base.

Canonical example (funeral): see pewsearch/migrations/20260421_multi_vertical_migration_1_funeral_tables.sql.

Step 1.1 — Create the 3 tables

For VetWiseAI, write migration <yyyymmdd>_vet_tenant_tables.sql following the funeral pattern:

  1. <vertical>s — tenant identity (no public directory equivalent — these tables hold customers + demo prospects only, not a 218K-row scraped listing like churches)
  2. premium_<vertical>s — subscription + admin state. plan column with CHECK constraint plan ~ '^<prefix>_'. admin_token UNIQUE. Cascade delete from <vertical>s.
  3. <vertical>_knowledge_base — FAQ editor source. Mirror of church_knowledge_base. curation_status enum. Cascade delete from <vertical>s.

Step 1.2 — Apply via Supabase MCP

mcp__plugin_supabase_supabase__apply_migration({
name: "<vertical>_tenant_tables",
query: "<full DDL>",
})

Step 1.3 — Verify

Query information_schema.columns to confirm all 3 tables exist with expected columns, triggers, CHECK constraints, and indexes. Migration 1 DDL is safe (additive, no existing-data impact).

Reference spec: architecture/multi-vertical-schema.md has the full DDL template.


Phase 2: Shared-core table is already multi-vertical (no changes needed for verticals in the existing allowlist)

As of 2026-04-21, 12 shared-core tables have a vertical column + CHECK allowing ('church','funeral','veterinary','legal','shop'):

  • tenant_voice_agents (was church_voice_agents)
  • tenant_team_members (was church_team_members)
  • voice_urgent_requests (was voice_prayer_requests)
  • voice_visitor_inquiries (was voice_visitor_contacts)
  • voice_callback_requests, voice_call_logs, tool_invocations
  • product_knowledge, chatbot_messages, chatbot_conversations, chatbot_questions_log, organization_settings

Adding a brand-new vertical not in the allowlist (e.g., dental, restaurant) requires one DO $$ block extending the CHECK — see 20260421_multi_vertical_migration_2_shared_core.sql lines 34-56 for the pattern.

Gotcha from M2 rollout (don't repeat): the original M2 renamed column church_id → tenant_id on 3 tables (voice_call_logs, voice_callback_requests, tool_invocations) that had NO backward-compat views. Every code caller using .eq('church_id', ...) broke. Took down live voice agents for ~1 hour. M2c reverted those column renames. See feedback memory feedback_never_migrate_before_audit.md. The 4 tables that WERE renamed have backward-compat views that alias tenant_id → church_id.


Phase 3: DNS + Vercel domain

Step 3.1 — Point the domain to Vercel at Porkbun

  • Log in to Porkbun → DNS settings
  • Add A record: 76.76.21.21 (Vercel)
  • Add CNAME www: cname.vercel-dns.com

Step 3.2 — Add the domain to Vercel churchwiseai-web project

vercel domains add <vertical>wiseai.com

If the domain is currently attached to another project, first remove it via the Vercel REST API (DELETE /v9/projects/:id/domains/:domain). The CLI only supports "add" and "remove from team."

Step 3.3 — Verify SSL cert provisions within minutes


Phase 4: Hostname rewrite in middleware

Add the hostname rewrite following the existing pattern (sermonwise.ai, sharewiseai.com, funeralwiseai.com, wiseaiagency.com).

File: churchwiseai-web/src/middleware.ts

if (hostname === '<vertical>wiseai.com' || hostname === 'www.<vertical>wiseai.com') {
return NextResponse.rewrite(new URL(`/<vertical>${pathname}`, request.url));
}

Phase 5: Marketing pages

Scaffold under src/app/<vertical>/:

RoutePurposeReference
/<vertical>Home — hero, value-prop trio, how-it-works, pricing callout, founder story, CTAsrc/app/funeralwiseai/page.tsx
/<vertical>/how-it-worksHEAR protocol adapted for this verticalsrc/app/funeralwiseai/how-it-works/page.tsx
/<vertical>/pricingPricing + FAQ + guaranteesrc/app/funeralwiseai/pricing/page.tsx
/<vertical>/aboutFounder credentials, domain-appropriate positioningsrc/app/funeralwiseai/about/page.tsx
/<vertical>/bookDemo booking form (POST to /api/contact with source=<vertical>)src/app/funeralwiseai/book/page.tsx

Create layout at src/app/<vertical>/layout.tsx with data-brand="<vertical>" so CSS tokens swap to the vertical's palette.

Define brand tokens at src/styles/tokens/brands/<vertical>.css — palette, typography, accent.

Update churchwiseai-web/CLAUDE.md Pages table with all new routes.


Phase 6: Stripe products

Confirm with founder before creating in live mode (per CLAUDE.md rule 10).

Test mode first

stripe products create --name "VetWiseAI Starter"
stripe prices create --product prod_xxx --unit-amount 19900 --currency usd --recurring[interval]=month

Live mode (after founder approval)

stripe products create --name "VetWiseAI Starter" --api-key $STRIPE_LIVE_SECRET_KEY

Add price IDs to:

  • C:/dev/PRICING.md
  • knowledge/data/pricing.yaml under a new verticals: entry
  • Run pnpm derive in knowledge/ to propagate

Add env vars to Vercel (all 3 environments)

printf "price_xxx" | vercel env add STRIPE_PRICE_<VERTICAL>_STARTER_MONTHLY production
printf "price_xxx" | vercel env add STRIPE_PRICE_<VERTICAL>_STARTER_MONTHLY preview
printf "price_xxx" | vercel env add STRIPE_PRICE_<VERTICAL>_STARTER_MONTHLY development

Use printf not echoecho appends \n which Vercel stores literally, silently breaking string comparisons. See memory feedback_vercel_env_newline.md.


Phase 7: Voice agent vertical

Clone voice-agent-livekit/verticals/church/ into voice-agent-livekit/verticals/<vertical>/.

Files to create:

  • __init__.py
  • prompts.pybuild_coordinator_prompt(), build_care_prompt() with vertical-appropriate tone + HEAR adaptation
  • tools.py — vertical-specific tools (e.g., funeral: capture_arrangement_inquiry, request_director_callback, provide_chapel_info, flag_urgent_concern)
  • agents.pyCoordinatorAgent + CareAgent classes cloning the church structure
  • README.md — design notes

Critical gotchas

  1. @function_tool MUST have NO_FALLBACK_NEEDED: comment or a real fallback chain — enforced by scripts/check-tool-add.sh on push. Every tool that's a DB write or session op needs the comment. Place the comment INSIDE the function body within 40 lines of the @function_tool() decorator (the check script scans a 40-line diff window).
  2. sign_off_style param on build_shared_fragments() — pass "secular" for faith-neutral verticals (funeral, vet, law, shop). Default "faith_based" keeps church farewells ("God bless you!"). Don't change the default.
  3. Tool inserts use shared-core table names + vertical explicitvoice_visitor_inquiries + tenant_id + vertical='<vertical>'. NOT the old voice_visitor_contacts view with church_id. Writes through the backward-compat view fail CASCADED CHECK OPTION for non-church verticals.
  4. Knowledge base filtercuration_status='approved' NOT is_active=true (the latter column doesn't exist on per-vertical knowledge base tables).
  5. Register in main.py:get_agent() — route phone numbers to the vertical's CoordinatorAgent.

Deploy: C:\dev\lk.exe agent deploy --project cwa-voice --silent

Mandatory post-deploy verification (per churchwiseai-web/CLAUDE.md):

# Wait 90 seconds, then:
C:\dev\lk.exe agent logs --project cwa-voice --id CA_pX3Me4NK6qK8 --log-type deploy
# Look for "registered worker" + zero CRITICAL/ERROR lines

lk agent list status "Available" is MISLEADING — only logs tell the truth.


Phase 8: Admin dashboard scaffold

Create src/app/admin/<vertical>/[token]/:

  • page.tsx — server component, reads x-founder-token or token in params, queries premium_<vertical>s WHERE admin_token=token joined to <vertical>s, renders dashboard shell
  • layout.tsx — sets data-brand="<vertical>" for palette

Create server-only query helpers in src/lib/<vertical>-queries.ts (import 'server-only'):

  • getPremium<Vertical>ByToken(token)
  • get<Vertical>ById(id)
  • resolve<Vertical>Token(token) — returns { premium, <verticalHome> } or null

Create client-safe types in src/lib/<vertical>-shared.ts:

  • <VerticalHome> type matching the tenant table
  • Premium<VerticalHome> type (omit admin_token + Stripe secrets — server-only fields)
  • Plan constants
  • Label helpers

Client/server boundary discipline: if a 'use client' component needs a type or constant, import from -shared.ts, NEVER from -queries.ts. Per CLAUDE.md rule, server-only modules pull in supabase etc. and crash the browser bundle.

Minimum viable admin scaffold is an Overview tab showing subscription state, plan, admin token, contact info — full Calls / Settings / etc. tabs come later per customer demand.


Phase 9: Provision.ts vertical-aware

src/lib/outreach/provision.ts already has a VERTICAL_CONFIG dispatch map. Add an entry for your vertical:

const VERTICAL_CONFIG: Record<string, VerticalConfig> = {
church: { tenantTable: 'churches', premiumTable: 'premium_churches', tenantFkColumn: 'church_id', planPrefix: 'cwa_' },
funeral: { tenantTable: 'funeral_homes', premiumTable: 'premium_funeral_homes', tenantFkColumn: 'funeral_home_id', planPrefix: 'fwa_' },
// Add:
veterinary: { tenantTable: 'veterinary_clinics', premiumTable: 'premium_veterinary_clinics', tenantFkColumn: 'clinic_id', planPrefix: 'vwa_' },
};

That's it. The dispatch handles identity, subscription, organization_settings, voice agent, and AI-generated Q&A (routed to <vertical>_knowledge_base via a vertical branch in step 6 of the provision flow — see src/lib/outreach/provision.ts for the full pattern).

Gotcha — canned_responses is church-FK-locked

canned_responses.church_id has a hard FK to churches.id. Funeral path can't insert here. AI-generated Q&A for non-church verticals goes into <vertical>_knowledge_base instead. Plan accordingly when adapting provision.


Phase 10: Public render at /s/[slug]

The public render at src/app/s/[slug]/page.tsx now supports multi-vertical lookup (added 2026-04-22, PR #135). It tries getProWebsiteData(slug) first (church), falls back to getFuneralDemoData(slug) (funeral).

To add a new vertical:

  1. Create get<Vertical>DemoData(slug) helper in the same file or a shared module
  2. Add it to the fallback chain in both generateMetadata and ProWebsitePage
  3. The helper returns a PremiumChurch + Church-shaped adapter so the existing ServiceBusinessTemplate renders without modification. Set website_template='service_business' on the adapter to force the prospect-demo branch (not UnifiedTemplate which assumes church fields).

Known render gap — FA-057

The current ServiceBusinessTemplate is MINIMAL — good for "doesn't 404" but not for serious prospect conversion. Full-parity public demos (branded hero, sections, interactive chatbot, live voice demo) are tracked under FA-057 in C:/dev/FOUNDER_ACTIONS.md. Funeral chatbot widget is explicitly disabled in the adapter (chatbot_enabled=false) because <vertical>_knowledge_base → unified_rag_content sync isn't wired yet. New verticals will inherit the same gap until FA-057 ships the pattern.


Phase 11: Knowledge + documentation

Step 11.1 — Product overview

Create knowledge/products/<vertical>wiseai/overview.md following the FuneralWiseAI template:

  • Status (domain, codebase, status, deploy branch)
  • Positioning (tone, palette, value proposition)
  • Pricing (with canonical plan keys)
  • Pages (site map)
  • HEAR protocol application
  • Technical architecture
  • Strategic role

Step 11.2 — Decision doc (if significant departure from existing patterns)

If the vertical needs custom treatment (e.g., multi-clinic-chain support for vet, per-attorney branding for law), write a decision doc at knowledge/decisions/<date>-<vertical>-<shape>.md.

Step 11.3 — Product knowledge

INSERT INTO product_knowledge (category, question, answer, keywords, priority, vertical)
VALUES ('<vertical>wiseai', 'What does <Vertical>WiseAI do?', 'Answer...', ARRAY['keyword1'], 8, '<vertical>');

Run validate_product_knowledge() after — per feedback_verify_pk_after_update.md.

Step 11.4 — Update indexes

  • knowledge/INDEX.md — add pointers to the new product + decision docs
  • knowledge/products/README.md — add the vertical to the Product Map + family diagram
  • C:/dev/CLAUDE.md quick-reference line — include the vertical's pricing
  • knowledge/data/products.yaml — add entry, run pnpm derive

Phase 12: End-to-end validation

Once all the above is in place, prove the pipeline works end-to-end:

FOUNDER_TOKEN=$(vercel env pull .env.vercel.tmp --environment=production && \
grep "^FOUNDER_TOKEN=" .env.vercel.tmp | sed 's/^FOUNDER_TOKEN=//' | tr -d '"')

curl -sS -X POST "https://churchwiseai.com/api/outreach/scrape" \
-H "Content-Type: application/json" \
-H "x-founder-token: $FOUNDER_TOKEN" \
-d '{"url":"https://example-vet-clinic.com/","vertical":"veterinary"}'

Verify via Supabase:

  • New row in veterinary_clinics (not churches)
  • New row in premium_veterinary_clinics with plan='vwa_demo_prospect'
  • tenant_voice_agents row with vertical='veterinary'
  • organization_settings row with vertical='veterinary'
  • veterinary_knowledge_base has AI-generated Q&A rows
  • Public URL at /s/<slug> renders (minimal template — FA-057 upgrades this)

Phase 13: Registry + launch

  1. Update C:/dev/FEATURE_REGISTRY.md — mark vertical launch features complete
  2. Deploy voice agent (requires founder approval)
  3. Test end-to-end from a fresh prospect: visit domain → marketing pages → chatbot → voice demo
  4. Document in C:/dev/DECISION_LOG.md
  5. Record in C:/dev/ACTIVE_WORK.md — new workstream for outreach campaigns or close the launch workstream

Verification checklist

  • Domain resolves to the vertical's home page with correct branding
  • Checkout flow uses the correct Stripe price IDs
  • Voice agent routes phone calls to the correct <vertical> handlers (verified via lk agent logs)
  • Scrape-and-demo provisions into vertical-specific tables (not church tables)
  • /s/<slug> renders for vertical demos (not 404)
  • ops_error_reports is clean 30 minutes post-launch
  • voice-health cron returns HEALTHY within 5 minutes of voice deploy
  • product_knowledge for the vertical passes validate_product_knowledge()

Known follow-up gaps (shared across all verticals)

These are tracked as separate FAs and shouldn't block the launch, but will matter before real-prospect outreach:

FAGap
FA-057Full-parity public demo (branded hero, sections, interactive chatbot, live voice demo). Current ServiceBusinessTemplate is minimal.
(pending)<vertical>_knowledge_base → unified_rag_content sync so chatbot can read per-tenant FAQs
(pending)Per-vertical shared demo phone number + dispatch rule for live voice demos

See Also