Skip to main content

Outreach Campaigns — Operator Runbook

What this is

The system behind ChurchWiseAI's first cold email campaign to PewSearch-listed churches that have no website. It targets 739 churches, delivers a free AI Starter Kit via per-church HMAC-tokenized landing pages, and captures intent signals (click → download → Pro Website interest → reply → convert) in Supabase. First batch launches Monday, 2026-04-13. The infrastructure is reusable for every future segment (non-claimers, denominational waves, ITW cross-sell, bounced-welcome recovery) via the church-outreach skill.

Active campaign: no-website-churches-2026-04 (id 5f006309-da0a-4784-9314-17015264fa4e, target 739, status draft as of 2026-04-12).

When to use it vs MailerLite

Use outreach-campaigns (this system)Use MailerLite
Cold, one-to-one, research-personalized first touchNewsletter broadcasts to opted-in subscribers
Compliance + do-not-contact-driven (implied-consent CASL basis)Double-opt-in marketing funnels
Per-church HMAC tokens, per-church landing pagesSame content to thousands of lead-magnet opt-ins
Reply triage into founder inboxOpen-rate / click-rate automations

One-off admin sends from the PewSearch dashboard go through Resend, not through this system.

System overview

Data flow on a single email:

  1. pull-targets.ts seeds outreach_contacts (one row per church, unique HMAC starter_kit_token, 30-day expiry).
  2. /church-outreach research N marks queued → researching, dispatches N parallel sub-agents, writes research_json, moves to researched (or do_not_contact if signals present).
  3. /church-outreach draft N picks rich vs sparse template, substitutes variables, runs the lint gate (draft-emails.ts:lintDraft), writes draft_subject + draft_body, moves to drafted.
  4. Founder reviews; agent updates to approved.
  5. Week 1: agent formats for Gmail Web UI paste. Week 2+: Gmail API sends from john@pewsearch.com. Row moves to sent.
  6. Recipient clicks → pewsearch.com/starter-kit/[token]outreach_contacts.clicked_at set, status link_clicked.
  7. PDF download → /api/outreach/kit-download/[token]kit_downloaded_at set, status kit_downloaded.
  8. "Tell me more" Pro Website click → /api/outreach/pro-website-click/[token]pro_website_clicked.
  9. Unsubscribe → /outreach/unsubscribe/[token] → POST confirmUnsubscribe Server Action → status='unsubscribed' + churches.email_do_not_contact=true.

Database schema

Two owned tables + three columns on churches (applied via pewsearch/migrations/20260412_outreach_tables.sql).

outreach_campaigns: id, name (unique), description, target_query_description, target_count, status (draft|active|paused|complete|cancelled), started_at, paused_at, completed_at, notes, created_at.

outreach_contacts: id, campaign_id, church_id, email, research_json, personalization_json, draft_subject, draft_body, subject_pattern (A|B|C), starter_kit_token (unique), starter_kit_token_expires_at, status (17 values — see below), sent_at, opened_at, clicked_at, kit_downloaded_at, pro_website_clicked_at, replied_at, reply_category, converted_at, unsubscribed_at, bounced_at, founder_notes, created_at, updated_at (trigger). Unique (campaign_id, church_id).

Status enum: queued, researching, researched, drafting, drafted, approved, scheduled, sent, bounced, opened, link_clicked, kit_downloaded, pro_website_clicked, replied, converted, unsubscribed, do_not_contact.

Indexes: campaign, church, status, token.

churches additions: email_do_not_contact (bool, default false, honored by every sender in the codebase), email_do_not_contact_reason, email_do_not_contact_at.

Current state snapshot (2026-04-12): 714 queued, 17 drafted, 8 do_not_contact.

Operational surfaces

SurfaceFilePurpose
Landing pagepewsearch/web/src/app/starter-kit/[token]/page.tsxKit download + below-fold Pro Website mention
Kit downloadpewsearch/web/src/app/api/outreach/kit-download/[token]/route.ts:10-31Redirects to /ai-starter-kit.pdf, sets kit_downloaded_at
Pro Website clickpewsearch/web/src/app/api/outreach/pro-website-click/[token]/route.tsPOST, idempotent
Unsubscribe (confirm page)pewsearch/web/src/app/outreach/unsubscribe/[token]/page.tsxPOST-only Server Action to defeat email-scanner pre-clicks
Unsubscribe (API)pewsearch/web/src/app/api/outreach/unsubscribe/[token]/route.tsAlternate POST endpoint, mirrors DNC flag to churches
HMAC helperpewsearch/web/src/lib/outreach-tokens.ts:8-37generateOutreachToken, lookupOutreachContactByToken (with expiry check)
Seed script~/.claude/skills/church-outreach/scripts/pull-targets.tsCampaign + 739-row idempotent upsert
Research orchestrator~/.claude/skills/church-outreach/scripts/dispatch-research.tsBatch pull + recordResearch()
Drafter + linter~/.claude/skills/church-outreach/scripts/draft-emails.ts:18-94VOICE_BLOCKLIST, REQUIRED_ELEMENTS, saveDraft()
Sender (Week 1)Gmail Web UI via john@pewsearch.com Send-as aliasManual paste
Sender (Week 2+)Gmail API (Phase 7 — not yet implemented)Automated with kill-switch monitoring

Sender identity: john@pewsearch.com is a Google Workspace domain alias of john@churchwiseai.com. MX + SPF + DKIM (google._domainkey, 2048-bit) + domain-verification TXT all live on Vercel DNS as of 2026-04-12 (Appendix C of the design spec).

The church-outreach skill

Location: ~/.claude/skills/church-outreach/. Commands:

CommandOne-linerStatus
plan <segment>Size the segment, create campaign row, propose calendarStub (Phase 7)
research <batch_size>Dispatch N parallel sub-agents, write research_jsonLive
draft <batch_size>Rich/sparse template, lint gate, write draft_*Live
reviewFounder approves drafts one at a timeManual (Phase 7 UI deferred)
send <ids>Respect caps + time-of-day + kill switchesWeek 1: manual paste
statusSent/replied/unsub/convert dashboardManual SQL (stub)
review-weekFriday analysis, copy iterationManual (stub)

