Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
272 commits
Select commit Hold shift + click to select a range
50b5f27
fix(skills): keep migrate-email's chain query uncached on its rerun
Silthus Nov 1, 2026
8f77354
fix(skills): keep cc, bcc, headers, tags and attachments sites on Res…
Silthus Nov 2, 2026
d871b8e
Merge feat/migrate-email-resend into feat/migrate-email-resend-cutover
Silthus Nov 2, 2026
b9d785e
Drop local templates and parked fields from the cutover steps
Silthus Nov 2, 2026
5edeb92
feat(migrate-email): set migratedFrom on the posthog-node client
Silthus Nov 2, 2026
64cf8ef
fix(migrate-email): ask for the PostHog template ID before a template…
Silthus Nov 2, 2026
85f6096
fix(migrate-email): check each PostHog template and its sender before…
Silthus Nov 2, 2026
a759ff4
fix(migrate-email): ask about a repeat import and test a send that na…
Silthus Nov 2, 2026
5c8a34b
fix(migrate-email): ask for the PostHog template ID and sender before…
Silthus Nov 2, 2026
e053dab
Merge feat/migrate-email-resend into feat/migrate-email-resend-cutover
Silthus Nov 2, 2026
efa4933
fix(skills): read Dirent.path when parentPath is missing in the migra…
Silthus Nov 2, 2026
5e40f2a
Merge feat/migrate-email-resend into feat/migrate-email-resend-cutover
Silthus Nov 2, 2026
2c36853
fix(migrate-email): match account.md's template-content rule to Step 3
Silthus Nov 2, 2026
8dcf6e7
fix(migrate-email): keep a site on Resend when its idempotency key do…
Silthus Nov 2, 2026
07dbf3a
fix(migrate-email): name PostHog's idempotency key rule exactly
Silthus Nov 2, 2026
a1376c0
Merge feat/migrate-email-resend into feat/migrate-email-resend-cutover
Silthus Nov 2, 2026
21f683e
fix(migrate-email): end Step 8's email check when the person declines…
Silthus Nov 2, 2026
d157859
docs(migrate-email): say where a send without distinctId lands
Silthus Nov 3, 2026
5dcdd21
docs(migrate-email): test Step 8 on a site that passes distinctId
Silthus Nov 3, 2026
78d36da
feat(skills): move SendGrid sends to PostHog in every SDK language
Silthus Nov 4, 2026
0a95802
fix(migrate-email): close round-1 review findings on the SendGrid layer
Silthus Nov 4, 2026
7d8c4f9
fix(migrate-email): let each provider's mapping decide which language…
Silthus Nov 4, 2026
4c37a14
fix(migrate-email): close round-2 review findings on the SendGrid layer
Silthus Nov 4, 2026
4fc8d41
docs(migrate-email): say why cc, bcc and attachments stay on Resend
Silthus Nov 4, 2026
9664659
docs(migrate-email): name the real reason a field stays on Resend
Silthus Nov 4, 2026
4e5a672
docs(migrate-email): give scheduled sends and topics a reason too
Silthus Nov 4, 2026
b9bf2df
Merge remote-tracking branch 'origin/feat/migrate-email-resend-cutove…
Silthus Nov 4, 2026
29e44dc
docs(migrate-email): name why each kept field stays
Silthus Nov 4, 2026
8d88206
fix(migrate-email): keep a site on Resend when its key length is unbo…
Silthus Nov 4, 2026
3cdf4f2
fix(migrate-email): keep a network error raising where an HTTP-only c…
Silthus Nov 4, 2026
f6e3f17
Merge commit '8d88206613708da974fa19a267f8c10b151292ec' into feat/mig…
Silthus Nov 4, 2026
5e26f02
fix(migrate-email): keep sends that ignored a refusal going, quote Li…
Silthus Nov 4, 2026
2001fe3
fix(migrate-email): let a field that stood for an account setting be …
Silthus Nov 4, 2026
42de36d
fix(migrate-email): name untracked sends in the added-field exception
Silthus Nov 4, 2026
5863a99
fix(migrate-email): quote Resend fallbacks with single quotes only
Silthus Nov 4, 2026
c2fd123
fix(migrate-email): keep network_error on the Node error a sendgrid r…
Silthus Nov 4, 2026
98ab5a8
fix(migrate-email): decide the send shapes real code showed
Silthus Nov 4, 2026
8fce47a
fix(migrate-email): cover SendGrid's callback send and snake_case keys
Silthus Nov 4, 2026
8ac97d7
Merge commit '5863a997198f52c9f98df1209e7b49383e404ce9' into feat/mig…
Silthus Nov 4, 2026
7f9b671
fix(migrate-email): keep uncalled send functions as sites, name more …
Silthus Nov 4, 2026
fa45f3e
fix(migrate-email): leave a catch of every error as it is
Silthus Nov 4, 2026
f504ac6
fix(migrate-email): read config values once, and follow template IDs …
Silthus Nov 4, 2026
c7b8c10
fix(migrate-email): keep SendGrid template sites when tracking is off…
Silthus Nov 4, 2026
9b390bd
fix(migrate-email): keep SendGrid sites with mixed or unknown account…
Silthus Nov 4, 2026
e1967c7
fix(migrate-email): a rate-limit branch skips PostHog's quota_limited
Silthus Nov 4, 2026
32deb28
fix(migrate-email): keep PostHog's code next to the status in SendGri…
Silthus Nov 4, 2026
ee0a324
fix(migrate-email): one PostHog catch for several provider handlers, …
Silthus Nov 4, 2026
6440c0f
fix(migrate-email): mirror a provider's suppression removal only when…
Silthus Nov 4, 2026
c48ef83
fix(migrate-email): a mirrored suppression removal also clears entrie…
Silthus Nov 4, 2026
9baece4
fix(migrate-email): a template value counts as its declared type, and…
Silthus Nov 4, 2026
127bc07
fix(migrate-email): a template value from unchecked input keeps the s…
Silthus Nov 4, 2026
eed16c6
fix(migrate-email): record the PostHog key as set only after a check …
Silthus Nov 4, 2026
2d91936
fix(migrate-email): accept spaces around = in the key check
Silthus Nov 4, 2026
f37c460
Merge remote-tracking branch 'origin/feat/migrate-email-resend-cutove…
Silthus Nov 4, 2026
074130e
fix(migrate-email): check the last assignment of the PostHog key
Silthus Nov 4, 2026
9a0f03c
Merge remote-tracking branch 'origin/feat/migrate-email-resend-cutove…
Silthus Nov 4, 2026
5c92245
docs(migrate-email): ask for the transactional_email:write scope
Silthus Nov 5, 2026
f5080a7
Merge remote-tracking branch 'origin/feat/migrate-email-resend' into …
Silthus Nov 5, 2026
eae6399
Merge #451 at f5080a7 (key scope transactional_email:write) into feat…
Silthus Nov 5, 2026
254dbb9
fix(migrate-email): name the transactional email scope in the preflight
Silthus Nov 5, 2026
464644f
Merge branch 'feat/migrate-email-resend' into feat/migrate-email-rese…
Silthus Nov 5, 2026
4b0f257
feat(migrate-email): move Resend sends in every PostHog SDK's language
Silthus Nov 5, 2026
c4ed38c
fix(migrate-email): keep the app booting and check the whole project …
Silthus Nov 5, 2026
a658c75
fix(migrate-email): use the one Rust client the app's features build
Silthus Nov 5, 2026
cffc009
feat(migrate-email): carry posthog-sdks.md's rules into the rewrite f…
Silthus Nov 5, 2026
6ab1cc2
Merge the migration lane's per-SDK rules into feat/migrate-email-all-…
Silthus Nov 5, 2026
f6b6af8
fix(migrate-email): check the merged SDK rules against the SDK heads
Silthus Nov 5, 2026
ef2c2e9
fix(migrate-email): give Elixir email its own instance when the app u…
Silthus Nov 5, 2026
fbd1e83
fix(migrate-email): never pass nil for Elixir's host or token
Silthus Nov 5, 2026
c53e0ad
Merge #472 at fbd1e83 (a rewrite file per SDK) into feat/migrate-emai…
Silthus Nov 5, 2026
78d56ce
fix(migrate-email): fall back past a nil :posthog host in Elixir
Silthus Nov 5, 2026
317a9cb
Merge #472 at 78d56ce (Elixir falls back past a nil host) into feat/m…
Silthus Nov 5, 2026
bb711c5
fix(migrate-email): list the error names once and state reply-to per SDK
Silthus Nov 5, 2026
0f6d4f0
Merge #472 at bb711c5 (error names listed once, reply-to per SDK) int…
Silthus Nov 5, 2026
5398fd9
fix(migrate-email): give email its own client behind a reverse proxy
Silthus Nov 5, 2026
78a308c
Merge #472 at 5398fd9 (own email client behind a reverse proxy) into …
Silthus Nov 5, 2026
6924d48
test(migrate-email): pin keeping the secret key off the proxied client
Silthus Nov 5, 2026
a8d0023
Merge #472 at 6924d48 (proxy test also checks the app client keeps no…
Silthus Nov 5, 2026
4b15d2e
feat(migrate-email): bring stored templates into the code
Silthus Nov 5, 2026
735bd63
feat(migrate-email): fill SendGrid substitutions in the code
Silthus Nov 5, 2026
2ebcce0
feat(migrate-email): bring Resend templates into the code
Silthus Nov 5, 2026
4ce6c2d
docs(migrate-email): record engagement events only when engagement ca…
Silthus Nov 9, 2026
62d1116
fix(migrate-email): keep SendGrid sites with opens untracked instead …
Silthus Nov 9, 2026
7761f99
fix(migrate-email): list no PostHog click event for SendGrid clicks, …
Silthus Nov 9, 2026
0192a3d
fix(migrate-email): keep an app's PostHog key, and a Resend HTTP call…
Silthus Nov 12, 2026
d2e2848
Merge #451 at 0192a3d (keep an app's PostHog key, and an HTTP call's …
Silthus Nov 12, 2026
5c996de
fix(migrate-email): keep email off a disabled client, and pass numeri…
Silthus Nov 12, 2026
20036d6
Merge #451 at 5c996de (no email on a disabled client, numeric IDs as …
Silthus Nov 12, 2026
dddd616
fix(migrate-email): keep undeadlined HTTP sites on Resend and keep im…
Silthus Nov 12, 2026
8234d71
fix(migrate-email): record the send's own sender, replace markers in …
Silthus Nov 12, 2026
e9953c0
fix(migrate-email): map Go's other Resend error checks and keep Outlo…
Silthus Nov 12, 2026
ecd0366
fix(migrate-email): render variables in Go's Outlook blocks and scope…
Silthus Nov 12, 2026
7e5a468
fix(migrate-email): keep encoded URL values as they are in Go templates
Silthus Nov 12, 2026
e22fa6c
Merge branch 'main' into feat/migrate-email-resend
Silthus Nov 12, 2026
7afab6e
Merge branch 'feat/migrate-email-resend-cutover' into feat/migrate-em…
Silthus Nov 12, 2026
ae88e6e
Merge branch 'feat/migrate-email-resend' into feat/migrate-email-rese…
Silthus Nov 12, 2026
83f0f4a
Merge remote-tracking branch 'origin/feat/migrate-email-all-sdks' int…
Silthus Nov 12, 2026
6b41f33
feat(migrate-email): move the shared step changes from the SendGrid l…
Silthus Nov 12, 2026
7889e1f
Merge the core skill into feat/migrate-email-sendgrid, keeping only t…
Silthus Nov 12, 2026
a366b71
fix(migrate-email): list Resend webhook handlers, keep ignored argume…
Silthus Nov 12, 2026
f279e43
fix(migrate-email): keep provider content inert in code literals, and…
Silthus Nov 12, 2026
8d1f697
Merge remote-tracking branch 'origin/feat/migrate-email-all-sdks' int…
Silthus Nov 12, 2026
e03b4a8
fix(migrate-email): keep the site's subject when a SendGrid version h…
Silthus Nov 12, 2026
487262b
fix(skills): close the migrate-email preparation gaps the QA swarm found
Silthus Nov 12, 2026
413dcd7
fix(migrate-email): SendGrid substitution tags, account mail settings…
Silthus Nov 12, 2026
00b9aa3
fix(skills): tighten the migrate-email key check and template rules
Silthus Nov 12, 2026
4211ef3
fix(migrate-email): SendGrid client wrappers and deadlines, account G…
Silthus Nov 12, 2026
bde5bb6
Merge branch 'feat/migrate-email-resend' into feat/migrate-email-rese…
Silthus Nov 12, 2026
708f7f1
fix(migrate-email): keep each site's own HTTP deadline
Silthus Nov 12, 2026
56158d1
fix(migrate-email): keep marketing sends on the provider
Silthus Nov 12, 2026
8de2222
fix(migrate-email): a bare name inside a SendGrid each reads only the…
Silthus Nov 12, 2026
04e7c84
fix(migrate-email): read the provider's report notes without the impo…
Silthus Nov 12, 2026
7c388d1
Merge branch 'feat/migrate-email-resend' into feat/migrate-email-rese…
Silthus Nov 12, 2026
02f46ca
fix(migrate-email): escape SendGrid subject values, one client per de…
Silthus Nov 12, 2026
922ae71
fix(migrate-email): Handlebars subject escape, SendGrid secret rules,…
Silthus Nov 12, 2026
0186ee2
fix(migrate-email): answer the round 1 review of the rewrite and veri…
Silthus Nov 12, 2026
30f3873
fix(migrate-email): match baseline typecheck errors without their line
Silthus Nov 12, 2026
dc18f5d
fix(migrate-email): count each baseline typecheck error
Silthus Nov 12, 2026
c33bf45
Merge branch 'feat/migrate-email-resend-cutover' into feat/migrate-em…
Silthus Nov 12, 2026
a2994e9
fix(migrate-email): keep a named recipient on Resend
Silthus Nov 12, 2026
bde40bc
Merge remote-tracking branch 'origin/feat/migrate-email-resend' into …
Silthus Nov 12, 2026
e4221d8
Merge remote-tracking branch 'origin/feat/migrate-email-resend-cutove…
Silthus Nov 12, 2026
82d6ebf
fix(migrate-email): settle deadlines, attribute quoting, and suppress…
Silthus Nov 12, 2026
497d017
fix(migrate-email): keep a site on Resend when it needs a suppressed …
Silthus Nov 12, 2026
69edcdd
fix(migrate-email): keep a Resend SDK send with an abortable signal o…
Silthus Nov 12, 2026
22df812
Merge remote-tracking branch 'origin/feat/migrate-email-resend-cutove…
Silthus Nov 12, 2026
e36fd64
fix(migrate-email): own email clients, long deadlines, clicks and tem…
Silthus Nov 12, 2026
08c9d15
test(migrate-email): drop the tests that match the skill's own wording
Silthus Nov 12, 2026
bb14da9
fix(migrate-email): blank .NET host, long .NET deadlines, shared temp…
Silthus Nov 12, 2026
eeeb620
fix(migrate-email): keep a Go site whose template fills CSS from a value
Silthus Nov 12, 2026
64bb2a2
fix(migrate-email): count brought-in templates as code content, stop …
Silthus Nov 12, 2026
8ead8e1
fix(migrate-email): keep enable_local_evaluation off the Python async…
Silthus Nov 12, 2026
959a01b
chore: merge feat/migrate-email-all-sdks into feat/migrate-email-send…
Silthus Nov 12, 2026
5786004
fix(migrate-email): converge SendGrid with the finished core
Silthus Nov 12, 2026
0de8dee
fix(migrate-email): keep SendGrid's untracked links out of PostHog's …
Silthus Nov 12, 2026
cfc202d
fix(migrate-email): name the outcome of SendGrid's untracked clicks
Silthus Nov 12, 2026
0158aef
fix(migrate-email): keep sites that branch on one kind of network fai…
Silthus Nov 12, 2026
f6978e7
fix(migrate-email): keep separate connect limits, require ext-curl fo…
Silthus Nov 12, 2026
ad23a92
fix(migrate-email): keep a Go request's transport limits
Silthus Nov 12, 2026
7ccc0ee
fix(migrate-email): keep a Python whole-request limit on the provider
Silthus Nov 12, 2026
64c26bd
fix(migrate-email): carry SendGrid's click notes through the plan
Silthus Nov 12, 2026
6f12513
chore: merge feat/migrate-email-all-sdks into feat/migrate-email-send…
Silthus Nov 12, 2026
558187b
fix(migrate-email): defer SendGrid's network and connect limits to St…
Silthus Nov 12, 2026
4075cdd
fix(migrate-email): keep SendGrid's Ruby http_options timeouts under …
Silthus Nov 12, 2026
e4d0a4b
fix(migrate-email): move SendGrid Ruby sites whose timeouts match pos…
Silthus Nov 12, 2026
3aac186
docs(migrate-email): name the outcome of SendGrid's Ruby split limits
Silthus Nov 12, 2026
eef1481
fix(migrate-email): settle untracked clicks, timeout kinds, and the m…
Silthus Nov 12, 2026
efb8451
fix(migrate-email): read Faraday's timeout by its adapter
Silthus Nov 12, 2026
f658354
fix(migrate-email): keep a site whose handler needs an event PostHog …
Silthus Nov 12, 2026
22cfedc
Merge feat/migrate-email-all-sdks (f658354) into feat/migrate-email-s…
Silthus Nov 12, 2026
106bf25
docs(migrate-email): point SendGrid clicks and timeouts at the core r…
Silthus Nov 12, 2026
a66e15b
fix(migrate-email): upgrade mocked send tests to the HTTP boundary
Silthus Nov 12, 2026
b0f6e45
docs(migrate-email): keep SendGrid's sensitive-template plan note and…
Silthus Nov 12, 2026
18ab351
fix(migrate-email): fake fetch instead of keeping a send mock
Silthus Nov 12, 2026
ced460c
Merge commit '18ab3510a0b5cb457fab6a6b3d6079d8078bbc2d' into HEAD
Silthus Nov 12, 2026
e14c1fc
fix(migrate-email): keep a Ruby site with a partial Net::HTTP timeout
Silthus Nov 12, 2026
a943460
fix(migrate-email): note a must-be-sensitive template in its plan row
Silthus Nov 12, 2026
45c5716
fix(migrate-email): keep network failures off Resend's 503 branch
Silthus Nov 12, 2026
d51979c
fix(migrate-email): allow a loopback app host in Go and .NET tests
Silthus Nov 12, 2026
17c6ca1
feat(migrate-email): ask before adding an open pixel to untracked email
Silthus Nov 12, 2026
3b34f45
Merge feat/migrate-email-all-sdks (17c6ca1) into feat/migrate-email-s…
Silthus Nov 12, 2026
023fc06
docs(migrate-email): point SendGrid opens and webhook events at the c…
Silthus Nov 12, 2026
3904508
docs(migrate-email): scope SendGrid's webhook stay to events a moved …
Silthus Nov 12, 2026
cff58f5
fix(migrate-email): route a network failure the way Resend's code did
Silthus Nov 12, 2026
96e10a2
Merge commit 'cff58f516b33b2fe5eb3c0a5d2704f874f7c3d85' into HEAD
Silthus Nov 12, 2026
6f7abcb
fix(migrate-email): test with a standard-library server, never a mock…
Silthus Nov 12, 2026
8776c81
fix(migrate-email): ask about a test dependency only where one is needed
Silthus Nov 12, 2026
63c3dfe
fix(migrate-email): ask before adding any test tool, in every language
Silthus Nov 12, 2026
21e1d27
feat(migrate-email): re-import the suppression list weekly while site…
Silthus Nov 12, 2026
a65bb90
fix(migrate-email): check the project before a suppression re-import
Silthus Nov 12, 2026
b28e13b
fix(migrate-email): write the re-import's project check out in Step 5
Silthus Nov 12, 2026
b2dfe53
fix(migrate-email): confirm the production project before a re-import
Silthus Nov 12, 2026
731b092
fix(migrate-email): read the project token with project-get
Silthus Nov 12, 2026
8929f93
fix(migrate-email): switch the MCP back after a re-import
Silthus Nov 12, 2026
e5735c8
fix(migrate-email): name the production project in a by-hand re-import
Silthus Nov 12, 2026
8b9757e
fix(migrate-email): let a re-import without the tool say so
Silthus Nov 12, 2026
349a407
fix(migrate-email): let a re-import say why it got no list
Silthus Nov 12, 2026
2df1f26
fix(migrate-email): read the project token with project-get
Silthus Nov 12, 2026
090ac92
Merge #451 (feat/migrate-email-resend-cutover) into feat/migrate-emai…
Silthus Nov 12, 2026
da31113
Merge feat/migrate-email-all-sdks (090ac92) into feat/migrate-email-s…
Silthus Nov 12, 2026
069a749
docs(migrate-email): point SendGrid suppression writes at the weekly …
Silthus Nov 12, 2026
d8dd2d4
fix(migrate-email): ask for re-imports only when the provider keeps a…
Silthus Nov 12, 2026
94d44d3
fix(migrate-email): record a provider with no list before the tool check
Silthus Nov 13, 2026
5553a6b
docs(migrate-email): keep SendGrid templates whose subject values Pos…
Silthus Nov 13, 2026
2b8d0d6
fix(migrate-email): end a re-import early when the provider keeps no …
Silthus Nov 13, 2026
d4b7a13
docs(migrate-email): keep the throw of SendGrid's .NET retry handler …
Silthus Nov 13, 2026
5e0607f
fix(migrate-email): keep sites a Resend webhook handler acts on, and …
Silthus Nov 13, 2026
fcef5e1
docs(migrate-email): keep .NET sites behind SendGrid's retry handler …
Silthus Nov 13, 2026
eeed7cb
fix(migrate-email): check every error in a rewritten line against its…
Silthus Nov 13, 2026
5f5debd
fix(migrate-email): check for a provider without a suppression list b…
Silthus Nov 13, 2026
cb26a5f
Merge #451 (feat/migrate-email-resend-cutover) into feat/migrate-emai…
Silthus Nov 13, 2026
bd24f05
docs(migrate-email): key the .NET retry stay on the handler's retries…
Silthus Nov 13, 2026
77b680b
fix(migrate-email): give Step 8 one rule for which typecheck errors a…
Silthus Nov 13, 2026
e7a7f3f
Merge #451 (feat/migrate-email-resend-cutover) into feat/migrate-emai…
Silthus Nov 13, 2026
a31db3b
docs(migrate-email): keep SendGrid retry failures and untracked plain…
Silthus Nov 13, 2026
67b925a
docs(migrate-email): read a subuser's SendGrid account, escape plain …
Silthus Nov 13, 2026
e983e39
docs(migrate-email): key SendGrid account reads on the sending key, h…
Silthus Nov 13, 2026
1d85155
fix(migrate-email): tie the kept Resend error names to posthog-node, …
Silthus Nov 13, 2026
75fa8f8
docs(migrate-email): ask the SendGrid import filters in every run and…
Silthus Nov 13, 2026
013a98e
Merge #450 (feat/migrate-email-resend) into feat/migrate-email-resend…
Silthus Nov 13, 2026
19b1b90
fix(migrate-email): mark secret emails sensitive, check template keys…
Silthus Nov 13, 2026
c46ba49
docs(migrate-email): ask the SendGrid import filters with the report …
Silthus Nov 13, 2026
fdea2d3
fix(migrate-email): send sensitive only with rendered content, and on…
Silthus Nov 13, 2026
8c03676
docs(migrate-email): ask the SendGrid import filters before taking en…
Silthus Nov 13, 2026
2fe8430
fix(migrate-email): check the app's Node version and count removed er…
Silthus Nov 13, 2026
6c45e23
Merge remote-tracking branch 'origin/feat/migrate-email-resend-cutove…
Silthus Nov 13, 2026
4087e6c
fix(migrate-email): carry the key, sensitive and Node checks; count r…
Silthus Nov 13, 2026
d35d356
docs(migrate-email): apply the SendGrid import filters to entries add…
Silthus Nov 13, 2026
26e5396
docs(migrate-email): give the by-hand SendGrid filters as rules in th…
Silthus Nov 13, 2026
808f61d
fix(migrate-email): name each SDK's sensitive flag; list the moved keys
Silthus Nov 13, 2026
3bec683
docs(migrate-email): write the SendGrid filter rules in every report,…
Silthus Nov 13, 2026
5b55af4
docs(migrate-email): ask for the SendGrid Allow List's entries right …
Silthus Nov 13, 2026
fd58f93
fix(migrate-email): move the template keys to after cutover; edge reason
Silthus Nov 13, 2026
1fc3ae4
docs(migrate-email): name missing Allow List entries in a SendGrid re…
Silthus Nov 13, 2026
f1ba031
fix(migrate-email): scope the runtime check to where each site runs
Silthus Nov 13, 2026
a0b01bc
fix(migrate-email): check every server that shares the SDK's manifest
Silthus Nov 13, 2026
8f51a57
fix(migrate-email): keep every site on a manifest an unsupported serv…
Silthus Nov 13, 2026
6b0083e
fix(migrate-email): count workspace and root lockfile installs in the…
Silthus Nov 13, 2026
ef71e1f
fix(migrate-email): keep a site whose HTTP client retries a request w…
Silthus Nov 13, 2026
6fdfdf7
fix(migrate-email): count only a retry setup that covers the POST
Silthus Nov 13, 2026
6d4801f
fix(migrate-email): name the retry setups as examples, not a full list
Silthus Nov 13, 2026
caff403
fix(migrate-email): read the retry setup's options before counting it
Silthus Nov 13, 2026
7aad014
fix(migrate-email): count a requests adapter whose max_retries is a n…
Silthus Nov 13, 2026
a090b5b
chore: merge main after #450 landed
Silthus Nov 13, 2026
66dc2b2
Merge branch 'feat/migrate-email-resend-cutover' into feat/migrate-em…
Silthus Nov 13, 2026
6d3d8b0
fix(migrate-email): keep a site that reads Resend's response headers
Silthus Nov 13, 2026
610e703
fix(migrate-email): name each SDK's retry delay in its rewrite file
Silthus Nov 13, 2026
d186900
fix(migrate-email): keep a send that turns Resend's plain-text part off
Silthus Nov 13, 2026
088f35b
fix(migrate-email): cover react and run-time empty text, word the tem…
Silthus Nov 13, 2026
3b204b7
fix(migrate-email): check the text-part rule in Step 3, count Go's em…
Silthus Nov 13, 2026
f3bb668
fix(migrate-email): ask whether a copied Resend template turns its pl…
Silthus Nov 13, 2026
355df95
fix(migrate-email): offer the full-access key for the plain-text ques…
Silthus Nov 13, 2026
74040e5
fix(migrate-email): note every template's plain text, offer the key o…
Silthus Nov 13, 2026
b99cea5
fix(migrate-email): sync suppression calls on the reasons PostHog stores
Silthus Nov 13, 2026
9eb4180
fix(migrate-email): report suppression-list calls instead of syncing …
Silthus Nov 13, 2026
1bcc53c
fix(migrate-email): say only hard bounces block a moved send
Silthus Nov 13, 2026
daf563e
fix(migrate-email): give the add item a check the app can make
Silthus Nov 13, 2026
d00e808
fix(migrate-email): ask in Step 3 which sites rely on a suppression add
Silthus Nov 13, 2026
2fe5090
fix(migrate-email): ask about hand-added suppressions too
Silthus Nov 13, 2026
b006c9b
fix(migrate-email): keep the sites when nobody can say who relies on …
Silthus Nov 13, 2026
cd9e38a
fix(migrate-email): ask about the suppression list before the templat…
Silthus Nov 13, 2026
4db6763
fix(migrate-email): list the suppression note in the plan file rules
Silthus Nov 13, 2026
0a6ae00
fix(migrate-email): keep Rust sites on runtimes other than Tokio
Silthus Nov 13, 2026
f98eb3b
fix(migrate-email): require Tokio's timers for the Rust async client
Silthus Nov 13, 2026
418cc5a
chore: merge main after #451 landed
Silthus Nov 13, 2026
1b2752a
Merge remote-tracking branch 'origin/feat/migrate-email-all-sdks' int…
Silthus Nov 13, 2026
fb5e78a
chore: merge main after #472 landed
Silthus Nov 13, 2026
8b33b8e
fix(migrate-email): follow #472's suppression steps for SendGrid
Silthus Nov 13, 2026
3bba58e
refactor(migrate-email): slim sendgrid references to what the core do…
Silthus Nov 14, 2026
225c098
refactor(migrate-email): slim sendgrid, restore the retry stays a cut…
Silthus Nov 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ longer exist — only some keep an alias.
|---|---|
| `wizard migrate statsig` | `migrate-statsig` |
| `wizard migrate resend` | `migrate-email-resend` |
| `wizard migrate sendgrid` | `migrate-email-sendgrid` |

**Commands vs. skills:** those audit subcommands **are** skills, promoted to
commands via `cli: role: command`. A `role: skill` skill is reachable only via
Expand Down
3 changes: 3 additions & 0 deletions context/skills/migrate-email/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ variants:
- id: resend
display_name: Resend
tags: [resend]
- id: sendgrid
display_name: SendGrid
tags: [sendgrid]
104 changes: 104 additions & 0 deletions context/skills/migrate-email/references/sendgrid/account.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# SendGrid account

What the migration needs from the person's SendGrid account. The agent never changes the account: it reads what the person exports or allows.

**EU subusers.** An app on an EU subuser sends through `api.eu.sendgrid.com`, or sets the client's data residency to `eu`, as `sdk-reference.md` says. For such an app, use `api.eu.sendgrid.com` in every request of this file instead of `api.sendgrid.com`.

**Subusers.** A send uses the suppression lists, templates, and settings of the account its API key belongs to: the parent account or one of its subusers. SendGrid's `on-behalf-of` header, which a client's subuser option such as `setImpersonateSubuser` in Node.js or `GetRequestSubuser` in Go sets, doesn't work with the Mail Send API, so it never sends for another account. Ask the person which account the app's sending key belongs to, make the read-only key below in that account, and switch to that account in SendGrid for every question and export of this file. When sites send with keys of different accounts, or a site picks its key at run time, such as per tenant, ask the person which account's lists Step 5 imports: the sites that send only with that account's key move, and every other site stays, with the reason `sends for another SendGrid account`. When the person can't say which account a key belongs to, its sites stay, with the reason `SendGrid account not checked`.

