Repository navigation
feat(cdp): key a code send on the sender the caller chose - #127129
Conversation
…il route A code send posts its accepted content to transactional_email/send. The route runs the same reserve, replay, refusal and enqueue steps as a template send. The accepted send binds the idempotency fingerprint, and a miss on a new workflow reads it from the primary. Co-Authored-By: Claude Opus 5.5 <[email protected]>
A keyed retry of a code send replays after the project's default sender changed, and after Django resolved a named address to another sender. The key leaves out a default sender (`from.isDefault`) and keys a named address on the address and name the caller sent, not on the integration Django picked. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…flow Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
🤖 CI report✅ Trunk lane — non-backend lane (
|
| 🔑 Login | [email protected] / 12345678 (demo data) |
| 🧩 Running | this PR's backend on the PostHog :master base (frontend unchanged by this PR) |
| 🔗 Link | stable across rebuilds: a re-push swaps the box underneath, the URL stays |
| 🔒 Access | tailnet only (PostHog VPN) |
| 🛠️ Admin | inspect & debug state in hogland |
| 💤 Idle | sleeps after ~30 min idle (snapshot to S3, zero node cost) and wakes on your next visit in ~30s, behind a brief "waking up" screen |
commit e99c3df · box box-d48c87ffe7be · ready in 2494s (push → usable) · build log · rebuilds on every push, torn down on close
There was a problem hiding this comment.
PostHog Review · Standard · reviewed 64d8940: 1 Should fix.
This is the only automatic review while this pull request is a draft. Comment @posthog review to review new changes sooner. Otherwise, a review of the new changes runs when you mark it ready for review.
See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.
…ode-send Co-Authored-By: Claude Opus 5.5 <[email protected]>
…otency-cdp Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ode-send Co-Authored-By: Claude Opus 5.5 <[email protected]>
…otency-cdp Co-Authored-By: Claude Opus 5.5 <[email protected]>
A send with captureEngagement false still put the recipient's distinct_id in its SES header and pixel/redirect tracking codes. Minting still emits the v2 payload, which has no capture choice, so the SES delivery, bounce, open and click callbacks for that send captured engagement events. Every reader skips engagement capture for a code without a distinct_id, so leaving it out honors the choice on every deployed reader now. The v3 payload and its minting switch stay as they are. The send-time capture and the message assets row keep their distinct_id. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…otency-cdp Co-Authored-By: Claude Opus 5.5 <[email protected]>
The test for a capture-off send covered only the SES header code. Its email had no link, so nothing checked that the pixel and redirect codes leave out the distinct_id too. The dev/test case now checks both codes for each capture choice. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The base now refuses an unknown field on from, and no Django sends the marker, so the retry cannot reach the key. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
😎 Stack merged successfully - details. |
#127122 landed on master, so this layer now targets master. Conflicts in cdp-api.ts keep this layer's retry fingerprint (the sender the caller chose) and take master's concurrent team, workflow and refusal lookups. The test file keeps this layer's retry cases and takes master's folded over-quota case. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
@PostHog review |
|
@Silthus PostHog Review is reviewing this pull request. |
A keyed retry of a code send whose sender has no address replays while its integration is unchanged, and answers 422 after the integration changed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
@PostHog review |
|
@Silthus PostHog Review is reviewing this pull request. |
Keeps both sides' rows in the rejected-key table: this PR's named and address-less sender rows and master's changed cc row (#127985). Co-Authored-By: Claude Opus 5.5 <[email protected]>
A new stamphog review started for this PR — the fresh verdict replaces this approval.
|
@PostHog review |
|
@Silthus PostHog Review is reviewing this pull request. |
There was a problem hiding this comment.
Approved.
The disclosed risky changes have owning-team authorship and STRONG familiarity, with focused regression coverage. All listed reviews are on older commits; no concrete unresolved substantive concern or other refusal ground is present.
- Author wrote 100% of the modified lines and has 5 merged PRs in these paths (familiarity STRONG).
Gate mechanics and policy version
| Gate | Result | |
|---|---|---|
| prerequisites | ✓ | all clear |
| deny-list | ✓ | no deny categories matched |
| size | ✓ | 20L, 1F substantive, 74L/2F incl. docs/generated/snapshots — within ceiling |
| tier | ✓ | T1-agent / T1b-small (74L, 2F, single-area, feat) |
| stamphog 2.7.0 | .stamphog/policy.yml @ e99c3df · reviewed head e99c3df |
|
/trunk merge |
Moved from Silthus#580. Part of the code-first rebuild of the email send stack; replaces parts of #121867 (one send per idempotency key on
send/email/).Problem
send/email/can get422 idempotency_key_reusedinstead of a replay, although the first send already went out.workflows_emailsactions per paying org: callers can retry without double sends. In scope: CDP email sends.Changes
Warning
🔴 Merge risk: high · two-way door: a revert puts the resolved integration back in a named sender's key
transactional_email/sendchange, and onlysend/email/makes them, behindworkflows-email-from-code. The template route is unchanged.422instead of a replay. A revert does the reverse.422. The first send went out from another sender, so a replay would hide that change.fromschema answers400to afrom.isDefault, so the test row for that retry is gone.e88ee89:cdp-api.tskeeps this PR's fingerprint and takes master's concurrent team, workflow and refusal lookups. The over-quota test row is master's folded case.How did you test this code?
Test seams: cdp-api's accepted-send route over HTTP (
cdp-api.serial.test.ts).Test rationale:
returns the first send for a retry of %s and queues nothing newholds every replay, run red first: a named address that resolved to another integration answered422before this change. Therejects the key for a request with %srows pin that another named sender still answers422, and that a sender without an address still keys on its integration.Proofs at
e88ee89, the master merge (saved withfleet proof, and reused atd1d495d, which only adds the two test rows for a sender without an address):cdp-api.serial.test.tspasses 189 of 189 ate88ee89, and its 12 idempotency rows pass atd1d495d, andty, prettier and oxlint pass. The localtscrun fails only in the session-replayai-researchcode, against a stale local build of its native package, so CI's typecheck is the check there.00f9015:message_idand one mail.422.fromreplays while the default sender is unchanged, and answers422after it changed.cdp-api.tsmove only the named-address retry, from fail to pass. The other 9 rows match.e0df364and this head, in both directions, replays default-sender and template keys. A named-sender retry across it answers422and sends no second mail.d1d495d: in both regions the code-send route took no request in the last 7 days, and keys live 24 hours. Master's Django never mints thecode_sendclaim that route needs; feat(messaging): send an email with inline html from code #127123 adds it. So every stored key is a template key, and the422window above affects none.Note
Not covered here: a Django build without the code-send token claim gets
401from #127122's cdp-api. That is #127122's rollout, and theworkflows-email-from-codeflag stays off until both sides deploy.Release status
Docs update
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: Claude Code, Claude Opus 5.5 (claude-opus-5-5)
Skills:
tdd,writing-tests,stacking-prs,modernize-pr,pr-description,writing-pr-descriptionstemplate.hashreplay?not_sendableto its own answernode3, restack of 2026-10-31from, and a named address that resolved elsewhere still needs this keyMore context for AI agents
node3and the code-send follow-up slice of map Map: send code-managed email templates with one PostHog call Silthus/posthog#461, Claude Code.senderTheCallerChoseinnodejs/src/cdp/cdp-api.ts.cdp-api.tsand its test file.🤖 Generated with Claude Code