feat(workflows): surgical email patching for workflow email steps - #76015
Conversation
PATCH /hog_flows/:id/actions/:action_id/email applies the email template library's id-addressed design operations to the email embedded in a function_email step, plus an email_patch deep merge for subject/preheader/ text/recipients. HTML is re-rendered server-side from the patched design so the sent content can't go stale. Rides the graph endpoint's draft routing, staleness guard, and revision machinery. Exposed as the workflows-patch-action-email MCP tool.
|
😎 Merged successfully - details. |
🦔 Hogbox preview · ❌ build failedThe preview didn't come up for commit Previews are optional and never block merging. A failure here is often a hogland or tailnet hiccup rather than anything in your PR, so the check stays green and this comment is the status. |
🤖 CI report✅ Bundle size — no changeUncompressed size of every built Total: 65.53 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.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.3 KiB | src/RootErrorBoundary.tsx |
| 912 B | ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js |
| 789 B | src/scenes/ChunkLoadErrorBoundary.tsx |
| 762 B | src/index.tsx |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 285.5 KiB | ../node_modules/.pnpm/posthog-js@1.410.1/node_modules/posthog-js/dist/rrweb.js |
| 267.7 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 |
| 235.5 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 231.5 KiB | ../node_modules/.pnpm/posthog-js@1.410.1/node_modules/posthog-js/dist/module.js |
| 154.3 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 |
| 105.3 KiB | src/lib/api.ts |
| 94.7 KiB | ../packages/quill/packages/quill/dist/index.js |
| 93.3 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
| 90.6 KiB | ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/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.19 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.19 MiB · 17 files | no change | ████░░░░░░ 38.3% of 5.72 MiB |
| Deferred (lazy) | 2.08 MiB · 33 files | no change | n/a — loads on demand |
Loader dist/toolbar.js |
1.1 KiB | no change | █░░░░░░░░░ 5.8% of 19.5 KiB |
Largest eagerly-shipped chunks
| Size | File |
|---|---|
| 718.3 KiB | dist/toolbar/toolbar-app-FT4UJ3IP.css |
| 551.5 KiB | dist/toolbar/chunk-chunk-W7B762O4.js |
| 484.6 KiB | dist/toolbar/chunk-chunk-T44C2V5C.js |
| 133.6 KiB | dist/toolbar/chunk-chunk-ZYWF76EY.js |
| 131.8 KiB | dist/toolbar/chunk-chunk-T5KY5WYR.js |
| 71.0 KiB | dist/toolbar/toolbar-app-NOHXZCKZ.js |
| 69.0 KiB | dist/toolbar/chunk-chunk-27JL52RE.js |
| 35.6 KiB | dist/toolbar/chunk-chunk-3XKYMPJQ.js |
| 20.9 KiB | dist/toolbar/chunk-chunk-3GU2JABZ.js |
| 12.2 KiB | dist/toolbar/chunk-chunk-PIK3PADE.js |
Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile
✅ Dist folder size — 🔺 +806 B (+0.0%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 1389.05 MiB · 🔺 +806 B (+0.0%)
ℹ️ MCP UI apps size — 32 app(s), 17070.3 KB JS
Built size of each MCP UI app (main.js + styles.css).
| App | JS | CSS |
|---|---|---|
| debug | 599.6 KB | 187.7 KB |
| action | 457.8 KB | 187.7 KB |
| action-list | 564.4 KB | 187.7 KB |
| cohort | 456.8 KB | 187.7 KB |
| cohort-list | 563.4 KB | 187.7 KB |
| email-template | 456.6 KB | 187.7 KB |
| error-details | 472.4 KB | 187.7 KB |
| error-issue | 457.5 KB | 187.7 KB |
| error-issue-list | 564.3 KB | 187.7 KB |
| experiment | 561.5 KB | 187.7 KB |
| experiment-list | 565.2 KB | 187.7 KB |
| experiment-results | 563.2 KB | 187.7 KB |
| feature-flag | 567.2 KB | 187.7 KB |
| feature-flag-list | 570.9 KB | 187.7 KB |
| feature-flag-testing | 461.0 KB | 187.7 KB |
| insight-actors | 562.2 KB | 187.7 KB |
| invite-email-preview | 456.0 KB | 187.7 KB |
| llm-costs | 559.5 KB | 187.7 KB |
| session-recording | 458.6 KB | 187.7 KB |
| session-summary | 463.9 KB | 187.7 KB |
| survey | 458.4 KB | 187.7 KB |
| survey-global-stats | 562.2 KB | 187.7 KB |
| survey-list | 565.1 KB | 187.7 KB |
| survey-stats | 562.2 KB | 187.7 KB |
| trace-span | 457.2 KB | 187.7 KB |
| trace-span-list | 564.3 KB | 187.7 KB |
| workflow | 457.1 KB | 187.7 KB |
| workflow-list | 563.7 KB | 187.7 KB |
| loops-review | 461.2 KB | 187.7 KB |
| query-results | 747.4 KB | 187.7 KB |
| render-ui | 828.0 KB | 187.7 KB |
| visual-review-snapshots | 461.6 KB | 187.7 KB |
⚠️ Backend snapshots — 8 updated (8 modified, 0 added, 0 deleted)
Query snapshots: Backend query snapshots updated
Changes: 8 snapshots (8 modified, 0 added, 0 deleted)
What this means:
- Query snapshots have been automatically updated to match current output
- These changes reflect modifications to database queries or schema
Next steps:
- Review the query changes to ensure they're intentional
- If unexpected, investigate what caused the query to change
⚠️ Backend coverage — 98.0% of changed backend lines covered — 6 uncovered
🧪 Backend test coverage
Patch coverage — changed backend lines (products + core): ████████████████████ 98.0% (309 / 315)
| File | Patch | Uncovered changed lines |
|---|---|---|
products/workflows/backend/api/hog_flow.py |
94.8% | 2045, 2075, 2110, 2114, 3009, 3017 |
🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 30821248243 -n patch-coverage), or the coverage-data block at the end of this comment.
Per-product line coverage (touched products)
| Product | Coverage | Lines |
|---|---|---|
platform_features |
██░░░░░░░░░░░░░░░░░░ 12.1% |
7 / 58 |
batch_exports |
████████░░░░░░░░░░░░ 39.5% |
8,806 / 22,287 |
demo |
███████████░░░░░░░░░ 56.3% |
1,497 / 2,661 |
warehouse_sources_queue |
████████████░░░░░░░░ 59.2% |
148 / 250 |
data_tools |
██████████████░░░░░░ 70.0% |
63 / 90 |
tasks |
██████████████░░░░░░ 70.4% |
33,513 / 47,611 |
ai_gateway |
███████████████░░░░░ 75.0% |
9 / 12 |
signals |
████████████████░░░░ 81.5% |
25,463 / 31,226 |
cdp |
████████████████░░░░ 82.1% |
3,285 / 3,999 |
data_modeling |
█████████████████░░░ 85.7% |
7,749 / 9,047 |
notebooks |
█████████████████░░░ 86.0% |
7,794 / 9,060 |
managed_warehouse |
█████████████████░░░ 86.2% |
5,471 / 6,347 |
actions |
█████████████████░░░ 86.6% |
717 / 828 |
wizard |
█████████████████░░░ 86.7% |
1,090 / 1,257 |
data_warehouse |
█████████████████░░░ 87.4% |
10,967 / 12,547 |
cohorts |
█████████████████░░░ 87.5% |
6,393 / 7,309 |
exports |
██████████████████░░ 87.9% |
7,078 / 8,054 |
product_tours |
██████████████████░░ 87.9% |
1,303 / 1,482 |
business_knowledge |
██████████████████░░ 89.0% |
4,384 / 4,928 |
engineering_analytics |
██████████████████░░ 89.3% |
6,529 / 7,309 |
dashboards |
██████████████████░░ 89.5% |
5,989 / 6,693 |
visual_review |
██████████████████░░ 89.5% |
5,870 / 6,558 |
conversations |
██████████████████░░ 90.0% |
18,270 / 20,301 |
alerts |
██████████████████░░ 90.3% |
4,482 / 4,966 |
links |
██████████████████░░ 90.6% |
183 / 202 |
streamlit_apps |
██████████████████░░ 90.7% |
2,630 / 2,901 |
error_tracking |
██████████████████░░ 91.0% |
10,938 / 12,022 |
slack_app |
██████████████████░░ 91.1% |
9,718 / 10,664 |
marketing_analytics |
██████████████████░░ 91.2% |
12,111 / 13,284 |
stamphog |
██████████████████░░ 91.3% |
4,505 / 4,936 |
mcp_store |
██████████████████░░ 92.2% |
6,525 / 7,076 |
product_analytics |
███████████████████░ 92.5% |
5,849 / 6,321 |
managed_migrations |
███████████████████░ 92.6% |
1,556 / 1,681 |
early_access_features |
███████████████████░ 92.6% |
1,287 / 1,390 |
mcp_analytics |
███████████████████░ 92.6% |
3,775 / 4,076 |
notifications |
███████████████████░ 92.6% |
1,017 / 1,098 |
ai_observability |
███████████████████░ 92.8% |
15,618 / 16,821 |
surveys |
███████████████████░ 93.2% |
5,853 / 6,281 |
posthog_ai |
███████████████████░ 93.2% |
1,326 / 1,422 |
approvals |
███████████████████░ 93.3% |
3,437 / 3,682 |
reminders |
███████████████████░ 93.4% |
468 / 501 |
web_analytics |
███████████████████░ 93.5% |
15,375 / 16,447 |
legal_documents |
███████████████████░ 93.8% |
1,628 / 1,736 |
endpoints |
███████████████████░ 94.2% |
8,655 / 9,192 |
workflows |
███████████████████░ 94.2% |
7,559 / 8,021 |
tracing |
███████████████████░ 94.5% |
2,671 / 2,827 |
review_hog |
███████████████████░ 94.6% |
8,246 / 8,715 |
skills |
███████████████████░ 94.6% |
3,158 / 3,337 |
messaging |
███████████████████░ 94.7% |
2,885 / 3,048 |
experiments |
███████████████████░ 95.5% |
26,047 / 27,287 |
logs |
███████████████████░ 95.5% |
10,491 / 10,990 |
growth |
███████████████████░ 96.1% |
3,245 / 3,376 |
annotations |
███████████████████░ 96.2% |
732 / 761 |
revenue_analytics |
███████████████████░ 96.3% |
1,887 / 1,960 |
feature_flags |
███████████████████░ 96.4% |
17,488 / 18,144 |
replay_vision |
███████████████████░ 96.4% |
16,612 / 17,226 |
user_interviews |
███████████████████░ 96.5% |
2,638 / 2,734 |
access_control |
███████████████████░ 96.9% |
870 / 898 |
customer_analytics |
███████████████████░ 97.1% |
10,463 / 10,777 |
warehouse_sources |
███████████████████░ 97.3% |
360,281 / 370,268 |
data_catalog |
████████████████████ 97.7% |
2,588 / 2,648 |
analytics_platform |
████████████████████ 98.0% |
2,153 / 2,197 |
metrics |
████████████████████ 98.2% |
2,491 / 2,536 |
pulse |
████████████████████ 98.4% |
2,017 / 2,049 |
live_debugger |
████████████████████ 99.2% |
613 / 618 |
field_notes |
████████████████████ 99.4% |
158 / 159 |
Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.
|
Reviews (1): Last reviewed commit: "fix(workflows): satisfy mypy strictness ..." | Re-trigger Greptile |
🦔 ReviewHog reviewed this pull requestFound 1 must fix, 1 should fix, 0 consider. Published 2 findings (view the review). |
|
ReviewHog Alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏 |
There was a problem hiding this comment.
ReviewHog Report
Feature
Issues: 2 issues
Files (4)
products/workflows/backend/api/hog_flow.pyproducts/workflows/frontend/generated/api.schemas.tsproducts/workflows/frontend/generated/api.tsproducts/workflows/frontend/generated/api.zod.ts
What were the main changes
- New PATCH /api/projects/:id/hog_flows/:id/actions/:action_id/email endpoint (action_email) for surgical email edits on a function_email workflow step
- HogFlowActionEmailUpdateSerializer validates operations (design ops) and email_patch (deep-merge, blocking design/html)
- _apply_action_email_edit applies design operations via apply_design_operations, re-renders html via render_design_html, then deep-merges plain email fields with _deep_merge
- Reuses /graph endpoint machinery: MCP-on-active routes to draft (gated by workflows-revisions flag), optimistic-concurrency base_updated_at staleness guard (409), select_for_update row locking, draft composition and revision bumps
- Auto-generated frontend API client/types (api.schemas.ts, api.ts, api.zod.ts) regenerated for the new endpoint
Review fixes on the action-email endpoint: - Declare the products.messaging dependency in tach.toml for the new design-operation imports (fixes the module-boundary CI failure). - Hoist apply/validate/render out of the select_for_update transaction: render_design_html is a synchronous Unlayer HTTP call (up to 30s) and must not extend the row-lock hold. The in-lock apply installs the pre-rendered design only while the stored design still matches the one the ops were applied to, and 409s otherwise, so a concurrent edit can't be clobbered even when the client omits base_updated_at. Two new tests cover the conflict and consecutive drafted design edits. - Regenerate services/mcp/schema/tool-inputs.json (fixes the MCP schema staleness CI failure). Raised by ReviewHog on #76015. Generated-By: PostHog Code Task-Id: d83f5523-557d-445d-b6ce-c1414e9437a9
The pre-lock draft-routing prediction ignored revisions_enabled, so an MCP edit to an active flow with the flag off ran the up-to-30s Unlayer render and was then rejected under the row lock. Folding the flag into the prediction wouldn't help (the request is doomed either way), so reject before the render instead; the locked re-check stays authoritative if the status flips in between. The flag-off test now sends design operations and asserts the render is never called. Raised by Graphite on #76015. Generated-By: PostHog Code Task-Id: d83f5523-557d-445d-b6ce-c1414e9437a9
|
/trunk merge |
Problem
The only way to change the email on a workflow
function_emailstep was to rewrite the entireconfig.inputs.email.valueblob through a graphupdate_action. For an agent editing an email mid-workflow that is slow and token-hungry: it has to read the whole email (design JSON + rendered html can be huge), regenerate the full object just to tweak a subject line or one design block, and send it all back. Small edit, big round-trip. As a bonus it also removes a footgun where the renderedhtmlcould drift out of sync with thedesignit was built from.Changes
Adds a surgical patch endpoint for the email on a single workflow step, so an agent can send just the delta instead of the whole email:
PATCH /api/projects/:id/hog_flows/:id/actions/:action_id/emailoperations: the same id-addressed design operations used by the email template library, applied atomically to the step's emaildesign. Thehtmlis re-rendered server-side from the patched design (via Unlayer), so design and sent content can't diverge.email_patch: a deep merge for the plain fields (subject,preheader,text,to,from,replyTo,cc,bcc); a null leaf deletes the key.designandhtmlare rejected here - the design is edited viaoperationsand html is always derived./graphendpoint machinery: MCP-on-active edits route to a draft (gated by the workflows-revisions flag), an optimistic-concurrency staleness guard (409 on stalebase_updated_at), draft composition, and revision bumps. Row-locked withselect_for_updateinside the transaction.Reuses existing utilities rather than adding new ones:
apply_design_operations,validate_design,render_design_html,DesignOperationSerializerfrom the messaging backend, and_deep_mergefrom workflows graph operations.Exposed as the
workflows-patch-action-emailMCP tool, with regenerated frontend API client and MCP tool schemas. Docs touched:building-workflowsanddesigning-email-templatesskills.How did you test this code?
15 new backend tests in
test_hog_flow_action_email.py, all passing (15 passed in 50.30s), plusmypyclean andruffclean. Coverage includes regressions no existing test caught:design/html) rejected inemail_patch; empty patch rejectedaction_idand non-function_emailstep rejectedbase_updated_atreturns 409Also manually tested end to end through the real
workflows-patch-action-emailMCP tool against a local dev workflow: a designupdate_contentop plus anemail_patchsubject change both applied correctly, the HTML was re-rendered via a live Unlayer call, the workflow's revision bumped, and theemail_patchguard rejectingdesign/htmlfired as expected.Automatic notifications
🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Authored across two agent sessions in PostHog Code (Claude). The first session built the feature and tests, ran mypy/ruff/pytest to green, then hit its usage limit right before opening the PR. A second session picked up the finished, committed-but-unpushed branch, re-ran the targeted suite (15 passed), pushed, and opened this PR - no code changes were made in the second session.
Skills invoked while shaping the work:
building-workflows,designing-email-templates.Key decisions: modelled the endpoint as a twin of the existing
/graphupdate path so it inherits draft routing, the staleness guard, and revision bumps rather than reinventing them; kept html strictly derived from design (never client-settable) so sent content can't go stale; forbadedesign/htmlinemail_patchto keep a single source of truth.Created with PostHog Code