Repository navigation
Conversation
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>
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.
Problem
migrate-email-resend(PostHog#435) 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 PostHog#435, which is stacked on PostHog#433. Merge them first. This PR's own commits start after
0a48cff:0a48cff...4499957.It also waits for the
posthog-noderelease withposthog.emails(PostHog/posthog-js#5312): the skill installs a version that has it.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: every moved send carries
migratedFrom: 'resend', which send attribution (PostHog/posthog#120628) records asmigrated_from. 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 withsecretKeyandmigratedFrom, set up as the send skill says (its client bullets are now the shared partialemail-node-client). 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 anything beyond plain variables and fallbacks, stays on Resend.@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.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 and a product event on the same distinct ID, 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. Full suite: 30 files, 227 tests pass. The builtomnibus-send-transactional-emailis byte-identical before and after the partial (diff -r).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. The migrated template app's
sendWelcomeagainst a local stub of the PostHog API:pnpm exec vitest run scripts/lib/tests/migrate-email-skill.test.js→ 5 passed.migrated_fromproperty on the send event are unproven. The send API slices are still open PRs.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)sites_rewrittencounts moved sitesInvolvement: no grilling rounds, no prototypes, one Opus 5.5 review of the strand (PostHog#435 and PostHog#436) with three final reads, ending clean. Proof captured.
🤖 Generated with Claude Code (Claude Opus 5.5)