Skip to content

feat(workflows): add utm tags to email links - #110697

Draft
dmarchuk wants to merge 5 commits into
masterfrom
posthog/email-utm-tags
Draft

dmarchuk wants to merge 5 commits into
masterfrom
posthog/email-utm-tags

Conversation

@dmarchuk

@dmarchuk dmarchuk commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Changes

  • The email step gets an Add UTM tags to links switch, in the workflow editor and on the broadcast Content step. It is off by default.

  • With the switch on, each link in the sent email gets four tags. Each tag has a field, and an empty field uses the default:

    Tag Default
    utm_source posthog
    utm_medium email
    utm_campaign workflow or broadcast name
    utm_content email step name
  • Fields accept Liquid with the recipient's data, for example {{ person.properties.plan }}.

    • A value that renders empty uses the default.
    • A value with a Liquid error uses the default and logs a warning on the run. The email still sends.
  • The fields show an example link with the current values.

  • Links that stay as written:

    • A tag already on a link is kept. Only the missing tags are added.
    • Links to PostHog itself (unsubscribe and preference pages), mailto:, relative links and anchors.
    • A link with a data-ph-no-utm attribute.
  • The tags go before any #fragment. The rest of the URL stays as written.

  • The tags are added at send time, before click tracking wraps the links, so they reach the destination page through the click redirect.

  • The broadcast Review step says when links get tags. A broadcast test email uses the same values, so the test shows the links the audience gets.

  • Mechanical: the step config keys utm_tags_enabled and utm_params in the nodejs and frontend schemas, and the flow and step names passed to the send code.

Broadcast content Broadcast review Workflow email step
utm-on-content utm-on-review utm-workflow-switch

Note

Open question for review: should new broadcasts have the switch on by default? A team-level default belongs in a follow-up, because it needs a settings migration.

How did you test this code?

Local stack, driven in Playwright. Django and a cdp-api Node process ran from this branch. The sender was the local maildev integration, and each email was read back from the maildev API. The broadcast was named Spring sale 🎉 Zoë.