Templates: templates/email-rich-data.md, templates/email-sparse-data.md, templates/subject-patterns.md. Reply templates (11): templates/reply-templates/*.mdsend-me-the-kit, tell-me-more, want-to-talk, send-info-later, what-does-it-cost, skeptical-of-ai, we-have-facebook, we-are-rural, not-interested, wrong-person, plus the README.md. Founder edits these for voice before Monday launch.

References: references/voice-principles.md (HEAR + voice linter regexes), references/compliance-rules.md (CASL + CAN-SPAM), references/cadence-rules.md (caps + kill switches).

Weekly cadence

WeekDaysDaily capBatchFounder behavior
1 (Apr 13–17)Mon–Fri20100Every email researched + approved
2 (Apr 20–24)Mon–Fri50250Template locked, sample-review 5, bulk-approve
3 (Apr 27–May 1)Mon–Fri100~389Full cadence; list exhausted ~Apr 30

Send window: 9:00 AM – 2:00 PM recipient local time (bucket by churches.state_code). Never weekends, never Holy Week. Day-1 throttle: 5 @ 9 AM, 5 @ 10 AM, 10 @ 11 AM to avoid new-sender spike.

Compliance — the 5 musts in every email

Enforced by the drafter's REQUIRED_ELEMENTS lint (draft-emails.ts:31-41):

  1. Accurate From/Reply-To: John Moelker <john@pewsearch.com>.
  2. Physical address: ChurchWiseAI LTD · 125 Concession Street, Ingersoll, ON N5C 1G2.
  3. One-click unsubscribe URL containing pewsearch.com/outreach/unsubscribe/.
  4. Relationship disclosure: "You're receiving this because ..." / "You're getting this because ..." naming the PewSearch listing.
  5. Non-misleading subject (no Re: / FW: on first touch).

CASL basis: conspicuously-published pastoral email on Google Business profile = 24-month implied consent. Prefer role addresses (office@, info@, churchname@) over personal @gmail.com. Every sender in the codebase MUST check churches.email_do_not_contact before sending — grep for the column name when adding a new send path.

Voice principles — HEAR applied to cold outreach

Hear open with one true specific fact from research — never "Hope this finds you well." Empathize without flattery (no compliments about their logo/mission). Advance — offer the gift, let the landing page explain. Respond — invite a reply even if they never click ("Would love to hear back either way"). And: no theology games. Never "the Lord led" / "God put you on my heart." The only honest hook is that they're listed in PewSearch without a website.

Voice linter auto-rejects (from draft-emails.ts:18-29): hope this finds/well, circle back, touch base, jump on a (quick) call, the Lord led/put on my heart, God led/brought/placed, act now/fast/today, limited time, We help churches.

Body 120–220 words. Subject 30–60 chars. Sign-off is John Moelker then Founder, ChurchWiseAI + PewSearch on the next line — no "Cheers,", no "Best,".

How to start a new campaign

  1. /church-outreach plan <segment-name> — agent queries the segment, creates outreach_campaigns row, proposes calendar. (Stub today; for now seed manually via a script mirroring pull-targets.ts.)
  2. Founder approves calendar + copy variants.
  3. /church-outreach research 20 — 3–5 min wall-clock via parallel sub-agents.
  4. /church-outreach draft 20 — lint gate enforces compliance + voice; failures return to drafter, not founder.
  5. /church-outreach review — founder approves/edits each draft.
  6. /church-outreach send <ids> — Week 1 formats for Gmail paste; Week 2+ via Gmail API.
  7. /church-outreach status daily, /church-outreach review-week each Friday.

Quick status SQL:

SELECT status, count(*) FROM outreach_contacts
WHERE campaign_id = '5f006309-da0a-4784-9314-17015264fa4e'
GROUP BY status ORDER BY count DESC;

Rolling 24h unsubscribe rate (kill-switch at 3%):

SELECT
count(*) FILTER (WHERE unsubscribed_at > now() - interval '24 hours') AS unsubs_24h,
count(*) FILTER (WHERE sent_at > now() - interval '24 hours') AS sent_24h,
round(100.0 *
count(*) FILTER (WHERE unsubscribed_at > now() - interval '24 hours')
/ NULLIF(count(*) FILTER (WHERE sent_at > now() - interval '24 hours'), 0)
, 2) AS unsub_pct
FROM outreach_contacts
WHERE campaign_id = '5f006309-da0a-4784-9314-17015264fa4e';

How to handle common reply scenarios

Pick the matching template in ~/.claude/skills/church-outreach/templates/reply-templates/ and adapt:

  • "Send me the kit / yes please" → send-me-the-kit.md, re-send the unlock URL, set reply_category='send-kit'.
  • "Tell me more" → tell-me-more.md, flag to founder, set reply_category='question'.
  • "Let's talk / schedule a call" → want-to-talk.md with Cal.com link.
  • "What's the cost?" → what-does-it-cost.md — point to pricing page, no hard pitch.
  • "Not interested" → not-interested.md, set status='do_not_contact', no reply.
  • "Wrong person / no longer here" → wrong-person.md, set status='do_not_contact', founder note.
  • Unsubscribe in reply text → honor immediately, same DB write as unsubscribe endpoint.

Every reply: update replied_at + reply_category. Founder-action replies surface in the admin UI (Phase 7).

Metrics to watch + kill switches

Track: delivered, clicked unlock link, kit downloaded, Pro Website clicked, replied, unsubscribed, converted (premium_churches.acquired_via='outreach_2026_04').

Do NOT track: open rate via pixel — Apple Mail Privacy made it meaningless.

Hard kill switches (auto-pause):

  • Rolling 24h unsubscribe rate > 3% of sent.
  • Any Gmail spam complaint via FBL.
  • Gmail disables john@pewsearch.com sending → pause 48h, resume at 50% prior volume.
  • DKIM/SPF/DMARC check fails.

Soft stops (flag founder):

  • Reply rate < 3% after 100 sends → copy isn't landing, rewrite.
  • Kit-download rate < 10% of clicks → landing page issue.
  • 30-day conversion = 0% despite healthy replies → CTA or Pro Website pitch weak.

Benchmarks: reply 8–15% good / <3% bad; unsub <1% target / 1–2% acceptable / >2% pause / >3% hard stop; 30-day conversion 1–3% is strong at this ACV.

Known hazards

  • Concurrent-agent races. Multiple Claude agents in parallel sessions can overlap on research N and double-pick the same queued rows. dispatch-research.ts:48-52 uses a queued → researching UPDATE gate, but it is not transactional across sessions. Mitigation: one outreach agent at a time. When starting, drop a sentinel file like C:/dev/.outreach-agent-running and check for it before research / draft commands. TODO: wrap the batch-claim in a single UPDATE ... WHERE status='queued' ... RETURNING statement so Postgres serializes claimants.
  • Slug / state-code mismatches. PewSearch listing URLs in email footers resolve via pewsearch.com/churches/{slug}. A change to slug-routing 2026-04-12 introduced a slug_redirects table + route fallback; verify every sent email's listing URL 404-checks before send in Week 1. If a draft references a slug that no longer resolves, fix the slug in churches, regenerate the draft, re-lint.
  • Email deliverability / warmup. We are brand-new on pewsearch.com Gmail outbound. DMARC is p=none for warmup — review at Week 3 for upgrade to p=quarantine. Hold the 20/50/100 ramp; do not accelerate even if Week 1 reply rate looks good. Any Gmail postmaster warning → pause immediately.
  • Tradition-specific voice landmines. Voice/title conventions vary: Churches of Christ (CoC) use "Minister" not "Pastor"; Catholic/Orthodox priests are "Father"; Episcopalian priests are "The Rev."; some Baptist churches use "Brother"; Quaker/Friends meetings have no pastor. Drafter defaults to Hi there when pastor_name_confidence < high — never gamble. If research returns a denomination-appropriate title, use it verbatim from research_json.pastor_name.

Monday launch readiness checklist

Before first send Monday 2026-04-13 9 AM ET:

  • outreach_campaigns + outreach_contacts migration applied (status snapshot shows 714 queued — confirmed).
  • OUTREACH_TOKEN_SECRET set in production env on both pewsearch.com and churchwiseai.com Vercel projects (use printf, not echo).
  • /starter-kit/[token] loads, click records clicked_at.
  • /api/outreach/kit-download/[token] redirects to /ai-starter-kit.pdf (TODO: confirm PDF is uploaded to pewsearch/web/public/ai-starter-kit.pdf — design spec open-question #2 not yet resolved).
  • /outreach/unsubscribe/[token] renders confirm button, POST writes both outreach_contacts and churches.email_do_not_contact.
  • Every existing sender in the codebase (Resend welcome, Stripe webhook emails, MailerLite stub) checks churches.email_do_not_contact before sending. (TODO: full audit not yet documented.)
  • DKIM passes for pewsearch.com (verified 2026-04-12).
  • Gmail Send-as john@pewsearch.com tested end-to-end.
  • 20 drafts for Monday researched, drafted, lint-passed, founder-approved. (Current DB: 17 drafted — need 3 more before Monday.)
  • 10 reply templates edited to founder's voice.
  • ai-starter-kit.pdf hosted and downloadable.

Post-launch: Week 2 transition to Gmail API + admin UI

Spec Phases 7–9 (deferred until Week 1 data is in):

  1. Gmail API OAuth for john@pewsearch.com stored in churchwiseai-web env.
  2. Cron-style job reads status='scheduled', respects daily cap, time-of-day bucket, kill switches, sends, writes sent_at.
  3. Gmail push notifications (Pub/Sub) → /api/outreach/reply webhook → Haiku 4.5 classifier → reply_category, auto-responder via Resend for send-kit-link-again, founder escalation for substantive replies.
  4. Admin UI at churchwiseai.com/admin/[token]/outreach — draft review, bulk-approve, random-sample-review, live metrics header. (Spec references churchwiseai-web/src/app/admin/[token]/outreach/page.tsx — not yet created.)

Verify Gmail Workspace daily-send quota supports the Week 3 ceiling of 100/day. Standard Workspace = 2000/day, so we are safe at 100.

Appendix — sample emails

Rich-data (Greater Nazaree Baptist, Mobile AL, Pattern A subject)

Subject: Greater Nazaree's listing on PewSearch

Hi Pastor Williams,

28 Google reviews averaging 4.8 stars says your people love what Greater
Nazaree is doing in Mobile — rare for a Baptist church of your size.

I noticed your listing on PewSearch doesn't link to a website, which is
common for Baptist churches in the South — most pastors I've talked to
either haven't had the time or haven't found a web tool that speaks
their language.

We put together a short guide called the AI Starter Kit for pastors in
exactly this spot. Normally $4.95 — for the 739 churches we're writing
to this month, Greater Nazaree included, it's free:

→ https://pewsearch.com/starter-kit/a8f2-greater-nazaree-mobile

If at some point you'd like something more, PewSearch's Pro Website
($19.95/mo, greaternazaree.pewsearch.com) includes a cinematic hero
video we hand-render from one photo of your church — typically $500+
from a freelance videographer. No pressure; the kit stands alone.

Would love to hear back either way.

John Moelker
Founder, ChurchWiseAI + PewSearch
john@pewsearch.com

---
ChurchWiseAI LTD · 125 Concession Street, Ingersoll, ON N5C 1G2
You're getting this because Greater Nazaree is listed at
pewsearch.com/churches/greater-nazaree-baptist-church-mobile-al.
Unsubscribe: https://pewsearch.com/outreach/unsubscribe/a8f2-greater-nazaree-mobile

Sparse-data (Sharon UMC, Shelby NC, Pattern B subject)

Subject: A note from PewSearch about Sharon UMC

Hi there,

I was looking through the Methodist churches in Shelby listed on PewSearch
and noticed Sharon UMC doesn't have a website attached to its listing.

That's common — especially for churches that have been around longer than
the modern "every church needs a website" assumption. I'm not here to
argue for or against one.

What I did put together is a short guide called the AI Starter Kit — a
plain-English look at what an AI assistant can actually do for a church
(answer phone calls, log prayer requests, help visitors find service
times). Normally $4.95. Here's a free copy for Sharon UMC:

→ https://pewsearch.com/starter-kit/c4e1-sharon-umc-shelby

If it's useful, great. If not, feel free to ignore this — I won't send a
follow-up unless you want one.

John Moelker
Founder, ChurchWiseAI + PewSearch

---
ChurchWiseAI LTD · 125 Concession Street, Ingersoll, ON N5C 1G2
You're getting this because Sharon UMC is listed at
pewsearch.com/churches/sharon-united-methodist-church-shelby-nc.
Unsubscribe: https://pewsearch.com/outreach/unsubscribe/c4e1-sharon-umc-shelby

TODO: Admin UI at churchwiseai.com/admin/[token]/outreach is referenced in the spec (Phases 7–9) but not yet implemented. Spec-vs-impl: kit-download route currently redirects to /ai-starter-kit.pdf but the PDF's hosting location (Supabase Storage vs static file) is still open (spec open-question #2). Cross-sender DNC audit (Resend welcomes, Stripe webhooks, MailerLite) is required by compliance but not yet enumerated in any doc.