## DNS records

A domain authenticated at SendGrid carries these records. Keep all of them until the cutover is done. The SendGrid dashboard shows the exact names under Settings, then Sender Authentication.

| Record | Name | What it does |
|---|---|---|
| `CNAME` | `em<digits>`, such as `em1234` | Return path for SendGrid's bounces, and its SPF |
| `CNAME` (DKIM) | `s1._domainkey`, `s2._domainkey` | Signs SendGrid's mail |
| `CNAME` | `url<digits>` and a second label of digits | Link branding, only when it is set up |
| `TXT` (DMARC) | `_dmarc` | Optional, shared by every provider on the domain |

- A domain set up without automated security has an `MX` and an SPF `TXT` record on `em<digits>`, and a DKIM `TXT` record at `m1._domainkey`, in place of the CNAMEs.
- PostHog's sender uses its own label for its MAIL FROM, `feedback` by default. Never give PostHog a label SendGrid uses.

## Read-only key

The app's own SendGrid key is usually a key with the Mail Send permission only, and it can't read templates or suppressions. When the person chooses the API over the dashboard, ask them to create a restricted key in SendGrid (Settings, then API Keys) with read access to Template Engine and Suppressions only, store it in `SENDGRID_READ_API_KEY` in the git-ignored env file, and write it into the plan as Step 3's plan layout says. Its last use is the suppression import in Step 5. At the end of Step 5, on every way out of it, ask the person to delete the key in SendGrid and remove its variable from the env file, and note in the plan whether they did. The report reminds them when they didn't. Never print the key, and never write it into another file.