Wizard settings, then Send test email Link target in the email (inside the click-tracking redirect)
Switch on, no custom values …/pricing?utm_source=posthog&utm_medium=email&utm_campaign=Spring%20sale%20%F0%9F%8E%89%20Zo%C3%AB…&utm_content=Send%20email
utm_source = newsletter, utm_campaign = {{ person.properties.email }} …/pricing?utm_source=newsletter&utm_medium=email&utm_campaign=success%40simulator.amazonses.com&utm_content=Send%20email
utm_campaign = {{ person.properties.not_a_property }} utm_campaign falls back to the broadcast name
utm_campaign = {{ person.properties.email (broken) The email sends with the broadcast name. Run log: "Used the default utm_campaign because its value has an error: output … not closed"
Switch off Every link unchanged

Per link, in the switched-on emails:

Link in the email Result
https://example.com/p?a=1&b=2#plans Tags go before #plans, a and b kept
https://example.com/?utm_campaign=launch utm_campaign=launch kept, the other tags added
{{ unsubscribe_url }}, mailto:, <a data-ph-no-utm href="https://example.com/partner"> Unchanged
  • Postgres after Continue: the email step config holds utm_tags_enabled: true and utm_params: {"utm_source": "newsletter", "utm_campaign": "{{ person.properties.plan }}"}.

  • Workflow editor: switch on, utm_campaign and utm_content filled in, Save. Postgres holds the same two keys under utm_params.

  • Test rationale:

    • helpers/utm.test.ts covers the link rules and how custom values replace defaults.
    • Cases in the tracking_enabled block of email.service.test.ts cover the send path: defaults from the flow and step names, rendered custom values, and the fallback for a Liquid error.
    • broadcastWorkflowEdits.test.ts checks that a broadcast saves the switch and the values onto the email step.
  • Real launches through the queue worker and SES: four broadcasts launched from the wizard to one person, with this branch's Node running in place of the local CDP worker. Every batch job completed and each run logged "Email sent" through the SES sender.

    Launch Email step config in Postgres Run log
    Switch on, defaults utm_tags_enabled: true, utm_params: {} "Email sent"
    Custom: newsletter, {{ person.properties.plan }} both values saved "Email sent"
    Broken: {{ person.properties.plan value saved as typed "Used the default utm_campaign because its value has an error: output … not closed", then "Email sent"
    Switch off utm_tags_enabled: false "Email sent"
  • Edge cases in a maildev test send: utm_campaign = {{ "Starter & Premium" }} arrives as utm_campaign=Starter%20%26%20Premium, not HTML-escaped. A link with data-href before href is tagged. A link whose title mentions data-ph-no-utm is tagged. A link with the real data-ph-no-utm attribute is unchanged.

  • SES delivery to a real inbox: a wizard test email went through SES from a tracia.io sender to a Gmail inbox, with utm_source = newsletter and utm_campaign = {{ person.properties.email }}. In the delivered HTML, SES click tracking wraps each link, and the wrapped targets are:

    • https://example.com/pricing?utm_source=newsletter&utm_medium=email&utm_campaign=success%40simulator.amazonses.com&utm_content=Send%20email
    • https://example.com/p?a=1&b=2&utm_source=newsletter&…#plans
    • https://example.com/?utm_campaign=launch&utm_source=newsletter&utm_medium=email&utm_content=Send%20email
    • the unsubscribe link, mailto: and the data-ph-no-utm link, unchanged

Release status

  • No feature flag controls this change
  • This change is behind a feature flag and is not available to users
  • This change makes a previously flagged feature available to everyone

Automatic notifications

  • Publish to changelog?

Docs update

None yet. The workflow email docs can describe the switch once the default is settled.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Agent: Claude Code, Claude Opus 5.5

  • Skills: /writing-ui-components, /writing-user-facing-copy, /writing-tests, /writing-pr-descriptions.
  • Link rewriting is regex based, like the existing click tracking, because nodejs has no HTML parser dependency.
  • The custom values render in the send code rather than as a step input, so a Liquid error falls back to the default instead of failing the send.
  • CodeRabbit local pass skipped: the CLI is signed out.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BR6LoqsDMB9Wfnftcy2KiG

@dmarchuk dmarchuk self-assigned this Oct 2, 2026
@trunk-io

trunk-io Bot commented Oct 2, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

✅ Trunk lane — non-backend lane

This PR is assigned to the non-backend lane. It does not run backend Python tests and may merge in parallel with PRs in other lanes.

⚠️ Complexity (TypeScript) — 14 functions above the limit (max 39)

Cyclomatic 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.

Function Location Complexity Limit
<anonymous> products/workflows/frontend/Workflows/workflowLogic.ts:3643 39 10
onDrop products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx:2731 36 10
runSave products/workflows/frontend/Workflows/workflowLogic.ts:3204 35 10
HogFlowEditorPanelBuildDetail products/workflows/frontend/Workflows/hogflows/panel/HogFlowEditorPanelBuildDetail.tsx:41 33 10
saveWorkflowSuccess products/workflows/frontend/Workflows/workflowLogic.ts:4139 31 10
launchBroadcast products/workflows/frontend/Broadcasts/broadcastWizardLogic.ts:1403 24 10
<anonymous> products/workflows/frontend/Broadcasts/broadcastWizardLogic.ts:942 17 10
BroadcastReviewStep products/workflows/frontend/Broadcasts/steps/BroadcastReviewStep.tsx:61 17 10
getBranchLabel products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx:78 14 10
fitView products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx:2899 13 10
setEmail products/workflows/frontend/Broadcasts/broadcastWizardLogic.ts:1189 12 10
saveName products/workflows/frontend/Broadcasts/broadcastWizardLogic.ts:1245 11 10
moveToDraft products/workflows/frontend/Broadcasts/broadcastWizardLogic.ts:1562 11 10
BroadcastContentStep products/workflows/frontend/Broadcasts/steps/BroadcastContentStep.tsx:17 11 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) — 13 new duplicated blocks (worst 3357 tokens)

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.

First copy Second copy Lines Tokens
products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx:233 products/workflows/frontend/Workflows/hogflows/hogFlowEditorLogic.tsx:1098 863 3357
products/workflows/frontend/Workflows/workflowLogic.ts:338 products/workflows/frontend/Workflows/workflowLogic.ts:1203 863 3357
products/workflows/frontend/Workflows/hogflows/steps/stepWaitUntilTimeWindowLogic.ts:404 products/workflows/frontend/Workflows/workflowLogic.ts:2076 308 1123
products/workflows/frontend/Workflows/hogflows/steps/stepDelayLogic.ts:478 products/workflows/frontend/Workflows/workflowLogic.ts:2468 306 1119
products/workflows/frontend/Workflows/hogflows/steps/stepWaitUntilTimeWindowLogic.ts:101 products/workflows/frontend/Workflows/hogflows/steps/stepWaitUntilTimeWindowLogic.ts:407 304 1115
products/workflows/frontend/Workflows/hogflows/steps/stepDelayLogic.ts:176 products/workflows/frontend/Workflows/hogflows/steps/stepWaitUntilTimeWindowLogic.ts:102 302 1110
products/workflows/frontend/Workflows/workflowMetricsSummaryLogic.ts:448 products/workflows/frontend/Workflows/workflowMetricsSummaryLogic.ts:824 58 259
products/workflows/frontend/Workflows/workflowMetricsSummaryLogic.ts:448 products/workflows/frontend/Workflows/workflowMetricsSummaryLogic.ts:682 58 257
products/workflows/frontend/Workflows/workflowLogic.ts:579 products/workflows/frontend/Workflows/workflowMetricsSummaryLogic.ts:448 58 246
nodejs/src/cdp/schema/hogflow.ts:223 products/workflows/frontend/Workflows/hogflows/steps/types.ts:358 28 183
products/workflows/frontend/Workflows/workflowLogic.ts:604 products/workflows/frontend/Workflows/workflowLogic.ts:973 31 136
products/workflows/frontend/Workflows/hogflows/steps/stepDelayLogic.ts:450 products/workflows/frontend/Workflows/hogflows/steps/stepDelayLogic.ts:755 28 109
products/workflows/frontend/Workflows/hogflows/steps/stepDelayLogic.ts:452 products/workflows/frontend/Workflows/workflowLogic.ts:580 26 105
✅ Comment density — 2% of added code lines are comments (11 of 559)

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
nodejs/src/cdp/services/messaging/helpers/utm.ts 3 116
nodejs/src/cdp/schema/hogflow.ts 2 11
products/workflows/frontend/Broadcasts/broadcastTestSendLogic.ts 2 13
products/workflows/frontend/Workflows/hogflows/steps/types.ts 2 11
nodejs/src/cdp/services/messaging/email.service.ts 1 30
products/workflows/frontend/Workflows/hogflows/steps/components/UtmTagFields.tsx 1 48

This check does not block merging. It updates on every push and clears when the share drops.

⚠️ Bundle size — 🔺 +227.0 KiB (+0.3%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 69.79 MiB · 🔺 +227.0 KiB (+0.3%)

File Size Δ vs base
exporter/src/exporter/scenes/ExporterNotebookScene.js 3.90 MiB 🔺 +68.1 KiB (+1.7%)
posthog-app/src/scenes/project-homepage/ProjectHomepage.js 1.6 KiB 🟢 -57.4 KiB (-97.2%)
posthog-app/src/scenes/project-homepage/ai-first/AiFirstHomepage.js 43.4 KiB 🔺 +43.4 KiB (new)
posthog-app/src/scenes/AuthenticatedShell.js 268.1 KiB 🟢 -35.6 KiB (-11.7%)
posthog-app/src/layout/today/TodaySpacesPane.js 31.9 KiB 🔺 +31.9 KiB (new)
posthog-app/_parent/products/ai_observability/frontend/scoreDefinitions/AIObservabilityScorerScene.js 21.6 KiB 🔺 +21.6 KiB (new)
posthog-app/src/scenes/project-homepage/today/TodayReportPage.js 19.0 KiB 🔺 +19.0 KiB (new)
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/components/ArtifactObjectEmbed.js removed 🟢 -18.7 KiB (-100.0%)
posthog-app/_parent/products/data_modeling/frontend/ModelsScene.js 58.3 KiB 🔺 +18.7 KiB (+47.1%)
posthog-app/_parent/products/canvas/frontend/scene/CanvasScene.js 69.1 KiB 🔺 +14.5 KiB (+26.5%)
posthog-app/_parent/products/tasks/frontend/spaces/SpaceScene.js 71.6 KiB 🟢 -13.7 KiB (-16.0%)
posthog-app/src/scenes/project-homepage/today/TodayHome.js 10.3 KiB 🔺 +10.3 KiB (new)
posthog-app/_parent/products/tasks/frontend/spaces/NewSessionScene.js 9.4 KiB 🔺 +9.4 KiB (new)
render-query/src/render-query/render-query.js 18.76 MiB 🔺 +9.0 KiB (+0.0%)
posthog-app/src/layout/today/TodayViewsSidebar.js 8.5 KiB 🔺 +8.5 KiB (new)
posthog-app/src/layout/today/TodaySessionHoverCard.js 8.3 KiB 🔺 +8.3 KiB (new)
exporter/_parent/products/posthog_ai/frontend/scenes/TaskTracker/components/ArtifactObjectEmbed.js removed 🟢 -8.0 KiB (-100.0%)
posthog-app/_parent/products/ai_observability/frontend/scoreDefinitions/AIObservabilityScorersScene.js 13.6 KiB 🟢 -7.6 KiB (-35.9%)
posthog-app/src/scenes/project-homepage/today/TodayReportHoverCard.js 6.8 KiB 🔺 +6.8 KiB (new)
posthog-app/src/layout/today/TodaySpaceHoverCard.js 6.7 KiB 🔺 +6.7 KiB (new)
posthog-app/_parent/products/canvas/frontend/sidePanel/CanvasSidePanel.js 45.9 KiB 🟢 -5.6 KiB (-10.8%)
posthog-app/_parent/products/tasks/frontend/spaces/NewSpaceDialog.js 5.5 KiB 🔺 +5.5 KiB (new)
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/TaskTracker.js 91.9 KiB 🔺 +4.6 KiB (+5.3%)
exporter/_parent/products/posthog_ai/frontend/scenes/TaskTracker/TaskTracker.js 92.2 KiB 🔺 +4.4 KiB (+5.0%)
posthog-app/src/scenes/notebooks/NotebookScene.js 34.7 KiB 🟢 -3.3 KiB (-8.8%)
posthog-app/_parent/products/error_tracking/frontend/scenes/ErrorTrackingIssueScene/ErrorTrackingIssueScene.js 94.3 KiB 🔺 +2.4 KiB (+2.7%)
posthog-app/src/layout/today/TodayToolsSidebar.js 2.1 KiB 🔺 +2.1 KiB (new)
posthog-app/src/layout/today/TodayChatHoverCard.js 2.0 KiB 🔺 +2.0 KiB (new)
exporter/_parent/products/workflows/frontend/Workflows/Workflow.js 124.7 KiB 🔺 +1.9 KiB (+1.5%)
posthog-app/src/layout/today/TodayLibrarySidebar.js 1.8 KiB 🔺 +1.8 KiB (new)

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

✅ Eager graph — within 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.63 MiB · 22 files 🔺 +12.0 KiB (+0.7%) █████████░ 88.7% of 1.84 MiB
logged-out boot: index + App + bootApp (preloaded by every page, including /login)
src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
3.72 MiB · 661 files 🔺 +144.2 KiB (+3.9%) █████████░ 92.3% of 4.03 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
7.59 MiB · 2,409 files 🟢 -201.9 KiB (-2.5%) █████████░ 91.0% of 8.34 MiB
dashboard scene
src/scenes/dashboard/Dashboard.tsx
9.67 MiB · 3,392 files 🔺 +26.8 KiB (+0.3%) ███████░░░ 71.8% of 13.48 MiB
today home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
7.61 MiB · 2,417 files (no base measurement) █████████░ 88.6% of 8.58 MiB
events scene
src/scenes/activity/explore/EventsScene.tsx
9.29 MiB · 3,244 files 🔺 +26.7 KiB (+0.3%) ███████░░░ 73.5% of 12.64 MiB
replay detail scene
src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
12.12 MiB · 4,130 files 🔺 +47.8 KiB (+0.4%) ████████░░ 77.1% of 15.72 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] 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/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.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

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.3 KiB src/index.tsx
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
854 B src/scenes/ChunkLoadErrorBoundary.tsx
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
220.3 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
100.5 KiB src/lib/api.ts
92.7 KiB src/products.tsx
69.4 KiB src/lib/lemon-ui/icons/icons.tsx
40.1 KiB src/lib/utils/eventUsageLogic.ts
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.0 KiB ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/schemas.js
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
90.6 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/dashboard/Dashboard.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
181.8 KiB src/queries/validators.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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
90.6 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/activity/explore/EventsScene.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
181.8 KiB src/queries/validators.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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
Size File
315.5 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
181.8 KiB src/queries/validators.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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 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.20 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.20 MiB · 19 files 🔺 +1.8 KiB (+0.1%) ████░░░░░░ 38.4% of 5.72 MiB
Deferred (lazy) 2.11 MiB · 44 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
835.5 KiB dist/toolbar/toolbar-app-FSDWO46I.css
657.5 KiB dist/toolbar/chunk-chunk-SLUTN3OK.js
259.4 KiB dist/toolbar/chunk-chunk-7JWMBALG.js
138.2 KiB dist/toolbar/chunk-chunk-JZ43POKQ.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
75.2 KiB dist/toolbar/toolbar-app-LJ7FFVU6.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
35.6 KiB dist/toolbar/chunk-chunk-P52PYKZI.js
21.0 KiB dist/toolbar/chunk-chunk-BO2MEODF.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 — 🔺 +2.65 MiB (+0.3%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 960.40 MiB · 🔺 +2.65 MiB (+0.3%)

@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
🧰 Additional context used
📚 Code guidelines (2)
.agents/security.md — configured
.agents/skills/writing-code-comments/SKILL.md — configured

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 230967c1-08fa-4900-b7cd-bba4f41f8add

📥 Commits

Reviewing files that changed from the base of the PR and between 5d41d26 and a331a7e.

📒 Files selected for processing (1)
  • frontend/snapshots.yml

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 1 remain after this review.


📝 Walkthrough

Walkthrough

Broadcast and workflow email actions now support optional UTM tagging and custom UTM parameters. When enabled, email sending adds missing tags to eligible links, using rendered values where configured and workflow or action names for defaults. Broadcast settings also apply to test sends.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to a331a

Email viewers may omit UTM tags that recipients received, making post-send inspection inaccurate. This is a bounded discrepancy; the PR is otherwise mergeable with owner awareness.

Security Architecture Review

Security architecture risk: 🔵 Low · up to a331a

Tagging is opt-in, encodes link values, and preserves team-scoped sender checks. However, saved copies of sent emails omit the added tags, potentially obscuring recipient data included in delivered links. Configuration authorization and privacy-policy coverage remain unverified.

Retained concerns

  • Low · reliability · inferred: Captured sent-email bodies omit UTM values added to delivered links. When overrides contain recipient properties, reviewing the captured body alone cannot establish which recipient data was included in those links, weakening sent-content verification.
Security review details

Security Blast Radius

  • inferred — An enabled email action can add configured recipient-derived values to every eligible link in each affected message. Recipients receive these values, and following links exposes them to destination services. This can span the workflow or broadcast audience; the inspected path does not demonstrate access to another team's integration.

Trust Boundaries and Controls

  • observed — Only four UTM keys are rendered; values are URL-encoded and HTML attribute delimiters are escaped. Existing keys are preserved. Non-HTTP(S), same-site-host, unresolved-template, and explicitly opted-out links are excluded. These are syntax and eligibility controls, not sensitive-data redaction.
  • observed — The shared sender rejects integrations owned by a different team, performs suppression checks before transformation, and attributes SES sends—including tests—to the invocation's team.

Resilience and Maintainability Implications

  • observed — Rendering errors log a warning and retain defaults without failing delivery. Pre-send pacing delays preserve original queue parameters. Transformation creates a new body without mutating the queued original, and preserving existing UTM keys prevents duplicate additions on repeated transformation.

Hardening Proposals

  • proposed — Provide explicit guidance that recipient-derived UTM values become destination-visible URL data, and discourage secrets or sensitive identifiers in overrides. This is privacy hardening, not an observed unauthorized-disclosure finding.
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description follows the required structure and clearly explains the problem, user-visible changes, testing evidence, test rationale, release status, documentation status, and agent context. It als…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Comment @coderabbitai help to get the list of available commands.

@trunk-io

trunk-io Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

Failed Test Failure Summary Logs
compareTopLevelSections() reports a modifiers change when the current query overrides the team default A TypeError occurred because the code attempted to access the 'add' property of an undefined object. Logs ↗︎

View Full Report ↗︎ ⋅ Docs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟠 Major · Capture the HTML that the provider sends. · email.service.ts:603

nodejs/src/cdp/services/messaging/email.service.ts:603
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Capture the HTML that the provider sends.

When UTM tagging is enabled, the provider receives sendParams, but buildRowForEmail on Line 618 still receives the original params. The captured email asset therefore shows untagged links even though the recipient receives tagged links. Pass sendParams to asset capture so the stored email matches the sent email.

🧹 Nitpick comments (1)
nodejs/src/cdp/services/messaging/helpers/utm.test.ts (1)

71-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert every resolved tag.

toMatchObject lets these cases pass if resolveUtmTags drops utm_medium or an omitted default utm_content. Use toEqual with all four expected tags for each case. As per coding guidelines, assertions should be “as concrete as possible” so broken code cannot pass the test.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: ba3776ec-9d8b-4af7-ad2a-ccb33cd4e3ff

📥 Commits

Reviewing files that changed from the base of the PR and between 45b8a78 and 9a9fadc.

📒 Files selected for processing (16)
  • nodejs/src/cdp/schema/hogflow.ts
  • nodejs/src/cdp/services/hogflows/actions/hog_function.ts
  • nodejs/src/cdp/services/hogflows/hogflow-functions.service.ts
  • nodejs/src/cdp/services/messaging/email.service.test.ts
  • nodejs/src/cdp/services/messaging/email.service.ts
  • nodejs/src/cdp/services/messaging/helpers/utm.test.ts
  • nodejs/src/cdp/services/messaging/helpers/utm.ts
  • products/workflows/frontend/Broadcasts/broadcastTestSendLogic.ts
  • products/workflows/frontend/Broadcasts/broadcastWizardLogic.ts
  • products/workflows/frontend/Broadcasts/broadcastWorkflowEdits.test.ts
  • products/workflows/frontend/Broadcasts/steps/BroadcastContentStep.tsx
  • products/workflows/frontend/Broadcasts/steps/BroadcastReviewStep.tsx
  • products/workflows/frontend/Workflows/hogflows/panel/HogFlowEditorPanelBuildDetail.tsx
  • products/workflows/frontend/Workflows/hogflows/steps/components/UtmTagFields.stories.tsx
  • products/workflows/frontend/Workflows/hogflows/steps/components/UtmTagFields.tsx
  • products/workflows/frontend/Workflows/hogflows/steps/types.ts

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 10 remain after this review.

Comment thread nodejs/src/cdp/services/messaging/helpers/utm.ts Outdated
Comment thread nodejs/src/cdp/services/messaging/helpers/utm.ts Outdated
@posthog

posthog Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

✅ Visual changes approved by @dmarchuk — baseline updated in a331a7e.

View this run in PostHog

4 changed, 2 new.

Install the Visual Review Chrome extension to see visual review results at the top of your pull requests.

6 updated
Run: f0eea788-77f7-425a-ae7c-49860e1fa072

Co-authored-by: dmarchuk <8395106+dmarchuk@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant