Skip to main content

Handoff: Lifecycle Email System — Phase 3 Triggers, QA, KB Wiring

Created: 2026-03-27 Context: Phase 1-2 are live (infrastructure, onboarding, win-back, CWA cross-promo). Phase 3 sequences have templates built but triggers not yet wired. Marketing opt-in compliance added to PewSearch. MailerLite KB updated.


Read These First

  1. C:\dev\knowledge\architecture\lifecycle-email-system.md — FULL architecture spec (sequences, skip conditions, DB schema)
  2. C:\dev\knowledge\handoffs\lifecycle-email-system-handoff.md — original Phase 1-2 handoff (completed)
  3. C:\dev\knowledge\integrations\mailerlite.md — MailerLite source of truth (updated 2026-03-27)
  4. C:\dev\churchwiseai-web\src\lib\lifecycle-emails.ts — the sequence engine (all 11 sequences + 25+ templates)
  5. C:\dev\churchwiseai-web\src\app\api\cron\lifecycle-emails\route.ts — the daily cron
  6. C:\dev\CLAUDE.md — cross-project rules

What's Already Live

ComponentStatusBranchHow It Works
lifecycle_emails_sent tableLiveSupabaseUNIQUE(church_id, email_key) = state machine
Cron jobLivemain (CWA)Daily 8am ET via vercel.json
Onboarding (Starter/Pro/Suite)LivemainCron processes days 2/5/7/13/30 for active churches
Welcome + Starter Kit emailsLivemainWebhook fires immediately, logs to sent table
Win-back (cancelled churches)LivemainCron processes days 3/14/30 after cancellation
CWA → ShareWise cross-promoLivemainCron processes day 7 for active CWA churches
Marketing opt-in (PewSearch)Livemaster (PS)ClaimForm + PastorLeadCapture + API gates
MailerLite KB docLivemaster (KB)Updated with lifecycle system, compliance, automation status

Part 1: Wire Phase 3 Triggers

What's missing

Six sequences have templates built in lifecycle-emails.ts but NO trigger wired:

