Recipes

migrate-email-resend skill: rewrite Resend sends to posthog-node emails.send

Discovered from README cross-links · today

Software & DevOpsWork & Operations

A skill that rewrites Resend send sites to PostHog and verifies one real email.

What they did

The author extended the existing `migrate-email-resend` skill in PostHog's context-mill repo so it no longer stops at finding Resend sends and preparing the account. The old report step was replaced by three steps. Step 7 (rewrite) installs or upgrades posthog-node, checks the installed types for what the moving sites need, and sets up one server-side client with `secretKey` and `migratedFrom: 'resend'`. Each site whose plan row says it moves is rewritten in one of two forms: content from code (a React site renders with `@react-email/render` and sends the result as `html`; an `html` or `text` site sends its content as-is; each send gets a `templateKey` named for the kind of email), or a template made in PostHog (the person makes it, makes it transactional, gives its template ID in Step 3, and the site sends `templateId` with `templateData` replacing Resend's `variables`). `from`, `replyTo`, the idempotency key and `error.name` branches keep working; `distinctId` is the ID the app already identifies people with; a send to a suppressed address gets a branch treating `recipient_suppressed` like a sent email. `cc`, `bcc`, `headers`, `tags`, `attachments`, scheduled sends and batch sends stay on Resend. Step 8 (verify) runs a typecheck against step 7's baseline and the tests of changed files, then with consent sends one real email and reads `$workflows_email_sent`, an engagement event of the same workflow, and a product event on the same distinct ID through the MCP's `execute-sql`. Step 9 (report) lists moved sites, sites that stay with what would let them move, the checks, a "Before cutover" list (including a test of each PostHog template) and an "After cutover" note on when to remove Resend. Step 3 asks once per Resend template whether to move or keep it, reads its content to classify it, and asks for the PostHog template ID and sender address; Step 4 sets up a sender for that address. `mapping.md` gained rewrite rules and `account.md` covers reading a template's content. A later revision added "Link emails to your users" to Step 7, passing the `distinctId` the app already passes to `identify()` so sends and their sent/opened/clicked events land on the user's person.

How it went

The bundle test (vitest) expects steps `1-detect` to `6-key`, then `7-rewrite`, `8-verify`, `9-report`; on `d157859` `pnpm test` passes 225 tests in 29 files and `pnpm build` builds `migrate-email-resend.zip`. Two headless dry runs with Claude Code on Sonnet 5.5, read-only tools, on a fixture app with four Resend sends (React email, html and text with replyTo, the Resend template `invoice`, and one with an attachment) changed no fixture files. In the keep run, 2 of 4 sites planned to move: the React email renders with `render(<WelcomeEmail … />)` and sends `html` with `templateKey: 'welcome'`; the html/text email keeps `replyTo`; `invoice` stays (`kept on Resend for now`) and the attachment email stays (`attachments`); the client sets `migratedFrom: 'resend'`. In the move run, `invoice` moves with `templateId` and `templateData: { INVOICE_URL: invoiceUrl }`, its `from` column holds the template's sender so Step 4 covers that domain, and "Before cutover" lists a test of that template. Three review rounds plus final reads by GPT-6.1 Sol and Opus 5.5 produced no outstanding findings.

Worth knowing

The posthog-node release with `templateKey` and `migratedFrom` (PostHog/posthog-js#5509) was not out, so the merge is held and no run has written code, typechecked and sent. No run has had the PostHog MCP connected, so Step 8's event check is unexercised, and no run happened inside the Wizard. The rewrite reads the person's code and Resend templates, which PostHog doesn't store, so prod shape is unverified. The skill now edits send code, so a wrong rewrite changes how mail goes out once deployed (medium merge risk, two-way door). Test-email matching has no per-send ID, so a late event from an earlier send of the same email to the same person can count. Template content comes from a dashboard copy saved outside the repo or Resend's API with a full-access key the person deletes at the end of Step 5.

Wiring

The skill lives in the context-mill repo as a bundled skill (`migrate-email-resend.zip` built by `pnpm build`, tested with vitest). It runs as ordered steps: `1-detect` through `6-key` (from the stacked PR #450) find sends and prepare the account, then `7-rewrite`, `8-verify`, `9-report`. Step 3 asks the person once per Resend template whether to move or keep it, reads its content to classify it, and collects the PostHog template ID and sender address; Step 4 sets up a sender for that address. Step 7 installs or upgrades posthog-node, checks installed types for `templateKey` and `migratedFrom`, creates one server-side client with `secretKey` and `migratedFrom: 'resend'`, and rewrites each moving site to `posthog.emails.send` with either `templateKey` plus rendered `html`/`text` or `templateId` plus `templateData`. Step 8 typechecks against Step 7's baseline, runs tests of changed files, then with consent sends one real email and reads events via the PostHog MCP's `execute-sql`. Step 9 writes the report with real `sites_rewritten`. `mapping.md` holds the rewrite rules and `account.md` covers reading template content. The skill depends on a posthog-node release containing PostHog/posthog-js#5509 and is stacked on PR #450, which must merge first.

Try it yourself

Run the migrate-email-resend skill on this repo as a dry run first. Find every Resend send, then for each one decide whether it can move to `posthog.emails.send` from posthog-node: content-from-code sites (React rendered with `@react-email/render` to `html`, or existing `html`/`text`) get a `templateKey`; Resend templates move only if I make them in PostHog as transactional templates and give you the template ID and sender address. Keep `cc`, `bcc`, `headers`, `tags`, `attachments`, scheduled and batch sends on Resend with a written reason. Set up one server-side client with `secretKey` and `migratedFrom: 'resend'`, preserve `from`, `replyTo`, idempotency keys and `error.name` branches, pass the `distinctId` the app already uses with `identify()`, and treat `recipient_suppressed` like a sent email. Then typecheck against the pre-rewrite baseline, run the tests of changed files, and with my consent send one real email and check `$workflows_email_sent`, an engagement event of the same workflow, and a product event on the same distinct ID. Finish with a report of moved sites, sites that stay and why, a "Before cutover" checklist including a test of each PostHog template, and an "After cutover" note on removing Resend.

Source

Tools in this stack