Repository navigation
Conversation
`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 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…late-send # Conflicts: # products/messaging/backend/tests/api/test_template_upload.py
…ate sends Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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 <noreply@anthropic.com>
…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 <noreply@anthropic.com>
…ail from code is off Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tside the email from code beta Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…late-send # Conflicts: # products/messaging/backend/presentation/views/message_templates.py # products/messaging/backend/tests/api/test_template_upload.py
🤖 CI report
|
| Root | Eager (shipped) | Δ vs base | Budget |
|---|---|---|---|
entry (logged-out pages, app bootstrap)src/index.tsx |
1.02 MiB · 25 files | no change | █████████░ 88.8% of 1.14 MiB |
logged-out boot: index + App + bootApp (preloaded by every page, including /login)src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts |
2.99 MiB · 622 files | no change | ██████████ 98.5% of 3.03 MiB |
authenticated shell (every logged-in page)src/scenes/AuthenticatedShell.tsx |
6.46 MiB · 2,235 files | no change | ██████████ 95.4% of 6.77 MiB |
dashboard scenesrc/scenes/dashboard/Dashboard.tsx |
9.97 MiB · 3,728 files | no change | ██████████ 99.5% of 10.01 MiB |
today home pathsrc/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx |
6.71 MiB · 2,337 files | no change | ██████████ 96.4% of 6.96 MiB |
AI-first home pathsrc/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx |
7.28 MiB · 2,426 files | no change | ██████████ 96.7% of 7.53 MiB |
events scenesrc/scenes/activity/explore/EventsScene.tsx |
9.40 MiB · 3,500 files | no change | ██████████ 98.0% of 9.59 MiB |
web analytics scenesrc/scenes/web-analytics/WebAnalyticsScene.tsx |
10.36 MiB · 3,767 files | no change | ██████████ 95.6% of 10.83 MiB |
replay detail scenesrc/scenes/session-recordings/detail/SessionRecordingDetail.tsx |
7.99 MiB · 2,968 files | no change | ████████░░ 84.4% of 9.47 MiB |
🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/svg/ stays out of src/index.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/components/ stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/layout/navigation-3000/navigationLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/scenes/dashboard/dashboardLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/lemon-ui/LemonMarkdown/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/RichContentEditor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/CodeSnippet/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/taxonomy/core-filter-definitions-by-group.json stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 node_modules/zod/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/scenes/billing/billingLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/ProductEmptyState/productSetupPreloadLogic.ts stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 products/dashboards/frontend/widgets/previews/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 products/wizard/frontend/runs/WizardRunDetailsDrawer.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/TodayShell.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/TodayContextPanelFloating.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/TodaySidePanel.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/TodayTopBar.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/todayRailItems.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 zod/v4/locales/de.js stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/svg/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/components/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/queries/validators.js stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/scenes/project-homepage/today/TodayReportPage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/queries/Query/Query.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 packages/quill/packages/charts/ stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 products/posthog_ai/frontend/components/tool/widgets/VisualizationWidget.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 products/posthog_ai/frontend/components/tool/widgets/RecordingsWidget.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 src/queries/Query/Query.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 products/posthog_ai/frontend/api/primitives.ts stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 src/scenes/notebooks/Notebook/notebookLogic.ts stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/queries/validators.js stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/scenes/web-analytics/LiveMetricsDashboard/LiveWebAnalyticsMetrics.tsx stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 products/product_analytics/frontend/insights/trends/WorldMap/countryVectors.tsx stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 src/scenes/settings/SettingsMap.tsx stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 @jspm/core/nodelibs/browser/fs stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
Largest files eagerly shipped from src/index.tsx
| 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 | src/index.tsx |
| 1.7 KiB | ../node_modules/.pnpm/base64-js@1.5.1/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/posthog-js@1.443.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 217.5 KiB | ../node_modules/.pnpm/@posthog+icons@0.39.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 |
| 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/@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 |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/posthog-js@1.443.0_@types+react@18.3.27_react@18.3.1/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/@posthog+icons@0.39.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 |
| 102.9 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.6 KiB | src/lib/api.ts |
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
| Size | File |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/posthog-js@1.443.0_@types+react@18.3.27_react@18.3.1/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/@posthog+icons@0.39.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 |
| 102.9 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 |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/posthog-js@1.443.0_@types+react@18.3.27_react@18.3.1/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/@posthog+icons@0.39.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 |
| 102.9 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.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/posthog-js@1.443.0_@types+react@18.3.27_react@18.3.1/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/@posthog+icons@0.39.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 |
| 102.9 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 |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/posthog-js@1.443.0_@types+react@18.3.27_react@18.3.1/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/@posthog+icons@0.39.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 |
| 102.9 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 |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/posthog-js@1.443.0_@types+react@18.3.27_react@18.3.1/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/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 187.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 |
| 102.9 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 |
|---|---|
| 325.0 KiB | ../node_modules/.pnpm/posthog-js@1.443.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 316.5 KiB | ../node_modules/.pnpm/posthog-js@1.443.0_@types+react@18.3.27_react@18.3.1/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/@posthog+icons@0.39.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 |
| 102.9 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 #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 |
✅ Django migration risk — migration analysis complete
We've analyzed your migrations for potential risks.
Summary: 4 Safe | 0 Needs Review | 0 Blocked
✅ Safe
Brief or no lock, backwards compatible
messaging.0012_messagetemplate_key
└─ #1 ✅ AddField
Adding nullable field requires brief lock
model: messagetemplate, field: key
messaging.0013_messagetemplate_unique_key_per_team
└─ #1 ✅ SeparateDatabaseAndState
Wrapper operation - see nested operations for risk: CreateIndexConcurrently
database_operations: CreateIndexConcurrently
└─ #2 ✅ CreateIndexConcurrently
PostHog concurrent-index helper: idempotent (timeout disabling + invalid-leftover recovery)
model: None, index: unique_message_template_key_per_team
messaging.0014_transactional_email_key
└─ #1 ✅ CreateModel
Creating new table is safe
model: TransactionalEmailKey
│
└──> ℹ️ INFO:
ℹ️ Skipped operations on newly created tables (empty tables
don't cause lock contention).
messaging.0015_messagetemplate_code_managed_fields
└─ #1 ✅ AddField
Adding nullable field requires brief lock
model: messagetemplate, field: content_hash
└─ #2 ✅ AddField
Adding nullable field requires brief lock
model: messagetemplate, field: managed_by
└─ #3 ✅ AddField
Adding nullable field requires brief lock
model: messagetemplate, field: source_path
└─ #4 ✅ AddField
Adding nullable field requires brief lock
model: messagetemplate, field: source_ref
└─ #5 ✅ AddField
Adding nullable field requires brief lock
model: messagetemplate, field: source_repository
└─ #6 ✅ AddField
Adding nullable field requires brief lock
model: messagetemplate, field: superseded_content_hashes
📚 How to Deploy These Changes Safely
AddField:
This operation acquires a brief lock but doesn't rewrite the table.
Deployment uses lock timeouts with automatic retries, so lock contention will cause retries rather than connection pile-up.
Last updated: 2026-11-02 10:24 UTC (b206f1b)
There was a problem hiding this comment.
PostHog Review · Standard · reviewed 9974101: 2 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.
| if "key" in data["template"]: | ||
| refuse_unknown_fields(sent, self.fields) |
There was a problem hiding this comment.
P2 · Recognize nested form fields before rejecting unknown fields
For form-encoded requests with template.key=welcome, DRF correctly parses the nested template, but sent still contains the raw template.key name. This check rejects it as unknown because self.fields contains only template, so every form-encoded send by key returns 400. Normalize nested form field names before checking for unknown fields.
| if found.hash_state == ContentHashState.OUTDATED: | ||
| raise TemplateOutdated(TEMPLATE_OUTDATED_DETAIL) | ||
| return send_transactional_email( | ||
| self.team, | ||
| template.id, |
There was a problem hiding this comment.
P2 · Validate the published version in the delivery path
After an upload, delayed or lost workflow cache invalidation leaves CDP serving older content. found.hash_state validates the new Postgres content, but send_transactional_email forwards no expected version, so the request succeeds and delivers the previous template without a warning. Pass the published hash through and validate the content used for delivery.
…late-send # Conflicts: # products/workflows/mcp/email_templates.yaml
Co-authored-by: posthog-js-upgrader[bot] <248546023+posthog-js-upgrader[bot]@users.noreply.github.com>
…nts (#129766) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…9767) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: posthog-js-upgrader[bot] <248546023+posthog-js-upgrader[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: posthog[bot] <206114724+posthog[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…tage (#130720) Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…e-send # Conflicts: # posthog/plugins/plugin_server_api.py # products/messaging/backend/facade/contracts.py # products/messaging/backend/facade/templates.py # products/messaging/backend/facade/transactional_email.py # products/messaging/backend/migrations/max_migration.txt # products/messaging/backend/models/message_template.py # products/messaging/backend/models/transactional_email_key.py # products/messaging/backend/presentation/views/message_templates.py # products/messaging/backend/services/html_text.py # products/messaging/backend/services/transactional_email.py # products/messaging/backend/tests/api/test_transactional_email.py # products/messaging/backend/tests/test_html_text.py # products/messaging/frontend/generated/api.schemas.ts # products/messaging/frontend/generated/api.ts # products/messaging/frontend/generated/api.zod.ts # products/workflows/backend/facade/transactional_emails.py # products/workflows/backend/services/transactional_email_workflows.py # products/workflows/frontend/generated/api.zod.ts # products/workflows/mcp/email_templates.yaml # services/mcp/schema/generated-tool-definitions.json # services/mcp/schema/tool-definitions-all.json # services/mcp/src/api/generated.ts # services/mcp/src/generated/email_templates/api.ts # services/mcp/src/generated/workflows/api.ts # services/mcp/src/tools/generated/email_templates.ts # services/mcp/tests/unit/__snapshots__/tool-schemas/broadcasts-create.json # services/mcp/tests/unit/__snapshots__/tool-schemas/workflows-create.json
# Conflicts: # products/messaging/backend/facade/contracts.py # products/messaging/backend/facade/templates.py # products/messaging/backend/presentation/views/message_templates.py # products/messaging/backend/tests/api/test_transactional_email.py # products/messaging/frontend/generated/api.schemas.ts # products/messaging/frontend/generated/api.ts # services/mcp/tests/unit/__snapshots__/tool-schemas/workflows-send-email.json
…130770) Co-authored-by: posthog[bot] <206114724+posthog[bot]@users.noreply.github.com>
Co-authored-by: posthog[bot] <206114724+posthog[bot]@users.noreply.github.com> Co-authored-by: Paul D'Ambra <paul@posthog.com> Co-authored-by: tests-posthog[bot] <250237707+tests-posthog[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: veryayskiy <13624984+veryayskiy@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…130143) Co-authored-by: posthog[bot] <206114724+posthog[bot]@users.noreply.github.com> Co-authored-by: Gilbert09 <1459269+Gilbert09@users.noreply.github.com> Co-authored-by: tests-posthog[bot] <250237707+tests-posthog[bot]@users.noreply.github.com>
…d template_hash Take the key and the hash check as flat send fields beside master's template_id, regenerate the OpenAPI types and MCP tool schemas, and keep the MCP send tool on rendered content only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moved from Silthus#574.
Part of the code-first rebuild of the email send stack; replaces parts of #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 #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 Silthus/posthog#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 #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 #127122). 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 #127122 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 #127182
c4c5ff0, which carries this head, cdp-api and workers at feat(cdp): key a code send on the sender the caller chose #1271291a43bdd, 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 #127122 at24644bc, curl into maildev (saved withfleet proof):message_datatemplate_supersededtemplate_outdatedreply_toinvalid_request,attr: "reply_to", with why and fixcapture_engagement: falsereply_toReply-To: support@example.comfrom: "Security <security@example.com>"Security <security@example.com>, not the template'shello@example.comCapture 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.
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 Silthus/posthog#461 from the reviewed stack's fork feat(messaging): upload, send and lock code-managed email templates Silthus/posthog#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 #127169 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 #120921 says;reply_tostays withtemplate.keytemplate.keytakefrom?capture_engagement: falsesend stop being captured?EVERY_TRACKING_DEPLOYMENT_PARSES_CAPTURE_CHOICE, after #127122 rolls out everywhere; the help text stays as it is409 template_outdatedwith a hash, as contract #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