The steps call this key the full-access provider key.

## Tracking

A send follows the account's open and click tracking for each of the two that its `trackingSettings` doesn't set. What PostHog does with opens and clicks is in `mapping.md` under "Open and click tracking". Ask the person once whether open tracking and click tracking are on in SendGrid (Settings, then Tracking), and whether click tracking also covers plain-text emails, and note the answers in the plan.

A site's emails had no open pixel when its `trackingSettings` sets `openTracking` with `enable: false`, or sets no `openTracking` while the account's open tracking is off. When it sets none and the person can't say whether open tracking is on, nobody can tell whether they had one. Step 3 says under "Opens the provider didn't track" what either means.

SendGrid didn't track a site's clicks when its `trackingSettings` sets `clickTracking` with `enable: false`, or sets no `clickTracking` while the account's click tracking is off. It didn't track the links in a plain-text part either, unless `clickTracking` sets `enableText: true`, or sets no `clickTracking` while the account's click tracking covers plain-text emails: a site whose plain-text part has a link SendGrid didn't track counts as one whose clicks SendGrid didn't track. When it sets no `clickTracking` and the person can't say whether click tracking is on, or whether it covers plain text for a site whose plain-text part has a link, nobody knows whether SendGrid tracked them. Step 3 says under "A template in PostHog" what either means for a site that moves to a template made in PostHog.

## Mail settings

More account settings change every send that doesn't set them, and no send field of PostHog's does the same. In the same question, ask the person whether each one is on in SendGrid:

- **Subscription tracking** (Settings, then Tracking): SendGrid added an unsubscribe link to each email, or filled the tag that stands for it. When it is on, every site whose `trackingSettings` doesn't set `subscriptionTracking` stays, and the reason is `account subscription tracking on`.
- **Google Analytics** (Settings, then Tracking): SendGrid added `utm_` parameters to every link. When it is on, every site whose `trackingSettings` doesn't set `ganalytics` stays, and the reason is `account google analytics on`.
- **Legacy Email Template** (Settings, then Mail Settings, when the account shows it): SendGrid wrapped every email in that template. When it is on, every site stays, and the reason is `account legacy email template on`.
- **Footer** (Settings, then Mail Settings): SendGrid appended the footer's HTML to the HTML part and its text to the text part. When it is on, ask the person for the footer's HTML and text. Each site that moves in the code form appends them to its `html` and `text`, as SendGrid did, and the plan notes it. A site that moves to a template made in PostHog sends no `html` or `text`: ask the person to put the footer at the end of that template too, and note it in the plan. When the person can't give the footer, every site stays, and the reason is `account footer not copied`.

When the person can't say whether one of these is on, the sites it would change stay, and the reason is `account mail settings not checked`. Note the answers in the plan.

