Repository navigation
Conversation
Silthus
force-pushed
the
feat/migrate-email-resend
branch
from
October 31, 2026 09:53
0a48cff to
6e902dc
Compare
Silthus
force-pushed
the
feat/migrate-email-resend-cutover
branch
from
October 31, 2026 09:53
4499957 to
03011c3
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The migrate-email skill now rewrites every call site PostHog can send: hosted Resend templates become local templates, React and html/text sites send rendered content, and the client carries migratedFrom. It checks the build and one email on one person before it writes the report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d provider in tests Fixture runs looped over or truncated a one-recipient list, and one run mocked posthog-node to rebuild a Resend request. The rewrite now narrows the helper's parameter to one address, and the verify step asserts on the fields posthog.emails.send gets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rate-email A moved site now treats recipient_suppressed like a send that went out, a site-built subject becomes a module placeholder so the template hash stays stable, fallbacks use single quotes, and the report warns that idempotency keys don't cross providers. The rules and Step 7 no longer contradict the mapping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… uses sending cap Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-email posthog-node has no migratedFrom option. The Wizard records the migration on its setup wizard finished event instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-email rewrites a site A posthog-node release with posthog.emails can still lack templateKey, local template content, or the extra send fields. The rewrite now checks the installed types for what each moving site needs, and keeps a site on Resend when the latest release lacks it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silthus
force-pushed
the
feat/migrate-email-resend-cutover
branch
from
November 1, 2026 13:04
03011c3 to
9cd85f5
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rt every behaviour change Step 8 checks that the app sends to the MCP's project, notes the start of the test, and counts only events since then whose workflow matches the send. It compares typecheck errors with a baseline step 7 takes before it changes code. A template's content waits in a temporary file for step 7 instead of in memory, an HTML fallback keeps a template on Resend, and a derived templateKey is normalized and unique. The report lists each suppressed-recipient branch, copy-address refusal, and cross-provider idempotency risk, and reminds the person to delete the full-access Resend key. The posthog-node client setup is inlined into step 7, and description.md says this skill wins over the general moving guide. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… keep template files traceable Step 8 takes the start of the test from `execute-sql` and compares Unix timestamps, so the project's timezone can't shift the window. The plan lists each saved template file, step 7 reads the module from it, and step 9 deletes every listed file. A derived templateKey falls back to the template id when the name normalizes to nothing and stays unique across the run. Step 8 may fix other posthog-node code the upgrade breaks, and the report lists those files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ndency upgrades The clock query carries a fresh random literal so a cached answer can't widen the test window. Step 3 may write the template files it saves, the dashboard copy includes the template's alias or name for its key, and a major dependency upgrade lands in the report's "Before cutover" list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
Author
|
Moved to PostHog#451. |
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.
Moved here from PostHog#436, closed on 2026-10-25 so this fork stack is the only copy. It opens upstream again after review.
This revision
Restacked on #1 on 2026-10-31. What changed:
03011c3removesmigratedFromfrom the posthog-node client in step 7, because posthog-node has no such option. The Wizard recordsmigrated_from: resendon its existingsetup wizard finishedevent.9cd85f5(review fix): step 7 installs the latest posthog-node and checks its types for what each moving site needs:templateKeywith inline content,templateDatawith local content for a template stored at Resend, and each extra send field the site sets. A site that needs a missing capability stays on Resend withmoves: no — posthog-node has no <capability> yet, and the rest go on.transactional-emails.mdandapi.mdfor error codes.e51df39merges feat(skills): add the migrate-email skill with a Resend variant #1's QA swarm fixes (41ca66a,0e526b9).8c841cd(QA swarm round 3): step 8 checks that the app sends to the MCP's project, notes the start of the test, and counts only events since then whose$workflow_idmatches the send. Step 7 takes a typecheck baseline that step 8 compares with. A template's content waits in a temporary file for step 7, an HTML fallback keeps a template on Resend, and a derivedtemplateKeyis normalized and unique. The report carries each behaviour note from the rewrite and the reminder to delete the full-access Resend key. Theemail-node-clientpartial is inlined into step 7.020b877merges feat(skills): add the migrate-email skill with a Resend variant #1's4ad8c6e.8452a82(QA swarm fixes round): step 8 takes the start of the test fromexecute-sqland compares Unix timestamps; the plan lists each saved template file, step 7 reads it, and step 9 deletes it; atemplateKeyfalls back to the template id and stays unique across the run; step 8 may fix other posthog-node code the upgrade breaks, and the report lists it.53a190cand50b5f27(QA swarm final reads): both of step 8's queries carry a fresh random literal soexecute-sqlcan't answer from its cache; step 3 may write the template files it saves; the dashboard copy includes the template's alias or name; a major dependency upgrade lands in "Before cutover".moving-from-other-providers.md(feat(messaging): add a skill that offers and sets up transactional emails PostHog/posthog#125415): that reference maps any provider's send to a PostHog template. This PR moves Resend sends in code to the code form of posthog-node: the field mapping, the Resend template conversion to Liquid, the suppressed-recipient and idempotency rules, and the verification chain from the send to the engagement and product events.Problem
migrate-email-resend(Silthus/context-mill#1) finds every Resend send and prepares the account, but it leaves every call site on Resend. The person still rewrites each send by hand.With this PR the skill does the cutover in code. It moves each send PostHog can take to
posthog.emails.sendfromposthog-node, leaves the rest on Resend with a reason, checks the build and one email on one person, and writes the report with the realsites_rewritten.Important
Stacked on Silthus/context-mill#1, which is based on
main. Merge it first. This PR's own commits start after6e902dc:6e902dc...03011c3.posthog.emails.sendwith a template ID is in posthog-node (PostHog/posthog-js#5418). This skill sends withtemplateKey, the code form, which posthog-node does not have yet. Hold the merge until a posthog-node release has the code form.Tracking: Silthus/posthog#539, slice 15 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. This is the slice that moves mail. The Wizard records
migrated_from: resendon its existingsetup wizard finishedevent when a run uses this skill, so a migration shows in the Wizard's own events. Expected move: large, up, oncewizard migrate resendships and the skill is released.Merge Danger
Warning
🟡 Merge risk: medium · two-way door (merge-risk rubric)
Summary
Three steps replace the old step 7:
posthog-nodeclient withsecretKey. Then each site whose plan row says it moves:{{{NAME}}}turns into{{ variables.NAME }}, and a fallback turns into Liquid'sdefault. The call site sendstemplateKey,subject,htmlandtemplateDatawith the oldvariables. A template with an HTML-valued variable or an HTML fallback, or anything beyond plain variables and fallbacks, stays on Resend. Step 3 saves the content in a temporary file, and step 7 writes the module from it. ThetemplateKeyis the alias or name, lowercased, with other characters turned into-, and unique.@react-email/render, andhtml/textsites send their content as is. Each gets atemplateKeynamed for the kind of email.from,replyTo,cc,bcc,headers,tags,attachmentsand the idempotency key move unchanged.error.namebranches keep working.distinctIdis the ID the app already identifies people with.{{ variables.* }}placeholders in the module, so the template hash stays stable and doesn't upload a new version on every send. Renderedhtmlandreactcontent sends withouttemplateDataand uploads nothing.recipient_suppressed), where Resend dropped it later. A site that throws or retries on errors gets a branch that treats it like a sent email, unless it setsccorbcc, where the report explains that a suppressed copy address now stops the email.$workflows_email_sent, an engagement event with the same$workflow_id, and a product event on the same distinct ID, all after the start of the test, read through the MCP'sexecute-sql.sites_rewrittenis now real.mapping.mdgains a Rewrite section,account.mdcovers getting a template's content (dashboard copy orGET /templates/{id}with a full-access key), andsdk-reference.mdadds template fallbacks and defaults.Evidence
Bundle test (vitest, red then green):
runs the rewrite and verify steps between the key and the reportexpects the workflow1-detect…6-key,7-rewrite,8-verify,9-report. Red before:7-report.mdwhere the three new steps belong. Lane gate on50b5f27:vitest run scripts/plugins/tests scripts/lib/tests, 29 files, 225 tests pass. Fork CI runs no workflows on pull requests.Agent runs on fixture apps. Claude Code on Sonnet 5.5 with only this skill installed, headless, with no PostHog MCP and the person's answers in the prompt.
posthog-nodecame from a local build of the release branch, because the release isn't out yet.resendSDK (PostHog/wizard-workbench#2221)from 'resend'left, 2posthog.emails.sendfor 2 old sends.svelte-check0 errors, mailer tests 3 passed. The first two runs looped or truncated a one-recipient list; after the fix both narrow it to one addresswelcome(a plain variable and a fallback) moved,order-receipt(HTML rows in a variable) stayed with that reason.tscpasses. Every report notes that an empty name now shows the fallbackpathattachment stayed.icsattachment (Silthus/next-session)4499957maxRetries: 0because the app's retrier retries. Convextscpasses, 665 tests pass. Before the fix, one run kept the tests green by mocking posthog-node to rebuild a Resend request; the rerun fakesposthog.emails.sendand asserts on its options. The run on the reviewed head also returns early onrecipient_suppressed(the site sets no cc) and lists the cross-provider duplicate in "Before cutover"Templates render as before. Each moved
welcomemodule, rendered with liquidjs (HTML escaping on) and compared with Resend's triple-brace output:"After entity decode" is
&in the link'shrefwritten as&, which browsers read back as&.On the wire (earlier revision, with the
migratedFromclient option that03011c3removes). The migrated template app'ssendWelcomeagainst a local stub of the PostHog API:pnpm exec vitest run scripts/lib/tests/migrate-email-skill.test.jspasses.03011c3. It drops one client option and renames one docs file in step 8, so the rewrite steps the runs above exercised are unchanged.8c841cd, which changes step 8's query and adds the typecheck baseline and the temporary template file.Decisions
GET /templates/{id}with a full-access key the person creates and deletes afterwards; the same key now also reads suppressions (RESEND_FULL_ACCESS_API_KEY)execute-sqlrecipient_suppressedlike a sent email, except on sites withcc/bcc, which keep failing with a report notesubjectandtextwithout HTML escaping (EMAIL_PLAIN_TEXT_FIELDSinnodejs/src/cdp/services/hog-inputs.service.ts)migratedFromclient optionmigrated_fromonsetup wizard finishedinsteadsites_rewrittencounts moved sites$workflow_idmatching the send's; no message id is on the eventsif_code_managedandif_not_superseded, so it never replaces a template edited in PostHog; another app's code-managed key is a follow-uptemplateDataand uploads nothingFollow-up: a
templateKeythat another app's code-managed template already uses in the same project. It needs a tool that lists the project's template keys, which the skill's MCP tools don't name today.Involvement: no grilling rounds, no prototypes, one Opus 5.5 review of the strand (PostHog#435 and PostHog#436) with three final reads, ending clean, and one GPT-6.1 Sol (high) round on this revision (one finding, fixed) with a clean read of the fix. QA swarm 2026-11-01: round 3, the first two-family panel (Opus 5.5 fresh eyes, GPT-6.1 Sol on nine lenses), round 4 on the fixes (seven lenses), and three final reads ending clean: 23 findings, 21 fixed, 1 rejected (template churn on rendered sites), 1 follow-up. Proof captured.
🤖 Generated with Claude Code