Captured live from
https://api.resend.com using the key in .env.local
(RESEND_API_KEY) on 2026-08-13. Automations and templates ARE queryable and
creatable via the Resend API (GET/POST /automations, GET/POST /templates,
POST /templates/{id}/publish) — this page is proof, and Gap 4 below was closed the
same way. Re-run the calls below to refresh; nothing here is hand-maintained. Resend
has no staging environment — this is production, so POST /events/send sends a
real email.Design at a glance
Custom events (7)
Event names are namespacedcodex: — Resend reserves the resend: prefix for its own
system events. Only one carries a declared schema.
All product-milestone sends (everything except signup and billing) go to every
domain_owner of the affected org, resolved via
members → user_profiles. Every
sender is best-effort: a missing RESEND_API_KEY or a Resend outage is swallowed
(.catch() at the call site) and never fails the request that triggered it.
Automations (7)
Each automation triggers onevent_received for exactly one event above.
New User Signup — the full free-tier drip (completed 2026-08-13)
All sixfree-signup-* templates are now wired in, end to end. Timing changed too
— the gap before the second email dropped from the original 1 week to 1 day.
As-built observation, not a defect flag:
free-signup-02-belief-breaker is
referenced by two separate send_email steps (day 1 and day 4) — every other
template in the sequence is sent exactly once. This may be a deliberate second touch
on the same asset, or an artifact of assembling the sequence in the Resend dashboard.
Worth a glance next time the sequence is edited, not urgent.Subscription Plan Changed — the only branching automation
Three static templates, one per plan — not one dynamic template driven by aPLAN_NAME variable, even though the code computes plan_name for the payload. See
Gap 3.
Subscription Cancelled — added 2026-08-13
Deliberately plan-agnostic copy — it does not referenceevent.plan_name. Given
Gap 2’s unresolved
question about whether Resend auto-binds event-payload keys to template variables,
this automation was built to not depend on the answer either way.
Templates (14)
All published, dark-themed (#070c1c ink / #02c39a teal — the product’s own brand
tokens), sender PolicyCodex <info@mailer.policycodex.cloud>.
Segments / Audiences (1)
“General” (6decbfb4-997b-45b3-b984-cc89246f0db5). Resend exposes the same
contact list under two API paths — GET /audiences/{id}/contacts and
GET /segments/{id}/contacts return identical data; this is one resource with a
naming transition in Resend’s own API, not two lists to keep in sync. Membership is
added only by the New User Signup automation’s add_to_segment step; nothing in this
codebase removes a contact from it (cancellation only sets unsubscribed: true via
the Contacts API, which stops sends without leaving the segment).
Webhooks (1)
Signed with Svix, verified against
RESEND_WEBHOOK_SIGNING_SECRET (distinct from
Clerk’s signing secrets). See Growth data flows for how events
become email_sends/email_events rows.
API keys (4, Resend’s own — not this repo’s secrets)
Findings for engineering
Gap 1: RECIPIENT_FIRST_NAME is very likely never populated
Two call sites send a lowercase snake_case payload key, first_name:
clerk-to-resend’s handleUserCreated and github-scan’s notifyGithubMilestones
(first-scan branch). The templates that want a first name declare the variable as
RECIPIENT_FIRST_NAME (free-signup-01-love-letter, event-first-full-scan).
Neither automation step maps event.first_name → RECIPIENT_FIRST_NAME explicitly (the
one place this codebase does map variables explicitly is New User Signup’s second
send_email step, for APP_URL/DASHBOARD_URL). Unless Resend does case/format-
insensitive auto-binding from event payload keys to variable names, every send of
these two templates falls back to the literal string “there” — never a name.
Confirm behaviour with a live probe (see below) before treating this as certain.
Gap 2: plan_name payload field is unused
clerk-to-resend computes a display name (planDisplayName(planSlug)) and sends it
as plan_name in the codex:subscription_plan_changed payload. But the automation
branches into three static templates (one hardcoded name per plan in the HTML
itself), none of which declares a PLAN_NAME variable. The computed value is
currently dead code from Resend’s perspective — either the templates were meant to
merge, or the branch-to-static-template design superseded it and plan_name can be
dropped from the payload.
Operational note: demo provisioning writes into the live segment
pnpm demo:provision creates real Clerk users for the ACME demo estate
(owner+clerk_test@acme.example.com etc.), which fire real user.created webhooks,
which flow through this exact pipeline into the live “General” segment — confirmed
live: all three ACME demo addresses are in it today. Harmless (synthetic addresses,
Clerk test-mode) but worth knowing before reading segment size as a real-lead count.
Reproducing this audit
GET /automations and GET /templates are read-only inventory calls, safe to run
any time. POST /events/send sends a real email to the address given — only use
your own inbox, never a customer’s.