## Template content

A site whose template moves needs the content of its active version: Step 3 checks it, and Step 7 brings it into the code, or the person makes a template in PostHog from it when they ask for one. The API returns the content.

**Copy from the dashboard.** In SendGrid: Email API, then Dynamic Templates, then the template, then its active version. Ask the person for the template's id, the version's subject, its HTML from the code editor, and its plain text when it has one.

**Read from the API.** `GET https://api.sendgrid.com/v3/templates/{template_id}` with `Authorization: Bearer $SENDGRID_READ_API_KEY`. The answer holds `id`, `name`, `generation` (`dynamic` or `legacy`), and `versions`, a list of `{ id, active, name, subject, html_content, plain_content }`. Take the version whose `active` is `1`.

Read the person's copies where they saved them. An API answer goes only into the temporary file Step 3 writes, which is deleted as `template-to-code.md` says. Never write the content into the plan or the report.

## Suppression list

SendGrid keeps several suppression lists. It skips an address on any of them on every send.
Comment thread
Silthus marked this conversation as resolved.
Resolved

**For Step 3's question.** SendGrid's suppression list, in Step 3's question about the sites that rely on it, is Bounces, Spam Reports, Invalid Emails, and Global Unsubscribes together. A person adds to them by hand under Suppressions, and the app with the calls `mapping.md` names under "Calls that change the suppression list", such as an unsubscribe handler that adds a global unsubscribe. A global unsubscribe stopped every send to the address at SendGrid, transactional ones too, unless the send bypassed it, and a moved send reaches it, as "Leave out the other lists" says: name Global Unsubscribes in the question. A site that sets `asm` or `mailSettings` already stays, as `mapping.md` says, so the question leaves out group unsubscribes and the bypass settings.