#SequenceTemplate KeysTrigger NeededWhere Trigger Lives
4Newsletter Welcomenewsletter_welcome, newsletter_why_ai, newsletter_demoNewsletter signupCWA /api/newsletter, PS /api/subscribe, ITW /api/newsletter
57-Day AI Ministry Coursecourse_day1 through course_day7Newsletter signup (concurrent with #4)Same as above
6Kit Buyer Nurturekit_delivery, kit_followup, kit_upgradeStarter Kit purchaseCWA Stripe webhook (product === 'ai_starter_kit')
7PewSearch → CWAxpromo_ps_cwa_0, xpromo_ps_cwa_5, xpromo_ps_cwa_14PewSearch claim (paid or free)PS /api/leads/capture + PS Stripe webhook
8ITW → SermonWisexpromo_itw_swITW Premium signupITW Stripe webhook or Supabase Auth signup
9SermonWise → ITWxpromo_sw_itwSermonWise Pro signupCWA Stripe webhook (product === 'sermon_pro')

How to wire each trigger

Sequences 4 & 5: Newsletter + Course

Problem: Newsletter subscribers aren't in premium_churches — they're in email_subscribers. The cron currently only queries premium_churches. Non-church sequences need a different data source.

Approach: Add a new section to the cron that queries email_subscribers for recent signups and processes newsletter/course sequences.

Steps:

  1. The email_subscribers table has: id, email, source, tags, status, created_at. The created_at is the signup date — use this as the sequence start date.
  2. For newsletter + course, the church_id in lifecycle_emails_sent should use a synthetic ID (e.g., hash of email) since these subscribers aren't churches.
  3. Add a section to the cron route after the church processing:
    // 4. Process newsletter + course sequences
    const { data: subscribers } = await supabase
    .from('email_subscribers')
    .select('id, email, created_at, tags, status')
    .eq('status', 'active')
    .gte('created_at', thirtyDaysAgo); // only process recent signups
  4. For each subscriber, check lifecycle_emails_sent using their id as church_id (repurpose the column for non-church sequences).
  5. Compute daysSinceSignup from created_at, check schedules for both NEWSLETTER_WELCOME and AI_MINISTRY_COURSE.
  6. LifecycleEmailProps won't have church-specific fields — use defaults: churchName: '', pastorName: '', adminToken: ''.
  7. The newsletter/course templates in renderEmailHtml don't reference church-specific props, so this works.

Alternative simpler approach: Instead of modifying the cron, fire the immediate emails (day 0) from the signup API routes, and add the subscriber to lifecycle_emails_sent with their subscriber id as church_id. The cron then picks up the follow-ups. This matches how the webhook handles onboarding.

Sequence 6: Kit Buyer Nurture

Problem: The Starter Kit already has a sendStarterKitDeliveryEmail in email.ts fired by the Stripe webhook. The lifecycle system also has kit_delivery, kit_followup, kit_upgrade templates.

Approach: In the Stripe webhook's AI Starter Kit handler (line ~237-257 of webhook/route.ts), after sending the existing delivery email, log kit_delivery to lifecycle_emails_sent using the customer email hash as church_id. The cron handles days 3 and 7.

Steps:

  1. In the webhook's if (session.metadata?.product === 'ai_starter_kit') block:
    • After sendStarterKitDeliveryEmail, insert into lifecycle_emails_sent:
      church_id: crypto.randomUUID() // or hash of email
      email: customerEmail
      email_key: 'kit_delivery'
      sequence: 'kit_nurture'
  2. Add a cron section that queries lifecycle_emails_sent entries with sequence = 'kit_nurture' and email_key = 'kit_delivery' to find kit buyers, then processes kit_followup (day 3) and kit_upgrade (day 7) based on sent_at of the delivery email.

Design decision needed: The current cron structure uses premium_churches as the source of truth for church sequences and lifecycle_emails_sent as the dedup log. For non-church sequences (newsletter, course, kit), we need a different approach since there's no premium_churches row. Options:

  • Option A: Use email_subscribers.id as church_id in lifecycle_emails_sent (repurpose the column)
  • Option B: Add a nullable subscriber_id column to lifecycle_emails_sent and query by that
  • Option C: Use a deterministic UUID derived from the email address (e.g., uuid5(email))

Recommendation: Option A is simplest. The church_id column is a UUID — email_subscribers.id is also a UUID. The UNIQUE constraint on (church_id, email_key) still works for dedup. Just name it clearly in comments.

Sequence 7: PewSearch → CWA Cross-Promo

Trigger: When a church claims their PewSearch listing (paid claim via /api/stripe/pre-checkout or free claim via /api/leads/capture).

Steps:

  1. In PewSearch's /api/leads/capture (free claim): after creating the premium_churches row, log xpromo_ps_cwa_0 to lifecycle_emails_sent via the CWA lifecycle system.
  2. Problem: PewSearch doesn't have direct access to sendAndLog (that's in churchwiseai-web). Two options:
    • Option A (recommended): Add the PewSearch subscriber to lifecycle_emails_sent directly via Supabase (both codebases share the same DB). Log xpromo_ps_cwa_0 as immediate: true from the PewSearch API route. The cron picks up days 5 and 14.
    • Option B: Create a webhook/API in CWA that PewSearch calls to trigger cross-promo sequences.
  3. For paid claims: PewSearch's Stripe webhook also activates the church. Add the same logging there.
  4. The cron needs a new section to process crosspromo_ps_cwa entries: query lifecycle_emails_sent where sequence = 'crosspromo_ps_cwa' and email_key = 'xpromo_ps_cwa_0', compute days since sent_at, send follow-ups.

Sequences 8 & 9: ITW ↔ SermonWise Cross-Promo

Trigger: When a user subscribes to ITW Premium or SermonWise Pro.

Steps:

  1. ITW Premium signup: In churchwiseai-web/src/app/api/stripe/webhook/route.ts, the ITW Premium checkout is NOT currently handled (ITW uses Supabase Auth, not Stripe for basic subscriptions, but Premium is Stripe). Check if there's an ITW Premium Stripe flow. If not, this trigger needs to be added to the ITW signup flow.
  2. SermonWise Pro signup: Already handled in the CWA Stripe webhook at line ~260-279 (session.metadata?.product === 'sermon_pro'). Add xpromo_sw_itw logging here.
  3. Same pattern as Kit Buyer: log the immediate email, let cron handle follow-ups.

Part 2: Deep QA Review

Internal QA (Agent Self-Review)

Run these checks AFTER wiring all Phase 3 triggers:

