Skip to main content

Email Deliverability — outbound auth audit

Why this exists

On 2026-05-26, Anthony (founder's nephew, IT professional) signed up for a free trial and reported that every email landed in junk. The two contributors we already addressed in this PR:

  1. Plain-text alternative bodies are now sent with every HTML message (htmlToPlainText in email-provider.ts). Major filters score multipart/alt noticeably higher than HTML-only.
  2. The /signin flow at /api/onboard/resend-link now has a public UI, so we'll see deliverability symptoms instead of silently failing recoveries.

The remaining contributor is outbound authentication — SPF, DKIM, and DMARC. These records live in the registrar (Porkbun) and the Resend dashboard, not the codebase, so they need a founder hand to verify.

This runbook is what you run to close the loop.


What gets sent and from where

StreamSubject typesVolumeFromProvider
Transactional (auth, billing, ops)Welcome / magic link / receipt / lifecycle / inbox notificationslow (1–50/day)ChurchWiseAI <hello@churchwiseai.com>Resend primary → AWS SES fallback
Outreach (cold + warm marketing)Cold prospects, win-back, newsletterhigh (~hundreds/day)varies, currently hello@churchwiseai.comResend
Founder correspondenceOne-off replieslowjohn@churchwiseai.comGoogle Workspace (out of scope)

The deliverability problem: today, all three streams share the same SPF/DKIM identity. A bad cold-outreach run can tank the reputation of the magic-link stream. The hardening plan below separates them.


Action 1 — verify what's published today

Run these from any terminal (Windows PowerShell or Mac/Linux). Both dig and nslookup are fine.

# SPF
nslookup -type=TXT churchwiseai.com

# DMARC
nslookup -type=TXT _dmarc.churchwiseai.com

# Resend DKIM (selector is "resend")
nslookup -type=TXT resend._domainkey.churchwiseai.com

# SES DKIM — three selectors, all configured at once when SES verifies the domain
nslookup -type=TXT <SELECTOR>._domainkey.churchwiseai.com

You can also paste churchwiseai.com into mxtoolbox.com/SuperTool.aspx and run SPF Record Lookup, DMARC Lookup, and DKIM Lookup — it'll show what's published with friendly explanations.

What you want to see

SPF (TXT record on root churchwiseai.com):

v=spf1 include:_spf.google.com include:amazonses.com include:_spf.resend.com -all
  • include:_spf.google.com — Google Workspace (founder mail)
  • include:amazonses.com — SES fallback
  • include:_spf.resend.com — Resend (primary)
  • -all — reject mail from anything not listed (strict, what we want)

If you see ~all instead of -all, it's "soft fail" — still acceptable but spam filters trust it less. Tighten to -all once Resend, SES, and Google are all known-good.

DMARC (TXT record on _dmarc.churchwiseai.com):

v=DMARC1; p=none; rua=mailto:dmarc@churchwiseai.com; ruf=mailto:dmarc@churchwiseai.com; fo=1; adkim=r; aspf=r
  • Start with p=none (monitoring only). Resend and SES dashboards will start reporting alignment after a few days.
  • After a week of clean reports, move to p=quarantine (suspicious mail → recipient's junk).
  • After another week of clean reports, move to p=reject (suspicious mail → discarded by recipient).
  • rua= and ruf= should point to an inbox you actually read — dmarc@churchwiseai.com (set up forwarding to john@) is fine.

DKIM (Resend):

  • Resend dashboard → Domains → churchwiseai.com → "Add records." Resend gives you exact TXT records to paste into Porkbun. Selector is typically resend._domainkey.churchwiseai.com.
  • After paste, click Verify in Resend. Must show green ✅ before relying on it.

DKIM (SES):

  • SES console → Verified identities → churchwiseai.com → "Authentication." SES will give three TXT records (selector1/2/3._domainkey.churchwiseai.com). All three must be present in Porkbun.

The current single-identity setup means a cold-outreach reputation hit affects magic-link delivery. Industry standard is to split:

UseNew from-addressDNS root
Transactional (welcome, magic link, receipt, lifecycle, inbox notifications)ChurchWiseAI <hello@auth.churchwiseai.com>auth.churchwiseai.com
Outreach (cold + warm marketing)variesmail.churchwiseai.com

Each subdomain gets its own SPF + DKIM + DMARC chain in Porkbun and its own verification in Resend. Once split:

  1. The auth subdomain only sends low-volume, high-engagement mail — its reputation rises and stays high.
  2. The marketing subdomain handles the cold-outreach blast — if it ever gets flagged, magic links keep landing in the inbox.
  3. The root domain still has SPF / DMARC, but its include: chain is shorter so it's harder to bust.

Founder action (pending): decide if/when to split. The minimum viable version of this PR ships the plain-text body and the /signin page; the subdomain split is a separate ~1 hr DNS task once Resend dashboard access is in hand.


Action 3 — quick wins inside the templates

Already shipped in this PR:

  • ✅ Plain-text alternative on every send.

Still to verify per template (low priority but worth a pass):

  • Subject lines ≤ 50 characters.
  • At least one List-Unsubscribe header on marketing sends (Resend supports the headers field — already wired in email-provider.ts).
  • No link shorteners.
  • Sender display name uses a real person on cold outreach (e.g., Pastor John Moelker <john@churchwiseai.com> not ChurchWiseAI <hello@churchwiseai.com>). Already done in some outreach paths; audit the rest.
  • BIMI + VMC for logo display in Gmail / Apple Mail — nice-to-have once DMARC is at p=quarantine or p=reject.

Action 4 — see it in the real inbox

Use mail-tester.com to spam-score every transactional template before considering this work done. Steps:

  1. Visit mail-tester.com — copy the unique test address it shows.
  2. From a deployed preview, trigger each transactional template addressed to that test address:
    • Welcome (use the resend-link flow with a known admin email)
    • Lifecycle (cron at /api/cron/lifecycle-emails)
    • Inbox notification (send a test prayer request to a test premium account)
  3. Click "Then check your score" — anything below 8/10 needs investigation.

Run the same battery after the subdomain split to confirm auth.churchwiseai.com scores higher than the current single-identity setup.


Founder action items

A small punch list for after this PR merges. Track in FOUNDER_ACTIONS.md.

#ActionOwnerTime
1Run the four nslookups above; paste output into a comment on this PRfounder5 min
2Spam-score the welcome email via mail-tester.com from a deployed previewfounder10 min
3Decide on the auth/marketing subdomain split (this runbook §Action 2)founderdiscussion
4Move DMARC from p=nonep=quarantine after a week of clean Resend reportsfounder5 min
5Add BIMI + VMC once DMARC is at quarantinefounder30 min + VMC cost

Change history

  • 2026-05-26 — initial doc, written after Anthony reported every onboarding email landing in junk. Companion to feat(email): auto-derive plain-text body for every transactional send.