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: 76.62 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/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 24.6 KiB | ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js |
| 6.3 KiB | ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js |
| 4.7 KiB | ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js |
| 4.0 KiB | ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js |
| 1.7 KiB | ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js |
| 1.6 KiB | src/index.tsx |
| 1.3 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 |
|---|---|
| 322.5 KiB | ../node_modules/.pnpm/posthog-js@1.438.21_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 215.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 99.8 KiB | src/products.tsx |
| 86.2 KiB | src/lib/api.ts |
| 68.2 KiB | src/lib/lemon-ui/icons/icons.tsx |
| 41.9 KiB | ../node_modules/.pnpm/@dnd-kit+core@6.0.8_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@dnd-kit/core/dist/core.esm.js |
| 36.4 KiB | ../node_modules/.pnpm/kea@4.0.0-pre.6_patch_hash=139b8d1f1304f9d9da452a9a1244c94ea679dbcb85687d8999563146879fb6f5_react@18.3.1/node_modules/kea/lib/index.cjs.js |
| 27.0 KiB | ../node_modules/.pnpm/tailwind-merge@2.2.2/node_modules/tailwind-merge/dist/bundle-mjs.mjs |
| 24.6 KiB | ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 322.5 KiB | ../node_modules/.pnpm/posthog-js@1.438.21_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 285.9 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.8 KiB | ../packages/quill/packages/quill/dist/index.js |
| 215.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.8 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 99.8 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js |
| 92.5 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
| 86.2 KiB | src/lib/api.ts |
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
| Size | File |
|---|---|
| 322.5 KiB | ../node_modules/.pnpm/posthog-js@1.438.21_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 285.9 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.8 KiB | ../packages/quill/packages/quill/dist/index.js |
| 215.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.8 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 144.1 KiB | ../packages/quill/packages/primitives/dist/index.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 99.8 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js |
| 92.5 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/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 |
|---|---|
| 322.5 KiB | ../node_modules/.pnpm/posthog-js@1.438.21_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 285.9 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.8 KiB | ../packages/quill/packages/quill/dist/index.js |
| 215.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.8 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 99.8 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js |
| 92.5 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
| 86.2 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 |
|---|---|
| 322.5 KiB | ../node_modules/.pnpm/posthog-js@1.438.21_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 285.9 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.8 KiB | ../packages/quill/packages/quill/dist/index.js |
| 215.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.8 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 144.1 KiB | ../packages/quill/packages/primitives/dist/index.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 99.8 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js |
| 92.5 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
Largest files eagerly shipped from src/scenes/activity/explore/EventsScene.tsx
| Size | File |
|---|---|
| 322.5 KiB | ../node_modules/.pnpm/posthog-js@1.438.21_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 285.9 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.8 KiB | ../packages/quill/packages/quill/dist/index.js |
| 215.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.8 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 144.1 KiB | ../packages/quill/packages/primitives/dist/index.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 99.8 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js |
| 92.5 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
Largest files eagerly shipped from src/scenes/web-analytics/WebAnalyticsScene.tsx
| Size | File |
|---|---|
| 322.5 KiB | ../node_modules/.pnpm/posthog-js@1.438.21_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 285.9 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.8 KiB | ../packages/quill/packages/quill/dist/index.js |
| 215.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 182.9 KiB | src/queries/validators.js |
| 153.8 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 144.1 KiB | ../packages/quill/packages/primitives/dist/index.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 99.8 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js |
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
| Size | File |
|---|---|
| 322.5 KiB | ../node_modules/.pnpm/posthog-js@1.438.21_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 316.1 KiB | ../node_modules/.pnpm/posthog-js@1.438.21_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js |
| 285.9 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 239.8 KiB | ../packages/quill/packages/quill/dist/index.js |
| 215.9 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 153.8 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 99.8 KiB | src/products.tsx |
| 94.5 KiB | ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js |
| 92.5 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/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.29 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.29 MiB · 18 files | no change | ████░░░░░░ 40.0% of 5.72 MiB |
| Deferred (lazy) | 2.11 MiB · 40 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 |
|---|---|
| 902.2 KiB | dist/toolbar/toolbar-app-PUACMHS6.css |
| 692.2 KiB | dist/toolbar/chunk-chunk-QQQ5YJLC.js |
| 260.7 KiB | dist/toolbar/chunk-chunk-L6XVLVLY.js |
| 131.8 KiB | dist/toolbar/chunk-chunk-FDH2IBXT.js |
| 130.4 KiB | dist/toolbar/chunk-chunk-BIOZUYB7.js |
| 74.8 KiB | dist/toolbar/toolbar-app-3H3VSIEG.js |
| 69.0 KiB | dist/toolbar/chunk-chunk-TSAL54PB.js |
| 35.6 KiB | dist/toolbar/chunk-chunk-EIARHJWB.js |
| 21.7 KiB | dist/toolbar/chunk-chunk-JR4YE4FS.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 — 🔺 +150 B (+0.0%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 964.92 MiB · 🔺 +150 B (+0.0%)
ℹ️ MCP UI apps size — 34 app(s), 27969.1 KB JS
Built size of each MCP UI app (main.js + styles.css).
| App | JS | CSS |
|---|---|---|
| debug | 844.3 KB | 209.1 KB |
| action | 767.7 KB | 209.1 KB |
| action-list | 835.3 KB | 209.1 KB |
| cohort | 766.7 KB | 209.1 KB |
| cohort-list | 834.3 KB | 209.1 KB |
| email-template | 766.6 KB | 209.1 KB |
| error-details | 771.6 KB | 209.1 KB |
| error-issue | 768.9 KB | 209.1 KB |
| error-issue-list | 836.7 KB | 209.1 KB |
| experiment | 832.4 KB | 209.1 KB |
| experiment-list | 836.1 KB | 209.1 KB |
| experiment-results | 834.9 KB | 209.1 KB |
| feature-flag | 834.7 KB | 209.1 KB |
| feature-flag-list | 838.4 KB | 209.1 KB |
| feature-flag-testing | 770.0 KB | 209.1 KB |
| inline-scan | 767.2 KB | 209.1 KB |
| insight-actors | 833.8 KB | 209.1 KB |
| llm-costs | 831.1 KB | 209.1 KB |
| session-recording | 768.9 KB | 209.1 KB |
| survey | 768.3 KB | 209.1 KB |
| survey-global-stats | 831.1 KB | 209.1 KB |
| survey-list | 836.0 KB | 209.1 KB |
| survey-stats | 831.1 KB | 209.1 KB |
| trace-span | 767.1 KB | 209.1 KB |
| trace-span-list | 835.2 KB | 209.1 KB |
| vision-observation-list | 834.4 KB | 209.1 KB |
| workflow | 767.0 KB | 209.1 KB |
| workflow-list | 834.7 KB | 209.1 KB |
| workflow-setup-status | 766.7 KB | 209.1 KB |
| query-results | 1086.5 KB | 209.1 KB |
| render-ui | 1162.4 KB | 209.1 KB |
| visual-review-snapshots | 771.6 KB | 209.1 KB |
| workflow-data-suggestions | 769.4 KB | 209.1 KB |
| workflow-sending-rules | 768.0 KB | 209.1 KB |
ℹ️ MCP agent API — agent-facing tool changes
What this PR changes for agents, from the tool schema snapshots and tool definitions.
Tools changed (1):
| Tool | Params | Scopes | Annotations | Schema chars |
|---|---|---|---|---|
workflows-send-email |
description changed |
c4fc6e3 to
c55794e
Compare
a02de53 to
54de7cd
Compare
49cab17 to
24881fa
Compare
54de7cd to
f9c45be
Compare
24881fa to
801bdfc
Compare
e70cb46 to
79e619b
Compare
a5d494f to
1d7fe4c
Compare
79e619b to
ad8792b
Compare
1d7fe4c to
0eeb308
Compare
ad8792b to
c4c5ff0
Compare
0eeb308 to
25f064a
Compare
c4c5ff0 to
67987fd
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ches its first send Django checks template.hash before cdp-api sees the key, so a retry with a hash PostHog doesn't hold answers template_outdated. After an upload, the same key replays the first send. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ller The fix told every caller to keep its Idempotency-Key, and the 409 description promised an earlier send's message_id without a condition. An unkeyed caller has no key, and on a fresh key the retry after an upload is the first send. The fix, the 409 description and the header help now state one rule: if you sent a key and an earlier request with it already sent the email, the retry answers with that message_id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A retry with a held Idempotency-Key and a template.hash answers 409 template_outdated once the key no longer names a template, and 404 template_not_found without a hash. The template lookup runs before the replay, so neither answer replays the first send. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
25f064a to
0f434a3
Compare
67987fd to
c6e5910
Compare
Part of the code-first rebuild of the email send stack; replaces parts of PostHog#121867 (one send per idempotency key on
send/email/).Problem
send/email/safely. The route ignoresIdempotency-Key, so a retry after a timeout or a503can send the email twice.Impact
Pays into Grow Workflows through billed
workflows_emailsactions per paying org: callers can retry without double sends. In scope: Messaging, email API sends.Merge Danger
Warning
🟡 Merge risk: medium · two-way door: a revert makes
send/email/ignore the key againsend/email/forwardsIdempotency-Keyto cdp-api, behindworkflows-email-from-code. The template route keeps its own key handling.workflows-email-from-codeoff for real callers until both are live. With this PR live first, a keyed send or retry to a paused email answers503every time, while the503detail and the key help promise a replay after a pause.template.hashPostHog doesn't hold answers409 template_outdatedbefore cdp-api sees the key, and sends nothing. After an upload, the same key replays the first send. The409help and the key help say so.422, as both new. A keyed send to a paused email answers503and sends nothing, where both new answer400. Nothing sends twice. The live run below shows it.422 idempotency_key_reused, with or without a namedfrom, and sends nothing.Changes
What a caller sees change
202with the first send'smessage_idandreplayed: true, for 24 hours.422 idempotency_key_reused. A key still in progress answers409 idempotency_key_in_flight.template_key,to,distinct_id,message_data, the content, a namedfrom,reply_toandcapture_engagement. It doesn't bindtemplate.hash, the template's content or the default sender, so those changes still replay, as long as PostHog holds the content the hash names. TheIdempotency-Keyhelp text says this.503detail and help now say a retry with the same key is safe. Without a key, they still say to check first.How it works
send.from.isDefaultwhen the send names nofrom, so cdp-api can leave the default sender out of the key.transactional_email_pausedanswer.409 template_outdatedwith a hash and404without one, and never replays. Contract Transactional email SDK contract for posthog-python and posthog-node PostHog/posthog#120921 says a deleted template still answers404or409.template.hashPostHog no longer holds answers409before the key lookup. Replaying it first needs a cdp-api route that looks a key up. Contract Transactional email SDK contract for posthog-python and posthog-node PostHog/posthog#120921 allows the409today.How did you test this code?
Test seams:
send/email/through the DRF client with cdp-api mocked (test_transactional_email.py).Test rationale:
409or404.Proofs: live runs saved with
fleet proof, curl into maildev. The paused key is a key's workflow set to draft through the model, which reloads the workers.40b98c4, cdp-api at #5801a43bdd)ad8792b, cdp-api before #580 (8b150c5)replayed: true, one email in totalreplayed: true, one email in totalidempotency_key_reused, nothing sentidempotency_key_reused, nothing senttransactional_email_paused, with why and fix; nothing sentdelivery_unavailable; nothing senttransactional_email_paused; nothing senttransactional_email_paused; nothing sentreplayed: true, one email in totalfrom, then its retryreplayed: true, one email in totalreplayed: true, one email in totalreplayed: true, one email in totalidempotency_key_reused; one email in totalfrom, retry after the default sender changedreplayed: true, one email in totalidempotency_key_reused; one email in totaltransactional_email.pyonly.ad8792bdiffers from this head only in 05's two fixes underneath and one test commit. No case sends an unknown key, and cdp-api did not change, so that window answers the same at this head.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 fork feat(messaging): send each email once per idempotency key on send/email #557, with its review fixes kept.tdd,writing-tests,improving-drf-endpoints,implementing-mcp-tools,writing-user-facing-copy,pr-description,writing-pr-descriptions.template.hashreplay?409holds, as contract PostHog#120921 allowsOpen: the workflow API can still change a code-managed template's workflow (D1). The planned read-only lock for workflows managed as code covers it.
Involvement: one brief from Michael with decisions; review rounds 1 and 2 (GPT-6.1 Sol team review, Opus fresh eyes) fixed; a final read by GPT-6.1 Sol found nothing; live and cross-version proof captured.
🤖 Generated with Claude Code