2A. Email Content QA

For EVERY email template in renderEmailHtml():

  1. Brand compliance — Sacred Gold (#D4AF37), Navy (#1B365D), Cream (#FEFCF8) used correctly
  2. CAN-SPAM compliance — marketing emails have unsubscribe link in footer
  3. Personalization${props.churchName}, ${props.pastorName} render correctly (not "undefined")
  4. Links work — dashboard URL, chat page URL, pricing page URL all valid
  5. Plan-specific content — Starter/Pro/Suite emails show correct tool counts (25/35/39)
  6. Tone — warm, pastoral, not corporate. "We're rooting for your church" energy.
  7. Length — 3-4 paragraphs max. Pastors are busy.

2B. Dedup QA

  1. Manually insert a test row into lifecycle_emails_sent for the demo church
  2. Run the cron — verify it does NOT re-send that email
  3. Call sendAndLog directly with same church_id + email_key — verify it returns { sent: false, skipped: 'already_sent' }
  4. Delete the test row after verification

2C. Skip Condition QA

For the demo church (00000000-0000-4000-a000-000000000001):

  1. Add custom_hours to premium_churches → verify day2_setup_starter is skipped
  2. Add a FAQ to church_knowledge_base → verify day2_setup_pro is skipped
  3. Add doctrinal_overrides → verify day5_theology_pro is skipped
  4. Add a chatbot_conversation → verify day7_activation_pro is skipped
  5. Remove each one → verify the email would now be sent

2D. Cron Safety QA

  1. Run cron twice in succession — verify 0 emails sent on second run (dedup)
  2. Verify email_opt_out = true on a test church → 0 emails sent
  3. Verify cancelled church gets win-back but NOT onboarding emails
  4. Verify active church gets onboarding but NOT win-back emails
  5. Check Supabase 1000-row limit: if premium_churches grows > 1000, the cron will silently stop processing extras. Add .range(0, 5000) or paginate.

2E. Stripe Webhook QA

  1. Create a test checkout session with metadata.church_id = demo church
  2. Verify welcome email is sent AND logged to lifecycle_emails_sent
  3. Verify starter kit email is sent AND logged
  4. Verify running cron after checkout does NOT re-send welcome/kit

External QA (Production Verification)

After deploying to Vercel:

2F. Resend Dashboard

  1. Check Resend dashboard for delivery confirmation of all test emails
  2. Verify no bounces or complaints
  3. Verify sender address is hello@churchwiseai.com

2G. Live Cron Verification

  1. After Vercel deploy, check Vercel cron logs for lifecycle-emails route
  2. Verify it returns { processed: true, sent: 0, skipped: 0 } (no active churches to process yet)
  3. If demo church has activated_at set and status = 'active', some onboarding emails may fire — check Resend dashboard

2H. PewSearch Claim Flow

  1. Visit pewsearch.com/claim/[demo-slug]
  2. Verify the marketing opt-in checkbox is visible below the declaration
  3. Test with GDPR timezone (use browser dev tools to override timezone) — verify checkbox starts unchecked
  4. Complete a test claim → verify marketing_opt_in is stored in premium_churches

2I. Email Rendering

  1. Send a test email to a real inbox (founder's email)
  2. Check rendering in: Gmail web, Gmail mobile, Outlook, Apple Mail
  3. Verify: CTA buttons are clickable, images/gradients render, footer links work
  4. Verify: unsubscribe link goes to admin preferences page

Part 3: KB System Wiring

3A. Update manifest.yaml

The lifecycle email system and MailerLite changes need to be tracked in knowledge/manifest.yaml.

Add these entries:

  1. Under the architecture section, add lifecycle-email-system.md with its code-files:
    • churchwiseai-web/src/lib/lifecycle-emails.ts
    • churchwiseai-web/src/app/api/cron/lifecycle-emails/route.ts
    • churchwiseai-web/src/app/api/stripe/webhook/route.ts
    • churchwiseai-web/vercel.json
  2. Under the integrations section, verify mailerlite.md has its code-files listed:
    • churchwiseai-web/src/lib/mailerlite.ts
    • churchwiseai-web/src/lib/mailerlite-groups.ts
    • churchwiseai-web/src/app/api/mailerlite/subscribe/route.ts
    • pewsearch/web/src/app/api/leads/capture/route.ts

3B. Update INDEX.md

Verify knowledge/INDEX.md includes:

  1. architecture/lifecycle-email-system.md in the Architecture section
  2. integrations/mailerlite.md in the Integrations section (should already be there)
  3. Both docs should have correct descriptions matching their frontmatter

Run pnpm derive from C:\dev\knowledge\ to regenerate INDEX.md if needed.

3C. Cross-Reference Check

These docs reference each other — verify links are correct:

  1. lifecycle-email-system.md → should link to mailerlite.md in "See Also"
  2. mailerlite.md → should link to lifecycle-email-system.md (already added 2026-03-27)
  3. mailerlite.md code-files list → should include lifecycle-emails.ts (already updated)
  4. PRICING.md → no changes needed (lifecycle emails reference pricing but don't modify it)

3D. Verify No Stale References

Check that no existing KB doc references MailerLite automations as the primary email system:

  1. knowledge/narrative/customer-journey.md — may reference MailerLite automations for onboarding. Update to reference lifecycle system.
  2. knowledge/narrative/operations.md — may reference MailerLite for email ops. Update.
  3. knowledge/processes/ — check for any email-related SOPs that reference MailerLite automations.

Search: grep -r "MailerLite.*automation\|automation.*MailerLite" knowledge/

3E. Decision Log

Append to C:\dev\DECISION_LOG.md:

## 2026-03-27 — Lifecycle Email System + Marketing Compliance
- Built code-based lifecycle email system replacing all 9 MailerLite automations
- 11 sequences, 25+ templates, daily cron, INSERT-before-send dedup
- MailerLite role reduced to: newsletter subscriber CRM + Care broadcasts only
- Added CAN-SPAM/GDPR marketing opt-in to PewSearch claim forms (ClaimForm + PastorLeadCapture)
- Phase 3 triggers (newsletter, course, kit, cross-promos) templates built but not yet wired

Files You'll Touch

Phase 3 Triggers (Part 1)

FileWhat to do
churchwiseai-web/src/app/api/cron/lifecycle-emails/route.tsAdd sections 4-6: newsletter subscribers, kit buyers, cross-promo triggers
churchwiseai-web/src/app/api/stripe/webhook/route.tsAdd kit buyer + SermonWise Pro lifecycle logging
churchwiseai-web/src/app/api/newsletter/route.tsFire immediate newsletter_welcome + course_day1, log to lifecycle_emails_sent
pewsearch/web/src/app/api/leads/capture/route.tsLog xpromo_ps_cwa_0 to lifecycle_emails_sent
pewsearch/web/src/app/api/stripe/webhook/route.tsLog xpromo_ps_cwa_0 for paid claims

QA (Part 2)

FileWhat to do
churchwiseai-web/src/lib/lifecycle-emails.tsReview all templates for brand, tone, links
Demo church data in SupabaseSet up test scenarios for skip conditions

KB Wiring (Part 3)

FileWhat to do
knowledge/manifest.yamlAdd lifecycle-email-system.md entry with code-files
knowledge/INDEX.mdVerify/regenerate via pnpm derive
knowledge/narrative/customer-journey.mdUpdate MailerLite automation references
knowledge/narrative/operations.mdUpdate email ops references
C:\dev\DECISION_LOG.mdAppend lifecycle email + compliance decision

Testing Approach

  • Use demo church (churchwiseai-demo, UUID 00000000-0000-4000-a000-000000000001) for all church-based tests
  • For non-church sequences, use a test email address (founder's email or test@churchwiseai.com)
  • DO NOT write junk data to production — only use demo church + test subscribers
  • Manually trigger cron via GET request with Authorization: Bearer <CRON_SECRET> header
  • Check Resend dashboard after each test for delivery confirmation
  • Verify lifecycle_emails_sent table has correct entries after each test

Order of Operations

  1. Wire Phase 3 triggers (Part 1) — newsletter, course, kit nurture, cross-promos
  2. Build and deploy — verify no TypeScript errors, push to main/master
  3. Run internal QA (Part 2A-E) — dedup, skip conditions, cron safety, content
  4. Deploy and run external QA (Part 2F-I) — Resend dashboard, live cron, email rendering
  5. Wire KB (Part 3A-E) — manifest, INDEX, cross-references, stale references, decision log
  6. Disable MailerLite automations — one by one, ONLY after each replacement is verified working