Email system
Verified 2026-05-07 · Resend + Kit · all transactional flows
One overview of every email an EO contact may receive. Who triggers it. What template it uses. Where the code lives. Last verified 2026-05-07.
Resend handles every custom email we author. Kit handles periodic newsletter broadcasts only. The two are bridged by our webhook on unsubscribe events.
Transactional emails (Resend)
| Trigger | Template file | Endpoint | First-email footer? | Status | |
|---|---|---|---|---|---|
| User completes Trauma Map quiz with consent | Trauma Map result | _lib/trauma-map-email.js | /api/trauma-map/save | Helper auto-inject | live |
| Stripe purchase, product = manifesto | Manifesto welcome | _lib/manifesto-welcome-email.js | /api/webhooks/stripe | Helper auto-inject | live |
| Newsletter signup via direct form | Newsletter welcome | _lib/newsletter-welcome-email.js | /api/subscribe-newsletter | Embedded in body (no helper) | live |
| Stripe purchase, product = work-with-alex | WWA welcome | _lib/wwa-welcome-email.js (TBD) | /api/webhooks/stripe | Helper auto-inject | copy refresh pending |
Newsletter broadcasts (Kit)
Periodic newsletters are composed and sent in the Kit dashboard by AL. They go to all subscribers attached to the EO form (KIT_FORM_ID). Kit auto-injects its own unsubscribe link in every broadcast. No code involvement on our side.
| Setting | Where in Kit | Status |
|---|---|---|
| Form incentive email (auto-responder on signup) | Grow → Forms → [form] → Settings → Incentive | disabled 2026-05-07 |
| Domain authentication (DKIM/SPF) | Settings → Email → Domains | to verify before 1st broadcast |
| From email + name | Settings → Account → Sending defaults | to verify |
| Physical address (CAN-SPAM) | Settings → Email | to verify |
| Reply-to address | Settings → Account | to verify |
First-email footer helper
Helper logic: any user who receives their first email from EO must see a clear "you're now subscribed + 1-click unsubscribe" footer. Subsequent emails skip the footer (the user already knows they're on the list).
Files
- Helper: functions/api/_lib/email-footer.js
- Tracking column: contacts.first_transactional_email_at (migration 0035)
- Unsubscribe endpoint: functions/api/unsubscribe.js
- Confirmation page: public/unsubscribe.html
Functions exposed
| Function | Purpose | Used by |
|---|---|---|
| maybeAppendFirstEmailFooter | Inject footer before </body> on first email; stamp column afterwards. | Trauma Map, Manifesto welcome (and future WWA welcome) |
| buildUnsubscribeUrl | Return signed unsubscribe URL (HMAC-SHA256). | Newsletter welcome (embeds link in body) |
| stampFirstTransactionalEmailAt | Stamp the column without injecting any footer. | Newsletter welcome (after send) so subsequent emails skip auto-footer |
Unsubscribe
One-click unsubscribe for RGPD / CAN-SPAM compliance. Available on every Resend email we send. Bridges to Kit so Kit broadcasts also stop.
- Route: GET /api/unsubscribe?email=<email>&token=<hmac>
- Token: HMAC-SHA256 of the lowercased email signed with UNSUBSCRIBE_SECRET (Cloudflare Pages secret, set 2026-05-07).
- On success: D1 mutations on contacts + kit_consents, plus best-effort Kit API call to remove the subscriber from KIT_FORM_ID.
- Confirmation page: /unsubscribe.html?status=ok · /unsubscribe.html?status=invalid
Edit a copy
- Open the template file under functions/api/_lib/<name>-welcome-email.js.
- Edit the readable strings inside the HTML literal.
- Deploy site: npx wrangler pages deploy public --project-name=eliteoutsiders-site --commit-dirty=true.
- Smoke: trigger the matching flow on prod (signup / purchase / quiz) and inspect the resulting email.
Open items
- todo Work with Alex welcome email — copy refresh pending (AL: "pourri, à refaire").
- todo Resend domain verification (DKIM/SPF/DMARC) — required for high-volume deliverability.
- consider Add a "marketing" vs "transactional" classification so receipts always go through even after unsubscribe.