Skip to main content

Knowledge > Processes > Email Sending Flows

Email Sending Flows

Three email systems serve distinct purposes, all managed from churchwiseai-web/:

  1. Resend transactional — one-to-one system-triggered emails (magic links, welcome, payment failed, trial ending, etc.)
  2. Resend lifecycle — onboarding drips, win-back, cross-promo sequences via lifecycle-emails.ts + daily cron
  3. MailerLite subscriber CRM — newsletter management and audience segmentation (subscriber CRM only; lifecycle automation has moved to the Resend lifecycle system)

System 1: Transactional Email (Resend)

All transactional emails are sent via the Resend API, implemented in churchwiseai-web/src/lib/email.ts. The Resend client is lazily instantiated using RESEND_API_KEY.

Sender Addresses

PropertyFrom Address
ChurchWiseAIChurchWiseAI <hello@churchwiseai.com>
PewSearchPewSearch <hello@pewsearch.com>
IllustrateTheWordIllustrateTheWord <hello@illustratetheword.com>

Email Types and Triggers

1. WELCOME EMAIL (sendPremiumWelcomeEmail)
TRIGGER: Onboarding completes OR Stripe webhook activates church
TO: admin_email from premium_churches
CONTAINS:
- Magic link: /auth/magic?t={admin_token}
- Dashboard access instructions
- If includesVoice=true: phone setup notice
("ready within 2 business days")
- Feature list (voice, chatbot, care, team)

2. PAYMENT FAILED (sendPaymentFailedEmail)
TRIGGER: Stripe invoice.payment_failed webhook
TO: admin_email
CONTAINS:
- Warning about service suspension
- Link to Stripe billing portal (portalUrl)
- Support contact

3. TRIAL ENDING (sendTrialEndingEmail)
TRIGGER: Scheduled job (3 days before trial_end)
TO: admin_email
CONTAINS:
- List of features they'll lose
- "No action needed if payment method on file"
- Dashboard link via magic link

4. UPGRADE NOTIFICATION (sendUpgradeNotificationEmail)
TRIGGER: Team member initiates plan change
TO: admin_email
CONTAINS:
- Who initiated (name + email)
- New plan label (e.g., "AI Chatbot Pro ($34.95/mo)")

5. COMMUNITY SUBMISSION (sendCommunitySubmissionEmail)
TRIGGER: Church submits to community marketplace
TO: founder notification address
CONTAINS: Submission details for review

6. STARTER KIT DELIVERY (sendStarterKitDeliveryEmail)
TRIGGER: AI Starter Kit purchase ($4.95 one-time)
TO: purchaser email
CONTAINS: Kit download/access links

7. VOICE SETUP ALERT (sendVoiceSetupAlertEmail)
TRIGGER: Voice plan activated, phone number needed
TO: founder/ops email
CONTAINS: Church name, plan, setup instructions

8. PHONE READY (sendPhoneReadyEmail)
TRIGGER: Agent manually triggers after Twilio setup
TO: admin_email
CONTAINS: Dedicated phone number, forwarding instructions

9. TEAM INVITE (sendTeamInviteEmail)
TRIGGER: Admin adds team member
TO: new team member email
CONTAINS:
- Role label (e.g., "Prayer Team")
- Direct dashboard link with access_token
- What tabs they can access (from ROLE_TABS)

Voice Agent Notifications (Python — core/notifications.py)

The voice agent sends real-time alerts for safety events and contact captures. These use Resend API directly from Python (not through the Node.js email.ts).

THREAT ALERTS:
send_threat_alert_email → church notification_email
send_threat_alert_sms → church notification_phone (Twilio)
send_threat_alert_to_support → ChurchWiseAI support team
TRIGGERED BY: check_threat() in moderation.py

CRISIS ALERTS:
send_crisis_alert_email → church notification_email
send_crisis_alert_sms → church notification_phone
send_crisis_alert_to_support → ChurchWiseAI support team
TRIGGERED BY: check_crisis() in moderation.py

PRAYER/VISITOR/CALLBACK:
Notification emails sent when tools capture contact info
TO: church notification_email
CONTAINS: caller phone, request details, dashboard link

System 2: Marketing Email (MailerLite)

MailerLite handles newsletter subscriptions, subscriber CRM, and cross-property subscriber sync. MailerLite is not used for subscriber lifecycle automation (that moved to the Resend lifecycle system). Implemented in churchwiseai-web/src/lib/mailerlite.ts.

Architecture

ALL PROPERTIES (ITW, PewSearch, CWA)
|
| POST /api/mailerlite/subscribe (CWA proxy)
| POST /api/newsletter (CWA direct)
v
syncSubscriber(opts)
|
v
MailerLite REST API
POST /subscribers (upsert by email)
body: { email, fields: {name}, groups: [...], status: "active" }

KEY DESIGN DECISIONS:
- All calls are fire-and-forget (non-blocking)
- If MAILERLITE_API_KEY is not set, functions silently skip
- MailerLite POST /subscribers is an idempotent upsert
- Group membership controls which automations trigger

Subscriber Sync Triggers

1. ONBOARDING (api/onboard/route.ts)
WHEN: Church completes onboarding form
ACTION: syncSubscriber({
email, name,
groups: [ML_GROUPS.customers],
fields: { church_name, plan }
})

2. NEWSLETTER SIGNUP (api/newsletter/route.ts)
WHEN: Visitor fills email capture form
ACTION: syncSubscriber({
email, name,
groups: [ML_GROUPS.newsletter],
})

3. CROSS-PROPERTY PROXY (api/mailerlite/subscribe/route.ts)
WHEN: ITW or PewSearch newsletter form submits
ACTION: syncSubscriber with property-specific group
NOTE: ITW and PewSearch don't have their own MailerLite key;
they POST to CWA's proxy endpoint

4. STRIPE WEBHOOK (api/stripe/webhook/route.ts)
WHEN: Subscription created or cancelled
ACTION: syncSubscriber with updated plan fields,
add to customers group or move to churned group

Available Functions

syncSubscriber({ email, name?, groups?, fields?, status? })
Add or update a subscriber (upsert by email)

removeFromGroup(subscriberId, groupId)
Remove subscriber from a specific group

updateFields(email, fields)
Update custom fields on existing subscriber

Proxy Pattern

ITW and PewSearch newsletter signups route through ChurchWiseAI's MailerLite proxy because there is ONE MailerLite account (churchwiseai@gmail.com) across all properties. The JWT key lives in churchwiseai-web/.env.local as MAILERLITE_API_KEY.

IllustrateTheWord signup form
→ POST https://churchwiseai.com/api/mailerlite/subscribe
→ body: { email, name, source: "itw" }
→ syncSubscriber with ITW-specific group

PewSearch signup form
→ POST https://churchwiseai.com/api/mailerlite/subscribe
→ body: { email, name, source: "pewsearch" }
→ syncSubscriber with PewSearch-specific group