Features
Email: what is sent, and making sure it arrives
Every email this product sends is a template in site/lib/email/templates.tsx,
drawn in one layout (site/lib/email/layout.tsx) and sent through Resend
(site/lib/email/send.ts). With pnpm dev running, open
http://localhost:3000/dev/emails to see each one exactly as sent, and its
plain-text part.
What is sent, and by what
| Kind | Sent by | |
|---|---|---|
| Receipt (with the licence key when the provider hands it back) | transactional | the payment webhook, on a paid order |
| Repository invitation / pending | transactional | the github-invite deliverable (docs/deliverables.md) |
| Your licence keys | transactional | /recover |
| You're on the list | transactional | /api/subscribe, when RESEND_SEGMENT_ID is set |
| On sale | broadcast | pnpm broadcast on-sale |
| Refund confirmation | transactional | the payment webhook, on a refund |
| Release notes | broadcast | pnpm broadcast release |
site/lib/email/catalogue.ts is the same list in code; the dev preview and the
tests walk it, so an email cannot exist without being previewable and tested.
The rules every email keeps
They are written at the top of layout.tsx, and templates.test.ts checks
each one on every email:
- tables and inline styles only;
- a light background, always;
- no remote images;
- a plain-text part, always;
- both parts leave as ASCII;
- only the
EMAILpalette colours; - the seller and the support address in every message;
- an unsubscribe link in broadcasts, and only in broadcasts.
Sending twice is not possible (by design)
Webhooks retry. Every send carries a stable reference (receipt:<order>,
recover:<address>, …), used twice:
- as Resend's idempotency key, so a repeat within Resend's window is accepted and not delivered;
- as
X-Entity-Ref-ID, so clients thread the rare duplicate that gets through.
Degradation
- No
RESEND_API_KEY: every email is skipped and logged, and the payment still settles. Both payment providers email the licence key themselves too. - No
RESEND_SEGMENT_ID: the waitlist logs addresses instead of keeping them, and sends no confirmation.
A send failure never fails a webhook: sends run as effects, outside the answer the provider gets.
Deliverability: set this up once, before launch
Mail from a new domain lands in spam until the receiving servers can prove it is really yours. Resend walks you through it; what matters:
- Send from a subdomain, e.g.
mail.your-domain.com(Resend → Domains → Add). This keeps the sending reputation separate from the domain that receives your mail, so one never drags down the other, and it means the records below never collide with your inbox provider's records on the apex. - SPF and DKIM: add exactly the records Resend shows (an MX and a TXT for SPF on the subdomain, a TXT for DKIM). Wait for Resend to show Verified.
- DMARC on the apex: a TXT record at
_dmarc.your-domain.com. Start withv=DMARC1; p=none; rua=mailto:you@your-domain.comto receive reports; move top=quarantineonce a few weeks of reports show only your own senders. RESEND_FROM=Your App <hello@mail.your-domain.com>. Replies still go toSITE.supportEmail(every send setsreplyTo), so a customer who answers a receipt reaches a person.- Test with mail-tester.com: send one receipt to the address it gives you
(with
pnpm devand a test purchase), and aim for 9/10 or better. Then open one in Gmail and one in Apple Mail, light and dark mode.
Adding an email
/add-email (.claude/skills/add-email/SKILL.md): a function in
templates.tsx, an entry in catalogue.ts, a call site that passes a stable
refId, and the tests run themselves.
This page is docs/email.md in the repository, copied 2026-09-25.