Repository navigation
feat(workflows): edit a transactional template's sender from the editor - #121079
Conversation
|
😎 Stack merged successfully - details. |
🤖 CI report
|
| Function | Location | Complexity | Limit |
|---|---|---|---|
MessageTemplate |
products/workflows/frontend/TemplateLibrary/MessageTemplate.tsx:58 |
61 | 10 |
NativeEmailTemplaterForm |
frontend/src/scenes/hog-functions/email-templater/EmailTemplater.tsx:532 |
15 | 10 |
NativeEmailIntegrationChoice |
frontend/src/scenes/hog-functions/email-templater/EmailTemplater.tsx:269 |
13 | 10 |
EmailSenderSummary |
frontend/src/scenes/hog-functions/email-templater/EmailSenderSummary.tsx:13 |
12 | 10 |
<anonymous> |
frontend/src/scenes/hog-functions/email-templater/emailTemplaterLogic.tsx:927 |
12 | 10 |
✅ Duplication (Python) — clean
New Python code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.
✅ Duplication (TypeScript) — clean
New TypeScript code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.
⚠️ Comment density — 4% of added code lines are comments (17 of 463)
This section warns when comments are more than 3% of the code lines a PR adds, and alerts above 6%. Before agent-assisted PRs, the typical share was about 2%. Only full-line comments count. Docstrings, generated files, snapshots, migrations, and workflow files are left out.
Comments that restate the code, record how the change came about, or narrate the next line add noise for the next reader. Keep the comments that explain a reason the code cannot show, and remove the rest. See .agents/skills/writing-code-comments/SKILL.md for the house rules.
Files with the most added comment lines:
| File | Comment lines | Added lines |
|---|---|---|
frontend/src/scenes/hog-functions/email-templater/emailSenderFields.ts |
5 | 57 |
frontend/src/scenes/hog-functions/email-templater/emailTemplaterLogic.tsx |
3 | 15 |
frontend/src/scenes/hog-functions/email-templater/EmailSenderForm.tsx |
2 | 109 |
frontend/src/scenes/hog-functions/email-templater/EmailTemplater.tsx |
2 | 60 |
products/workflows/frontend/TemplateLibrary/messageTemplateLogic.ts |
2 | 42 |
frontend/src/scenes/hog-functions/email-templater/emailSenderFields.test.ts |
1 | 37 |
products/workflows/frontend/TemplateLibrary/messageTemplateTestSendLogic.test.ts |
1 | 14 |
products/workflows/frontend/TemplateLibrary/messageTemplateTestSendLogic.ts |
1 | 18 |
This check does not block merging. It updates on every push and clears when the share drops.
⚠️ Bundle size — 🔺 +554 B (+0.0%)
Uncompressed size of every built .js bundle, compared against the base branch.
Total: 75.83 MiB · 🔺 +554 B (+0.0%)
No file changed by more than 1000 B.
Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report
⚠️ Eager graph — 2 over budget
How 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 import() / React.lazy chunks are not counted.
| Root | Eager (shipped) | Δ vs base | Budget |
|---|---|---|---|
entry (logged-out pages, app bootstrap)src/index.tsx |
1.01 MiB · 24 files | no change | █████████░ 88.6% 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.93 MiB · 615 files | no change | ██████████ 96.8% of 3.03 MiB |
🟡 authenticated shell (every logged-in page)src/scenes/AuthenticatedShell.tsx |
7.59 MiB · 2,580 files | no change | ██████████ 101.2% of 7.50 MiB |
dashboard scenesrc/scenes/dashboard/Dashboard.tsx |
9.61 MiB · 3,646 files | no change | ██████████ 96.0% of 10.01 MiB |
🟡 today home pathsrc/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx |
7.99 MiB · 2,919 files | no change | ██████████ 103.3% of 7.74 MiB |
events scenesrc/scenes/activity/explore/EventsScene.tsx |
9.09 MiB · 3,437 files | no change | █████████░ 94.8% of 9.59 MiB |
web analytics scenesrc/scenes/web-analytics/WebAnalyticsScene.tsx |
9.93 MiB · 3,652 files | no change | █████████░ 91.7% of 10.83 MiB |
replay detail scenesrc/scenes/session-recordings/detail/SessionRecordingDetail.tsx |
8.16 MiB · 3,021 files | no change | █████████░ 86.2% 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
🟢 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
🟢 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.5 KiB | ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js |
| 3.9 KiB | ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js |
| 1.4 KiB | ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js |
| 1.4 KiB | src/index.tsx |
| 1.3 KiB | src/RootErrorBoundary.tsx |
| 1.3 KiB | src/lib/utils/bootTiming.ts |
| 912 B | ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js |
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
| Size | File |
|---|---|
| 321.6 KiB | ../node_modules/.pnpm/posthog-js@1.438.12_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 220.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 |
| 106.8 KiB | src/products.tsx |
| 98.3 KiB | src/lib/api.ts |
| 69.4 KiB | src/lib/lemon-ui/icons/icons.tsx |
| 38.7 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 |
| 33.9 KiB | ../node_modules/.pnpm/kea@4.0.0-pre.6_patch_hash=139b8d1f1304f9d9da452a9a1244c94ea679dbcb85687d8999563146879fb6f5_react@18.3.1/node_modules/kea/lib/index.cjs.js |
| 29.4 KiB | src/scenes/scenes.ts |
| 26.8 KiB | src/lib/constants.tsx |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 321.6 KiB | ../node_modules/.pnpm/posthog-js@1.438.12_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 280.7 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 238.5 KiB | ../packages/quill/packages/quill/dist/index.js |
| 220.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.7 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 143.2 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 |
| 106.8 KiB | src/products.tsx |
| 98.3 KiB | src/lib/api.ts |
| 93.3 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
| Size | File |
|---|---|
| 321.6 KiB | ../node_modules/.pnpm/posthog-js@1.438.12_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 280.7 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 238.5 KiB | ../packages/quill/packages/quill/dist/index.js |
| 220.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.7 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 143.2 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 |
| 106.8 KiB | src/products.tsx |
| 98.3 KiB | src/lib/api.ts |
| 93.3 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 |
|---|---|
| 321.6 KiB | ../node_modules/.pnpm/posthog-js@1.438.12_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 280.7 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 238.5 KiB | ../packages/quill/packages/quill/dist/index.js |
| 220.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.7 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 143.2 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 |
| 106.8 KiB | src/products.tsx |
| 98.3 KiB | src/lib/api.ts |
| 93.3 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 |
|---|---|
| 321.6 KiB | ../node_modules/.pnpm/posthog-js@1.438.12_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 280.7 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 238.5 KiB | ../packages/quill/packages/quill/dist/index.js |
| 220.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.7 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 143.2 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 |
| 106.8 KiB | src/products.tsx |
| 98.3 KiB | src/lib/api.ts |
| 93.3 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 |
|---|---|
| 321.6 KiB | ../node_modules/.pnpm/posthog-js@1.438.12_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 280.7 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 238.5 KiB | ../packages/quill/packages/quill/dist/index.js |
| 220.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 |
| 183.2 KiB | src/queries/validators.js |
| 153.7 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 143.2 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 |
| 106.8 KiB | src/products.tsx |
| 98.3 KiB | src/lib/api.ts |
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
| Size | File |
|---|---|
| 321.6 KiB | ../node_modules/.pnpm/posthog-js@1.438.12_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 315.9 KiB | ../node_modules/.pnpm/posthog-js@1.438.12_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js |
| 280.7 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 238.5 KiB | ../packages/quill/packages/quill/dist/index.js |
| 220.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.7 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 |
| 106.8 KiB | src/products.tsx |
| 98.3 KiB | src/lib/api.ts |
| 93.3 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.28 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.28 MiB · 19 files | no change | ████░░░░░░ 39.9% of 5.72 MiB |
| Deferred (lazy) | 2.10 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 |
|---|---|
| 898.5 KiB | dist/toolbar/toolbar-app-LIRVHRPD.css |
| 691.0 KiB | dist/toolbar/chunk-chunk-HVEQZXL4.js |
| 258.3 KiB | dist/toolbar/chunk-chunk-YUTUHJHZ.js |
| 131.8 KiB | dist/toolbar/chunk-chunk-FDH2IBXT.js |
| 130.4 KiB | dist/toolbar/chunk-chunk-MGILAAF6.js |
| 74.8 KiB | dist/toolbar/toolbar-app-F7Q25OJS.js |
| 69.0 KiB | dist/toolbar/chunk-chunk-TSAL54PB.js |
| 35.6 KiB | dist/toolbar/chunk-chunk-CWKXICRU.js |
| 21.7 KiB | dist/toolbar/chunk-chunk-XK3VNHDG.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 — 🔺 +71.9 KiB (+0.0%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 1016.12 MiB · 🔺 +71.9 KiB (+0.0%)
ℹ️ MCP UI apps size — 34 app(s), 27827.8 KB JS
Built size of each MCP UI app (main.js + styles.css).
| App | JS | CSS |
|---|---|---|
| debug | 840.0 KB | 208.3 KB |
| action | 764.0 KB | 208.3 KB |
| action-list | 831.0 KB | 208.3 KB |
| cohort | 763.0 KB | 208.3 KB |
| cohort-list | 829.9 KB | 208.3 KB |
| email-template | 762.9 KB | 208.3 KB |
| error-details | 767.9 KB | 208.3 KB |
| error-issue | 764.4 KB | 208.3 KB |
| error-issue-list | 831.6 KB | 208.3 KB |
| experiment | 828.1 KB | 208.3 KB |
| experiment-list | 831.7 KB | 208.3 KB |
| experiment-results | 830.6 KB | 208.3 KB |
| feature-flag | 830.3 KB | 208.3 KB |
| feature-flag-list | 834.1 KB | 208.3 KB |
| feature-flag-testing | 766.3 KB | 208.3 KB |
| inline-scan | 763.5 KB | 208.3 KB |
| insight-actors | 829.4 KB | 208.3 KB |
| llm-costs | 826.7 KB | 208.3 KB |
| session-recording | 765.3 KB | 208.3 KB |
| survey | 764.6 KB | 208.3 KB |
| survey-global-stats | 826.8 KB | 208.3 KB |
| survey-list | 831.6 KB | 208.3 KB |
| survey-stats | 826.8 KB | 208.3 KB |
| trace-span | 763.4 KB | 208.3 KB |
| trace-span-list | 830.9 KB | 208.3 KB |
| vision-observation-list | 830.0 KB | 208.3 KB |
| workflow | 763.4 KB | 208.3 KB |
| workflow-list | 830.3 KB | 208.3 KB |
| workflow-setup-status | 763.0 KB | 208.3 KB |
| query-results | 1081.8 KB | 208.3 KB |
| render-ui | 1156.7 KB | 208.3 KB |
| visual-review-snapshots | 767.9 KB | 208.3 KB |
| workflow-data-suggestions | 765.7 KB | 208.3 KB |
| workflow-sending-rules | 764.4 KB | 208.3 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-create-email-template |
3,354 -> 4,347 | |||
workflows-update-email-template |
3,440 -> 4,433 |
|
|
@PostHog review |
|
@mayteio PostHog Review is reviewing this pull request. |
| fields["from"] = EmailTemplateSenderSerializer( | ||
| required=False, | ||
| help_text="The sender a transactional template sends from. A template made transactional without " | ||
| "one gets the sender it was made transactional with.", | ||
| ) |
There was a problem hiding this comment.
P1 · Preserve creation of templates with an empty sender
Creating a blank library template sends NEW_TEMPLATE.content.email.from === '' through saveTemplate unchanged. The new nested serializer rejects this with “Expected a dictionary, but got str”, so ordinary template creation now returns 400 even with transactional emails disabled. Normalize the empty sender away or update all existing authoring paths.
There was a problem hiding this comment.
Fixed in dde6371. The email serializer treats an empty or null from as no sender, so a blank template from the editor creates again. I reproduced the 400 in the UI first. The workflow sync also ignores a legacy non-object value. test_create_email_template_with_subject_succeeds now sends the editor's from: '' shape.
| def validate(self, data: dict[str, Any]) -> dict[str, Any]: | ||
| if not Integration.objects.filter( | ||
| team_id=self.context["team_id"], kind="email", id=data["integrationId"] |
There was a problem hiding this comment.
P2 · Validate missing integration IDs safely on PATCH
A PATCH containing content.email.from: {} or a sender with only name raises KeyError('integrationId') here. DRF propagates partial=True into nested serializers, so the required integer field does not reject its omission before validate runs. The endpoint returns 500 instead of a validation error; explicitly reject a missing integration ID.
There was a problem hiding this comment.
Fixed in dde6371. The sender serializer now checks for a missing integrationId itself and returns a 400, "Choose an email sender." A new sender_without_an_id case is in test_rejects_an_update_that_would_stop_a_transactional_template_sending.
| sender_id = resolve_transactional_email_sender(team_id, from_integration_id or sender.get("integrationId")) | ||
| template.content = {**template.content, "email": {**email, "from": {**sender, "integrationId": sender_id}}} |
There was a problem hiding this comment.
P2 · Merge the resolved sender into the editor after conversion
Converting a template now changes its content, but the frontend makeTransactional listener merges only transactional_hog_flow_id and updated_at from the response. The editor therefore shows “Choose sender” despite the configured sender, and editing its name produces a sender without an integration ID. Merge the returned sender into form state during conversion.
There was a problem hiding this comment.
Fixed in dde6371. The makeTransactional listener merges the returned sender into both the baseline and the form, with or without an edit made during conversion. The conversion test is now a test.each over both paths.
| from={from} | ||
| replyTo={emailTemplate.replyTo} | ||
| senders={senders} | ||
| onChangeFrom={(next) => setEmailTemplateValue('from', next)} |
There was a problem hiding this comment.
P2 · Carry custom sender overrides into test emails
After setting a custom name or address here, clicking “Send test” sends neither override. messageTemplateTestSendLogic.sendTestEmail spreads the template content, then replaces from with only the test integration ID, so the received message uses the integration's default identity. Preserve the template's name and address when constructing the test sender.
There was a problem hiding this comment.
Fixed in dde6371. A test send defaults to the template's own sender and keeps its custom name and address. The send test now sets a non-default sender with overrides and asserts all three reach the email step.
| fields["from"] = EmailTemplateSenderSerializer( | ||
| required=False, |
There was a problem hiding this comment.
P1 · Normalize legacy senders when serializing stored templates
For a stored legacy content.email.from: "", list/retrieve raises AttributeError when EmailTemplateSenderSerializer reads its required integrationId. to_internal_value only normalizes incoming writes, so one existing row breaks the library listing even with transactional emails disabled. Normalize legacy senders during representation or migrate them.
There was a problem hiding this comment.
| def to_internal_value(self, data: Any) -> Any: | ||
| # New templates and templates saved before senders existed hold an empty string, which means no sender. | ||
| if isinstance(data, dict) and data.get("from") in ("", None): | ||
| data = {key: value for key, value in data.items() if key != "from"} | ||
| return super().to_internal_value(data) |
There was a problem hiding this comment.
P2 · Preserve saving templates from destination email editors
When a Mailgun destination uses “Save as new template”, saveAsTemplate forwards its nonempty string from unchanged. This normalization handles only "" and None, so the new nested serializer returns 400 and breaks the existing template-saving flow. Strip destination sender strings when constructing library content, or accept them here.
There was a problem hiding this comment.
Fixed in #121871. A write treats any string from as no sender, so a destination's "Save as new template" saves again. test_create_email_template_with_subject_succeeds now covers the editor's blank value and a destination sender string.
|
|
||
|
|
||
| # Matches the send path, which ignores a custom address that fails these checks and sends from the sender's own. | ||
| SENDER_ADDRESS_PATTERN = re.compile(r'^[^\s@"<>,;]+@[^\s@"<>,;]+\.[^\s@"<>,;]+$') |
There was a problem hiding this comment.
P2 · Bound sender addresses before running the regex
Publishing from.email containing a@, thousands of dots, and a trailing @ triggers quadratic backtracking in SENDER_ADDRESS_PATTERN. The new CharField has no length limit: 16,000 dots take about 0.7 seconds locally, and larger inputs occupy a request worker for much longer. Bound the address length before matching or use linear-time validation.
There was a problem hiding this comment.
Fixed in #121871. The address field is capped at 254 characters, and the check returns before the regex for anything longer, in the API and the editor alike. A new custom_address_too_long case covers it.
| sender_id = resolve_transactional_email_sender(team_id, from_integration_id or sender.get("integrationId")) | ||
| template.content = {**template.content, "email": {**email, "from": {**sender, "integrationId": sender_id}}} | ||
| hog_flow_id = create_transactional_email_workflow( | ||
| team_id=team_id, user_id=user_id, content=_content(template, from_integration_id) | ||
| team_id=team_id, user_id=user_id, content=_content(template) |
There was a problem hiding this comment.
P2 · Validate the custom address against the resolved conversion sender
When a template has a custom address on sender A's domain, calling make_transactional with sender B's from_integration_id retains A's address while replacing its integration. Conversion bypasses sender_address_error, so it succeeds and sends silently use B's default address. Validate the resulting sender/address pair before creating the workflow.
There was a problem hiding this comment.
Fixed in #121871. Conversion now checks the custom address against the sender it resolved and rejects it with template_not_sendable when the domains differ. A new case is in test_rejects_making_a_template_transactional_it_cannot_send.
dde6371 to
05fd57c
Compare
- The transactional template editor shows the sender as one summary line, with an Edit sender popover for sender, display name, address and reply-to. - The address field takes the part before the @, and the sender's verified domain is fixed after it. - An invalid address shows inline and blocks Publish. - Making a template transactional shows the stored sender, and test sends keep the template's sender, name and address. - Applying a transactional template inside a workflow email step keeps the step's own sender and reply-to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-By: PostHog Desktop Task-Id: 90d9a51d-beba-47ba-8f50-b351327676f9
05fd57c to
877fa19
Compare
|
Risk: No findings This PR adds the frontend editor for a transactional email template's sender (from-name, address, reply-to) — a sender summary line with an edit popover, client-side address validation mirroring the server's checks, sender carry-over on template apply, and sender defaults for test sends. It is frontend-only; the storing and validating API is the stacked base PR already in pr-base. The main risk considered was a client/server validation differential, but the frontend regex, length bound, templating escape, and domain derivation exactly mirror the server's Sentinel reviewed |
There was a problem hiding this comment.
Approved.
Risky sender configuration changes have owning-team and STRONG-familiarity assurance, with targeted regression tests. The current code addresses the substantive review concerns, including the remaining unmarked thread; neither historical reviews nor the bot's scanning announcement constitute a current-head review.
- Author wrote 83% of the modified lines and has 4 merged PRs in these paths (familiarity STRONG).
Gate mechanics and policy version
| Gate | Result | |
|---|---|---|
| prerequisites | ✓ | all clear |
| deny-list | ✓ | no deny categories matched |
| size | ✓ | 468L, 8F substantive, 576L/12F incl. docs/generated/snapshots — within ceiling |
| tier | ✓ | T1-agent / T1d-complex (576L, 12F, two-areas, feat) |
| stamphog 2.6.0 | .stamphog/policy.yml @ 877fa19 · reviewed head 877fa19 |
|
/trunk merge |
Problem
This is the editor half. The API that stores and checks the sender is #121871, the base of this stack.
Changes
Demo (local stack, narrated): transactional sender demo
The received email (local MailDev)
How did you test this code?
Manual, on a local stack with two verified MailDev senders:
From: Hedgebox Security Team <security-team@posthog.com>andReply-To: security@posthog.com.Test rationale:
emailSenderFields.test.tstables cover the address check, including the 254-character bound, and the local-part split and join. They are pure functions, so the cases stay cheap.emailTemplaterLogic.test.ts: applying a transactional template keeps the step's own sender and reply-to.messageTemplateLogic.test.ts: conversion shows the stored sender, with or without an edit made meanwhile.messageTemplateTestSendLogic.test.ts: a test send uses the template's sender, name and address.Not checked:
Release status
Transactional templates sit behind
workflows-transactional-email-api.Docs update
None. No existing doc covers transactional template settings.
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Agent: PostHog Desktop with Claude Code, Claude Opus 5.5 (
claude-opus-5-5)improving-drf-endpoints,writing-ui-components,writing-tests,writing-user-facing-copy,recording-demo-videos,stacking-prs,writing-pr-descriptions.Created with PostHog Desktop
🤖 Generated with Claude Code