Repository navigation
Conversation
Teaches a coding agent to send one transactional email from server code with PostHog: verify a sender through the PostHog MCP first, keep the template as an HTML file in the repository, upload it by key, and send it with posthog-node or the HTTP API. Ships the transactional email API docs page as its reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Check the PostHog MCP before any sender work, never recreate an existing sender, bound verification polling, set the default sender through a verify, ask before any test send, and cover Next.js and edge runtimes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… file The MCP upload passes source.repository from the origin remote without credentials, source.path from the repository root and source.ref from HEAD. A new section sends a change to a code-managed template to its file and leaves the upload to posthog-node or the deploy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reverted file goes live only through an upload, because a send with an older published hash gets the newer content. Any send from the changed code publishes it. A by-key change is uploaded after the change. An agent without the repository tells the person or requests the change. The SDK always sends a path, so only repository and ref keep their stored value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A revert to published content needs an upload only for templateFile(). A by-key change waits for the deploy step when there is one. A test through workflows-send-email publishes nothing, and an agent without the repository shares the PostHog Code task link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The skill now writes a local template in the app's code and sends it with templateKey, subject, html and templateData. The first send uploads it, so the MCP upload, templateFile() and the deploy-time upload by key go. Rendered content and templates made in PostHog (templateId) are the other two forms. Sends carry an idempotency key and retry with it. Job runners pass maxRetries: 0 and a key derived from the email's purpose. STEP 9 lists the contract's codes, including the 409 for a template edited in PostHog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Inside the Wizard the agent asks through wizard_ask, writes the secret key with set_env_values, reaches PostHog tools through posthog_exec, and ends a blocked run with an [ABORT] reason the Wizard's transactional-email program matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Wizard's login may lack the workflows and integrations scopes, so a hidden tool there no longer stops the run as "API not enabled". The Wizard section names its tools as the runtime shows them, and replyTo no longer needs a verified sender. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The migrate group keeps Statsig's mapping and SDK reference under references/statsig/, and its step files point there, but the generator copied only the top-level reference files. Copy references/<variant-id>/ into each variant's bundle, and nothing of the other variants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move the sender setup of send-transactional-email's STEP 3 into
context/shared/email-sender-setup.md, so the email migration skill
includes the same text. A partial now takes the indentation of its
{{> name}} line on every line, so it nests under a step. The built
send skill is unchanged byte for byte.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0 prepares a move off Resend and changes no send call site. It finds every Resend send, classifies each one as ready to move or staying on Resend with a reason, sets up the PostHog sender next to Resend's DNS records, imports Resend's suppression list when the import tool exists, hands over the project secret key, checks the sending tier, and writes posthog-migrate-email-resend-report.md with a summary block. Steps are provider-neutral; references/resend/ holds what is specific to Resend. Statsig and Resend now publish as wizard migrate subcommands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixture runs wrote reasons into the summary block, which the Wizard can't parse, and counted a forwarding helper as one site or as each caller. The report step now shows bare values, and a site is the place that decides a send's content and fields. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 22, 2026
Author
|
Note 🤖 Claude Opus 5.5 writing on behalf of Michael Decisions made while building this PR:
|
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A team on Resend that wants its transactional email on PostHog has no skill for the move. Its coding agent would have to find every send, decide which ones PostHog can take, set up the sender without breaking Resend's DNS, and carry the suppression list over, all by hand.
With this skill,
migrate-email-resenddoes the preparation and changes no send call site: it finds every Resend send, classifies each one as ready to move or staying on Resend with a reason, sets up the PostHog sender next to Resend's records, imports Resend's suppression list, hands over the secret key, checks the sending tier, and writesposthog-migrate-email-resend-report.md. The next slice moves the sites.Important
Stacked on #433. Merge #433 first. This PR's own commits start after
161a6fd:161a6fd...0a48cff.Tracking: Silthus/posthog#539, slice 14 of the Resend parity spec. Contract: PostHog/posthog#120921.
Impact
Pays into Grow Workflows through Switched orgs: teams that move their transactional email from Resend to PostHog. Not measured yet: it needs the migration marker on each send (PostHog/posthog#120628) and the Wizard's migration summary. Expected move: large, up, once the cutover slice and
wizard migrate resendship. This slice alone moves no mail.Merge Danger
Warning
🟡 Merge risk: medium · two-way door (merge-risk rubric)
skill-menu.jsonchanges for the Wizard:migrate-statsigmoves fromcommand: migratetoparentCommand: migrate, command: statsig, andmigrate-email-resendjoins asmigrate resend. Today's Wizard runs barewizard migratefrom a hardcoded skill id, so Statsig keeps working. The family command comes with the Wizard PR.references/<variant-id>/folder now ships (onlymigratehas one today), and a partial takes its directive's indentation (every existing{{> }}line is unindented, so their output is unchanged).transactional-email-api.mdon posthog.com (PostHog/posthog.com#21306). A revert removes the skill from the next release.Summary
A new skill group,
context/skills/migrate-email/, with one variant per provider. Resend is the first. The seven steps are provider-neutral;references/resend/holds everything specific to Resend, so a SendGrid or Postmark variant is mostly three reference files.api.resend.com, orsmtp.resend.com. React Email or Nodemailer alone is not Resend:[ABORT] no email provider sends foundbefore any change.template,react,html,text) and fields, written to.posthog-email-migration-plan.md.mapping.mddecides field by field:cc,bcc,headers,tagsandattachmentsmove within the contract's limits;scheduledAt,topicId, batch sends, SMTP, more than oneto,last_eventreads, and error names PostHog lacks stay on Resend with a reason.fromdomain, set up with the sender setup ofsend-transactional-email. Resend's records stay; PostHog's MAIL FROM label must not be Resend'ssend.email-suppressions-add, from the dashboard CSV orGET /suppressions. Addresses never land in a file of the repository, the plan, or the report. That tool does not exist yet (see Open below): until it does, the step recordsnot importedand the report tells the person to add them before cutover.phs_key withhog_flow:writeforPOSTHOG_PROJECT_SECRET_API_KEY. The report lists every other place the Resend key lives, such as CI and docs.Other changes:
send-transactional-email's sender setup moves intocontext/shared/email-sender-setup.md, included by both skills. The built send skill is unchanged byte for byte.fix(skills): the generator copied only top-level reference files, somigrate-statsigshipped withoutreferences/statsig/mapping.mdandsdk-reference.mdthat its steps tell the agent to read. Both now ship.AGENTS.mdlists the migrate subcommands.Evidence
Bundle and generator tests (vitest, red then green):
migrate-email-skill.test.js:migrate-email-resendandmigrate-statsigbuild with everyreferences/...path they name present in the bundle, the API page and the Resend files ship without the send skill, and both publish asmigratesubcommands. Red before the skill: 3 of 4 fail (expected undefined to be defined).skill-generator-references-folder.test.js: a variant ships its own folder and no other variant's. Red before the fix:ENOENT ... references/statsig/mapping.md.partial-includes.test.js: an indented directive indents every partial line. Red before: the first bullet lost its indent.pnpm buildpasses with the docs page stubbed in.docs-cache(it answers404until #21306).Ranking in the MCP's
SkillCatalog(itsskill-catalog.tsfrom PostHog/posthog master over every built bundle):migrate-statsig(126)migrate-email-resend(1162)feature-flags-ios(138)migrate-email-resend(990)omnibus-send-transactional-email(133)migrate-email-resend(1011)Agent runs on fixture apps. Claude Code on Sonnet 5.5 with only this skill installed, headless, the PostHog MCP not connected, and the person's answers given in the prompt. Every run changed no send call site: the diff is one commented
POSTHOG_PROJECT_SECRET_API_KEYline in the env example, plus the report.resendSDK (PostHog/wizard-workbench#2221)sendUserEmail. Summary parses every timefetch('https://api.resend.com/emails'), noresendpackagescheduled_at), the invoice stays (attachment bypath). The Resend key in the deploy workflow is listedThe first two REST runs found two skill gaps, fixed in
0a48cff: a run wrote reasons into a summary value, and runs disagreed whether a helper that only forwards its callers' payload is one site. The reruns agree and parse.Happy fixture report, excerpt
cliEntriesthe Wizard reads, the MCPSkillCatalogranking, and the files an agent run leaves in a fixture.pnpm exec vitest run scripts/lib/tests/migrate-email-skill.test.js→ 4 passed.wizard migrate resendneeds the Wizard PR), no run with the PostHog MCP connected, so sender creation, DNS publish, and sender verification on a deployed project are unproven. The suppression import has no tool to run against yet.Decisions
migrate-emailgroup, one variant per provider; Resend parts stay in its folderemail-suppressions-add, and recordnot importeduntil it exists; the existing one-address route has no MCP toolsuppressions_readyafter the spec's five keysmigratenow; today's Wizard ignores the entryOpen: the server half of the suppression import, a bulk route and the
email-suppressions-addMCP tool in PostHog/posthog. Recommended as its own slice.Involvement: no grilling rounds, no prototypes, one Opus 5.5 review of the strand (#435 and #436), proof captured. Its findings were on rules the cutover uses (suppressed recipients, subjects, fallback quoting, idempotency across providers), and the fixes landed in #436, where those rules apply.
🤖 Generated with Claude Code (Claude Opus 5.5)