Whenever the person adds entries to PostHog's list by hand, in the import or a re-import, such as when the import tool is missing, ask only whether the Address Allow List and Bounce Purge are on, as "For the report" says, and for Bounce Purge's two day counts. Tell them the rule of each one that is on: leave out the addresses and domains on the Allow List, which they read in SendGrid themselves, and the bounces older than the day count for their kind, as "Leave out what SendGrid delivered to anyway" says. A setting they can't say is on gets no rule.

**Export from the dashboard (preferred).** In SendGrid: Suppressions, then each of Bounces, Spam Reports, and Invalid Emails, then **Export CSV**. Ask the person where they saved each file. Read the header row to find the address column.

**Read from the API.** With `Authorization: Bearer $SENDGRID_READ_API_KEY`:

- `GET https://api.sendgrid.com/v3/suppression/bounces`
- `GET https://api.sendgrid.com/v3/suppression/spam_reports`
- `GET https://api.sendgrid.com/v3/suppression/invalid_emails`
Comment thread
Silthus marked this conversation as resolved.
Resolved

Each answers a list of `{ created, email, reason, status }`. Page with `limit=500` and `offset`, adding 500 each time, until a page has fewer than 500 entries. Wait and retry on `429`.

**Map each entry.**

| SendGrid list | PostHog `reason` |
|---|---|
| Bounces | `bounce` |
| Invalid Emails | `bounce` |
| Spam Reports | `complaint` |

