Repository navigation
Conversation
🤖 CI report✅ Complexity (TypeScript) — cleanCyclomatic complexity above the limit in changed typescript files (10 for production files, 15 for test files). Warn only: worth simplifying when you next touch these functions. ✅ Bundle size — no changeUncompressed size of every built Total: 75.13 MiB · no change No file changed by more than 1000 B. Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report ✅ Eager graph — within budgetHow much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy
🟢 Largest files eagerly shipped from
|
| Size | File |
|---|---|
| 126.8 KiB | ../node_modules/.pnpm/[email protected][email protected]/node_modules/react-dom/cjs/react-dom.production.min.js |
| 24.6 KiB | ../node_modules/.pnpm/[email protected]/node_modules/buffer/index.js |
| 6.3 KiB | ../node_modules/.pnpm/[email protected]/node_modules/react/cjs/react.production.min.js |
| 4.7 KiB | ../node_modules/.pnpm/@[email protected]/node_modules/@jspm/core/nodelibs/browser/process.js |
| 4.0 KiB | ../node_modules/.pnpm/[email protected]/node_modules/scheduler/cjs/scheduler.production.min.js |
| 1.7 KiB | src/index.tsx |
| 1.7 KiB | ../node_modules/.pnpm/[email protected]/node_modules/base64-js/index.js |
| 1.7 KiB | src/lib/utils/bootTiming.ts |
| 1.2 KiB | src/RootErrorBoundary.tsx |
| 949 B | src/scenes/ChunkLoadErrorBoundary.tsx |
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
| Size | File |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/posthog-js/dist/module.mjs |
| 217.5 KiB | ../node_modules/.pnpm/@[email protected][email protected][email protected][email protected]/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 126.8 KiB | ../node_modules/.pnpm/[email protected][email protected]/node_modules/react-dom/cjs/react-dom.production.min.js |
| 102.9 KiB | src/products.tsx |
| 86.6 KiB | src/lib/api.ts |
| 67.9 KiB | src/lib/lemon-ui/icons/icons.tsx |
| 41.9 KiB | ../node_modules/.pnpm/@[email protected][email protected][email protected][email protected]/node_modules/@dnd-kit/core/dist/core.esm.js |
| 36.4 KiB | ../node_modules/.pnpm/[email protected]_patch_hash=139b8d1f1304f9d9da452a9a1244c94ea679dbcb85687d8999563146879fb6f5_react@18.3.1/node_modules/kea/lib/index.cjs.js |
| 27.0 KiB | ../node_modules/.pnpm/[email protected]/node_modules/tailwind-merge/dist/bundle-mjs.mjs |
| 24.6 KiB | ../node_modules/.pnpm/[email protected]/node_modules/buffer/index.js |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/posthog-js/dist/module.mjs |
| 286.4 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.9 KiB | ../packages/quill/packages/quill/dist/index.js |
| 217.5 KiB | ../node_modules/.pnpm/@[email protected][email protected][email protected][email protected]/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.8 KiB | ../node_modules/.pnpm/[email protected]/node_modules/re2js/build/index.esm.js |
| 126.8 KiB | ../node_modules/.pnpm/[email protected][email protected]/node_modules/react-dom/cjs/react-dom.production.min.js |
| 102.9 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@tiptap/core/dist/index.js |
| 92.5 KiB | ../node_modules/.pnpm/[email protected]/node_modules/prosemirror-view/dist/index.js |
| 86.6 KiB | src/lib/api.ts |
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
| Size | File |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/posthog-js/dist/module.mjs |
| 286.4 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.9 KiB | ../packages/quill/packages/quill/dist/index.js |
| 217.5 KiB | ../node_modules/.pnpm/@[email protected][email protected][email protected][email protected]/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.8 KiB | ../node_modules/.pnpm/[email protected]/node_modules/re2js/build/index.esm.js |
| 144.1 KiB | ../packages/quill/packages/primitives/dist/index.js |
| 126.8 KiB | ../node_modules/.pnpm/[email protected][email protected]/node_modules/react-dom/cjs/react-dom.production.min.js |
| 102.9 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@tiptap/core/dist/index.js |
| 92.5 KiB | ../node_modules/.pnpm/[email protected]/node_modules/prosemirror-view/dist/index.js |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
| Size | File |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/posthog-js/dist/module.mjs |
| 286.4 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.9 KiB | ../packages/quill/packages/quill/dist/index.js |
| 217.5 KiB | ../node_modules/.pnpm/@[email protected][email protected][email protected][email protected]/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.8 KiB | ../node_modules/.pnpm/[email protected]/node_modules/re2js/build/index.esm.js |
| 126.8 KiB | ../node_modules/.pnpm/[email protected][email protected]/node_modules/react-dom/cjs/react-dom.production.min.js |
| 102.9 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@tiptap/core/dist/index.js |
| 92.5 KiB | ../node_modules/.pnpm/[email protected]/node_modules/prosemirror-view/dist/index.js |
| 86.6 KiB | src/lib/api.ts |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
| Size | File |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/posthog-js/dist/module.mjs |
| 286.4 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.9 KiB | ../packages/quill/packages/quill/dist/index.js |
| 217.5 KiB | ../node_modules/.pnpm/@[email protected][email protected][email protected][email protected]/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.8 KiB | ../node_modules/.pnpm/[email protected]/node_modules/re2js/build/index.esm.js |
| 144.1 KiB | ../packages/quill/packages/primitives/dist/index.js |
| 126.8 KiB | ../node_modules/.pnpm/[email protected][email protected]/node_modules/react-dom/cjs/react-dom.production.min.js |
| 102.9 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@tiptap/core/dist/index.js |
| 92.5 KiB | ../node_modules/.pnpm/[email protected]/node_modules/prosemirror-view/dist/index.js |
Largest files eagerly shipped from src/scenes/activity/explore/EventsScene.tsx
| Size | File |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/posthog-js/dist/module.mjs |
| 286.4 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.9 KiB | ../packages/quill/packages/quill/dist/index.js |
| 217.5 KiB | ../node_modules/.pnpm/@[email protected][email protected][email protected][email protected]/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.8 KiB | ../node_modules/.pnpm/[email protected]/node_modules/re2js/build/index.esm.js |
| 144.1 KiB | ../packages/quill/packages/primitives/dist/index.js |
| 126.8 KiB | ../node_modules/.pnpm/[email protected][email protected]/node_modules/react-dom/cjs/react-dom.production.min.js |
| 102.9 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@tiptap/core/dist/index.js |
| 92.5 KiB | ../node_modules/.pnpm/[email protected]/node_modules/prosemirror-view/dist/index.js |
Largest files eagerly shipped from src/scenes/web-analytics/WebAnalyticsScene.tsx
| Size | File |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/posthog-js/dist/module.mjs |
| 286.4 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.9 KiB | ../packages/quill/packages/quill/dist/index.js |
| 217.5 KiB | ../node_modules/.pnpm/@[email protected][email protected][email protected][email protected]/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 187.9 KiB | src/queries/validators.js |
| 153.8 KiB | ../node_modules/.pnpm/[email protected]/node_modules/re2js/build/index.esm.js |
| 144.1 KiB | ../packages/quill/packages/primitives/dist/index.js |
| 126.8 KiB | ../node_modules/.pnpm/[email protected][email protected]/node_modules/react-dom/cjs/react-dom.production.min.js |
| 102.9 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@tiptap/core/dist/index.js |
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
| Size | File |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/posthog-js/dist/module.mjs |
| 316.5 KiB | ../node_modules/.pnpm/[email protected]_@[email protected][email protected]/node_modules/posthog-js/dist/rrweb.js |
| 286.4 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.9 KiB | ../packages/quill/packages/quill/dist/index.js |
| 217.5 KiB | ../node_modules/.pnpm/@[email protected][email protected][email protected][email protected]/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.8 KiB | ../node_modules/.pnpm/[email protected]/node_modules/re2js/build/index.esm.js |
| 126.8 KiB | ../node_modules/.pnpm/[email protected][email protected]/node_modules/react-dom/cjs/react-dom.production.min.js |
| 102.9 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@tiptap/core/dist/index.js |
| 92.5 KiB | ../node_modules/.pnpm/[email protected]/node_modules/prosemirror-view/dist/index.js |
Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of PostHog#32479
✅ Toolbar bundle — eager 2.33 MiB within budget
What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.
| Metric | Size | Δ vs base | Budget |
|---|---|---|---|
| Eager (shipped) entry + static imports |
2.33 MiB · 19 files | no change | ████░░░░░░ 40.8% of 5.72 MiB |
| Deferred (lazy) | 2.11 MiB · 42 files | no change | n/a — loads on demand |
Loader dist/toolbar.js |
1.2 KiB | no change | █░░░░░░░░░ 6.0% of 19.5 KiB |
Largest eagerly-shipped chunks
| Size | File |
|---|---|
| 941.7 KiB | dist/toolbar/toolbar-app-UY2NCWYC.css |
| 695.8 KiB | dist/toolbar/chunk-chunk-OU5JIGRB.js |
| 261.2 KiB | dist/toolbar/chunk-chunk-T5DKNJQU.js |
| 131.8 KiB | dist/toolbar/chunk-chunk-FDH2IBXT.js |
| 130.4 KiB | dist/toolbar/chunk-chunk-H2B6C5E4.js |
| 75.7 KiB | dist/toolbar/toolbar-app-SI4MRMOT.js |
| 69.0 KiB | dist/toolbar/chunk-chunk-TSAL54PB.js |
| 33.8 KiB | dist/toolbar/chunk-chunk-FR2SFTP5.js |
| 21.7 KiB | dist/toolbar/chunk-chunk-SFGY4JKG.js |
| 6.8 KiB | dist/toolbar/chunk-chunk-DV7IWQNF.js |
Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile
✅ Dist folder size — 🔺 +138 B (+0.0%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 966.34 MiB · 🔺 +138 B (+0.0%)
ℹ️ MCP UI apps size — 35 app(s), 29120.8 KB JS
Built size of each MCP UI app (main.js + styles.css).
| App | JS | CSS |
|---|---|---|
| debug | 844.3 KB | 211.6 KB |
| explain-chart | 1117.7 KB | 211.6 KB |
| action | 767.7 KB | 211.6 KB |
| action-list | 835.3 KB | 211.6 KB |
| cohort | 766.7 KB | 211.6 KB |
| cohort-list | 834.3 KB | 211.6 KB |
| email-template | 766.6 KB | 211.6 KB |
| error-details | 771.6 KB | 211.6 KB |
| error-issue | 768.9 KB | 211.6 KB |
| error-issue-list | 836.8 KB | 211.6 KB |
| experiment | 832.4 KB | 211.6 KB |
| experiment-list | 836.1 KB | 211.6 KB |
| experiment-results | 834.3 KB | 211.6 KB |
| feature-flag | 834.7 KB | 211.6 KB |
| feature-flag-list | 838.4 KB | 211.6 KB |
| feature-flag-testing | 770.0 KB | 211.6 KB |
| inline-scan | 767.2 KB | 211.6 KB |
| insight-actors | 833.8 KB | 211.6 KB |
| llm-costs | 831.1 KB | 211.6 KB |
| session-recording | 769.0 KB | 211.6 KB |
| survey | 768.3 KB | 211.6 KB |
| survey-global-stats | 831.1 KB | 211.6 KB |
| survey-list | 836.0 KB | 211.6 KB |
| survey-stats | 831.1 KB | 211.6 KB |
| trace-span | 767.1 KB | 211.6 KB |
| trace-span-list | 835.2 KB | 211.6 KB |
| vision-observation-list | 834.4 KB | 211.6 KB |
| workflow | 767.1 KB | 211.6 KB |
| workflow-list | 834.7 KB | 211.6 KB |
| workflow-setup-status | 766.7 KB | 211.6 KB |
| query-results | 1089.6 KB | 211.6 KB |
| render-ui | 1193.5 KB | 211.6 KB |
| visual-review-snapshots | 771.6 KB | 211.6 KB |
| workflow-data-suggestions | 769.4 KB | 211.6 KB |
| workflow-sending-rules | 768.1 KB | 211.6 KB |
ℹ️ MCP agent API — agent-facing tool changes
What this PR changes for agents, from the tool schema snapshots and tool definitions.
Tools changed (2):
| Tool | Params | Scopes | Annotations | Schema chars |
|---|---|---|---|---|
workflows-send-email |
2,190 -> 2,368 | |||
workflows-upload-email-template |
description changed | 2,420 -> 2,476 |
ba370c8 to
52e70ee
Compare
bf2e702 to
fa61f6b
Compare
fa61f6b to
9836720
Compare
3b06d8a to
3e24e8b
Compare
9836720 to
edc79a5
Compare
3e24e8b to
c4cf425
Compare
e20426a to
5675299
Compare
c4cf425 to
5ce7197
Compare
5675299 to
4a41fc8
Compare
4512563 to
e8fff1d
Compare
4a41fc8 to
6011954
Compare
`POST /api/projects/:id/messaging/send/email/` now takes
`template: {key, hash?}` for a template uploaded from code. A send by key
may set `from`, `reply_to` and `capture_engagement`; a send by
`template.id` still refuses them. With `template.hash`, a hash PostHog
doesn't hold answers `409 template_outdated` and sends nothing, and a
superseded hash sends the current content with a `template_superseded`
warning. An unknown key answers `404 template_not_found`, or
`409 template_outdated` when the send carries a hash. A code-managed
template without text sends the text its html holds.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
6011954 to
be7252c
Compare
…late-send # Conflicts: # products/messaging/backend/tests/api/test_template_upload.py
…ate sends Co-Authored-By: Claude Opus 5.5 <[email protected]>
…answer an unconfirmed key send A send by template.key now answers an unconfirmed send like a code send and reports a distinct addressed_by. A send by template.id takes capture_engagement and points from and reply_to to template.key. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…late-send # Conflicts: # products/messaging/backend/services/template_uploads.py # products/messaging/backend/tests/api/test_template_upload.py
…late-send Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ail from code is off Co-Authored-By: Claude Opus 5.5 <[email protected]>
…tside the email from code beta Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…late-send # Conflicts: # products/messaging/backend/presentation/views/message_templates.py # products/messaging/backend/tests/api/test_template_upload.py
|
Moved to PostHog#127176. |
Part of the code-first rebuild of the email send stack; replaces parts of PostHog#118924 (the template send on
send/email/).Problem
send/email/only takes rendered content, and master's template route needs the PostHog id.capture_engagementon every form, andreply_toandfromon a template uploaded from code.Impact
Pays into Grow Workflows through billed
workflows_emailsactions per paying org, for template sends from code. In scope: Messaging, email API sends.Merge Danger
Warning
🟡 Merge risk: medium · two-way door: a revert removes the template forms from
send/email/capture_engagementand a send by key withfromorreply_toanswer503and send nothing. Template sends without those options deliver (cross-version proof below).workflows-email-from-codeis off. A send by id keeps master's route. One that setscapture_engagementanswers400on that field and sends nothing, because the opt-out needs the beta. An existing caller that sends the field gets the400once this deploys.workflows-email-from-code.Changes
POST messaging/send/email/sends a stored template bytemplate: {key}ortemplate: {id}, next to the rendered-content form, in one serializer that refuses unknown fields.message_datafills the template's{{ variables.* }}.template.hashnames the content the caller holds, asv1:and 64 lowercase hex characters. Another format answers400 invalid_inputontemplate.hash.template: {key, hash}send to a key PostHog doesn't hold yet answers409 template_outdated, as contract Transactional email SDK contract for posthog-python and posthog-node PostHog/posthog#120921 says, so the SDK uploads and sends again. It answered404before. A key sent without a hash still answers404.409 template_outdated. Content a newer upload replaced still sends, with atemplate_supersededwarning.capture_engagement(defaulttrue) works on every form, a send by id included, as map Map: send code-managed email templates with one PostHog call #461 decided.capture_engagementonly whileworkflows-email-from-codeis on. While it is off, a send that sets the field answers400withattr: "capture_engagement": "A send bytemplate.idtakes this field only in the beta for sending email from code. Remove it to send the email." A send without it keeps master's route, with nosendblock. The field's help text says so too.template: {key}that cdp-api does not confirm answers the same503as a code send: the email may still go out, retry with the same Idempotency-Key.addressed_byistemplate_key,template.keyortemplate.id. The help text says a project with capture off captures nothing.template: {key}takesreply_toandfrom, as contract Transactional email SDK contract for posthog-python and posthog-node PostHog/posthog#120921 promises for a template uploaded from code. A namedfrommust be a verified sender, and the email goes out from it instead of the template's. Withoutfrom, the template's own sender holds.template: {id}refusessubject,html,text,fromandreply_towith400 invalid_inputon that field, withwhyandfix. Afromorreply_torefusal points attemplate: {key}: a template made in PostHog sends with its own settings. A send by key still refuses the content fields.capture_engagement: falsesend are still captured. cdp-api mints tracking codes that carry the choice only whenEVERY_TRACKING_DEPLOYMENT_PARSES_CAPTURE_CHOICEis on (feat(cdp): deliver a code send's content on its own transactional email route #565). The flag-on gate forworkflows-email-from-codeflips that switch after feat(cdp): deliver a code send's content on its own transactional email route #565 rolls out to every deployment. The help text keeps the full rule.template_keyhelp says that a template uploaded under a key that code sends already used takes over those sends.warningsreturns with its first producer,template_superseded.TemplateByKeyorTemplateById, never both.How did you test this code?
Test seams: the
send/email/route through the DRF client with cdp-api mocked (tests/api/test_transactional_email.py).Test rationale:
test_a_send_by_template_key_runs_the_templates_workflow:capture_engagement,reply_toor a namedfromnot reaching cdp-api on a send by key. The SDK depends on this seam.test_a_send_by_template_id_carries_its_engagement_capture: a send by id that dropscapture_engagement, or that leaves master's route whileworkflows-email-from-codeis off.test_refuses_engagement_capture_on_a_send_by_template_id_while_email_from_code_is_off: a send outside the beta that drops the opt-out instead of refusing it.test_a_form_encoded_send_by_template_id_without_engagement_capture_keeps_masters_route: a form body that omits the field, which DRF fills withfalse, refused or routed as if the caller set it.test_refuses_on_a_send_by_template_id_what_its_template_sets, one row per field: a send by id that takes a field the template sets. A red run showedreply_topassing.test_a_send_from_code_the_pipeline_does_not_confirm_warns_it_may_still_go_out, over code sends and sends by key: a timeout that tells the caller to retry without the key.fromthat answers anything butsender_not_verifiedonfrom.template_outdated.404and so stops the SDK's upload.test_an_upload_without_text_sends_the_text_its_html_holds: an upload with no text, or a blank text, that sends an empty text part. A single-image upload sends none.Proofs on this head
33e700b(saved withfleet proof):f9abed0, maildev as the sink. 21 requests through the public API: sends by key with the current and a superseded hash, an unknown key, a send by key withfrom,reply_toandcapture_engagement: false, sends by id, code sends and uploads.43333f8. Each difference is a send by key, the404for an unknown key,capture_engagementon a send by id, the id refusal pointing attemplate.key, the derived text part, or the new usage event properties.capture_engagementand the send by key with options answer503 delivery_unavailable; nothing reaches maildev and nothing goes out twice. Withworkflows-email-from-codeoff, a send by id withoutcapture_engagementanswers202and delivers the stored template; withcapture_engagementfalseortrueit answers400and maildev gets nothing (run on0f27c16).Earlier proofs, at c4c5ff0, 7d9f7aa and 9836720
Live run of the two fixes, Django at feat(messaging): mark a code send that leaves the sender to the project #578
c4c5ff0, which carries this head, cdp-api and workers at feat(cdp): key a code send on the sender the caller chose #5801a43bdd, curl into maildev (saved withfleet proof):template_outdatedif_code_managed: true, send again with the same idempotency keyHi Ada, start here (https://example.com/start).derived from the htmlLive run at
7d9f7aa, before the two fixes, cdp-api and workers from feat(cdp): deliver a code send's content on its own transactional email route #565 at24644bc, curl into maildev (saved withfleet proof):message_datatemplate_supersededtemplate_outdatedreply_toinvalid_request,attr: "reply_to", with why and fixcapture_engagement: falsereply_toReply-To: [email protected]from: "Security <[email protected]>"Security <[email protected]>, not the template's[email protected]Capture check at
9836720, the project's engagement capture on, captured events recorded per distinct id:capture_engagement: falsecapture_engagement: truecapture_engagement(default)The click under
falseis the known limit above. Later commits leave the capture path as it was at9836720.Cross-version at
7d9f7aa, Django there and cdp-api atmaster: sends by key and by id answer503with the "did not confirm the send" detail, and maildev gets nothing. The template-by-id route still delivers.The two fixes change only Django, so the cdp-api window above still holds.
Prod shape: a gap until read-only production access exists on 2026-11-02.
CI: the fork has no Depot runners, so those jobs never start, and no secret for the project-board app, so
flags-project-boardfails; the other GitHub-hosted checks pass. The lane check passes: against master the PR changes only the Python and frontend side.Release status
Docs update
None in this PR. The send API docs follow when the flag opens.
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: Claude Code, Claude Opus 5.5 (claude-opus-5-5)
srv2of map Map: send code-managed email templates with one PostHog call #461 from the reviewed stack's fork feat(messaging): upload, send and lock code-managed email templates #548 (themessaging_send.pypart).723e264answers an unconfirmed key send like a code send, lets a send by id carrycapture_engagement, points the id refusal attemplate.key, and reportsaddressed_by: template.key. The merge from feat(messaging): upload email templates from code by key #573 droppedsent_email_hash, because the drift check now compares whole email steps.qa-swarm,tdd,improving-drf-endpoints,writing-tests,writing-user-facing-copy,pr-description,writing-pr-descriptions.capture_engagementdefault to on a template send?trueon every form,template: {id}includedtemplate_keyhelp says soworkflows-email-from-code, per organizationtemplate.idrefuse?subject,html,text,fromandreply_to, as contract PostHog#120921 says;reply_tostays withtemplate.keytemplate.keytakefrom?capture_engagement: falsesend stop being captured?EVERY_TRACKING_DEPLOYMENT_PARSES_CAPTURE_CHOICE, after #565 rolls out everywhere; the help text stays as it is409 template_outdatedwith a hash, as contract PostHog#120921 says;404without one, where the contract is silenttemplate.keyanswer?503: the email may still go out, retry with the same Idempotency-Keycapture_engagementwhileworkflows-email-from-codeis off?400on the field, so the opt-out is never dropped silently. Replaces ignoring the fieldInvolvement: one brief from Michael with decisions; one review round (GPT-6.1 Sol: review, design and Michael's lens; Opus fresh eyes) with its fixes; the SDK lane's live test found the
reply_togap, fixed here; final reads by GPT-6.1 Sol; QA swarm on 2026-11-01 with two panel rounds and three final reads, the last clean: 6 findings fixed, none rejected; live, shadow-diff and cross-version proof captured. Fix reviews (GPT-6.1 Sol, correctness and systems):6d6c5a2clean;30b7e40found the form-body refusal, fixed in0f27c16, and a flag-off caller now getting400, rejected as the review owner's decision; the final read of0f27c16found nothing.🤖 Generated with Claude Code