**Leave out what SendGrid delivered to anyway.** Before you take the entries, ask the person what "For the report" asks about the Address Allow List and Bounce Purge, unless they answered it in this run, and, when the Allow List is on, ask for its entries. Apply each one on its own:

- **Address Allow List** on: leave out every entry of the lists whose address or domain is on it.
- **Bounce Purge** on: leave out each bounce older than its number of days from its `created` time. A `status` that starts with `5` is a hard bounce, and one that starts with `4` is a soft one.

When you take the entries yourself, a setting the person can't say is on, or whose entries or day counts they can't give, leaves out nothing, and the other one still applies. In a re-import, tell the person each setting's rule and how many entries it left out, that it was not checked, or, for an Allow List whose entries they couldn't give, `Address Allow List entries not given`.

**Leave out the other lists.**

- **Blocks** are temporary refusals by a receiving server, not a sign that the address is bad.
- **Global unsubscribes and group unsubscribes** are marketing preferences, and PostHog's opt-outs apply to marketing messages only. At SendGrid a global unsubscribe also stopped transactional mail, unless the send bypassed it. Once the sends move, these addresses get transactional email again. The report gives their count, as "For the report" says.

## For the report

The count of global unsubscribes and of group unsubscribes, which the import leaves out. Count the rows of the person's exports (Suppressions, then each of Global Unsubscribes and Group Unsubscribes, then **Export CSV**), or the entries of `GET https://api.sendgrid.com/v3/suppression/unsubscribes`, paged as above, and of `GET https://api.sendgrid.com/v3/asm/suppressions`, with the read-only key. When the person has neither, write `unsubscribes not counted`.

Ask the person once, whether or not the import runs, whether two settings are on in SendGrid (Settings, then Mail Settings). With the **Address Allow List**, SendGrid delivered to an address on it, or at a domain on it, whatever its lists said. With **Bounce Purge**, SendGrid deleted a bounce once it was older than the days set for hard bounces or for soft bounces. When Bounce Purge is on, ask for its two day counts. Then write:

- Each setting that is on and its rule, never an address or an Allow List entry: the Allow List leaves out the addresses and domains on it, and Bounce Purge the bounces older than the day count for their kind, where a `status` that starts with `5` is a hard bounce and one that starts with `4` is a soft one. Whoever adds entries to PostHog's list by hand, before cutover or in a re-import, leaves out the same entries. After the import, add under the phase how many entries each one left out.
- When Bounce Purge is on: bounces that SendGrid would have purged later stay suppressed in PostHog until someone removes them.
- Each setting the person can't say is on, and Bounce Purge without its day counts: `<setting> not checked`, because PostHog may suppress addresses that SendGrid still delivered to. After an import in which the person couldn't give the Allow List's entries: `Address Allow List entries not given`, for the same reason.
Loading
Loading