Skip to content

feat(workflows): add the smtp relay - #130795

Closed
dmarchuk wants to merge 4 commits into
workflows/smtp-2-attachments-object-storagefrom
workflows/smtp-3-relay
Closed

dmarchuk wants to merge 4 commits into
workflows/smtp-2-attachments-object-storagefrom
workflows/smtp-3-relay

Conversation

@dmarchuk

@dmarchuk dmarchuk commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Changes

A new plugin server mode, cdp-smtp-relay, behind the workflows-smtp-relay flag, takes mail and calls the existing send API with the customer's own key.

flowchart LR
  A[App] -->|SMTP, any port, TLS or STARTTLS| R[SMTP relay]
  R -->|project secret key| D[Send API]
  D --> C[cdp-api] --> W[Email worker] --> S[SES]
  classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff;
  classDef phGray fill:#e5e7eb,stroke:#c7ccd1,color:#000;
  class R phBlue;
  class A,D,C,W,S phGray;
Loading
  • Ports 465, 587, 2465 and 2587. Every port takes both TLS and STARTTLS, so a wrong setting still works.
  • Any username works. Logins need TLS and a key that can send. Only PLAIN and LOGIN are offered.
  • The reply covers the whole message, in one plain line, within 4 seconds (20 for messages over 1 MB). Before: no SMTP path existed.
  • Repeated bad logins with one key from one address get that key locked out, with 454. Other keys from that address still work. An outage on our side answers 454 and locks out nobody.
  • Each pod limits connections per address from the moment they open, and closes any that don't finish TLS in time.
  • Each pod limits the messages and bytes it holds, in total and per project. Over a limit, the client gets a 451 to retry. Large messages go to one recipient at a time, and slow uploads get cut off.
  • TLS 1.2 or newer, with forward-secret ciphers only.
  • Refusals and dropped headers go to a short per-project list for the setup page. Bcc addresses stay out of it, and it takes the relay's own token, not a customer key. The token needs a new secret, SMTP_RELAY_ACTIVITY_JWT_SECRET. Without it the list stays empty.
  • In this mode the plugin server starts without Redis, Postgres or Kafka, because the relay only talks to Django.

How did you test this code?

The output of every run below is in the collapsed block.

  • Real clients over real TLS on the local stack: Nodemailer (including a pool of 150), Django send_mail, smtplib, curl, and Grafana 11 (a Go client, alert test and password reset).
  • Storage: Cyclotron jobs, email_status_events, the local mail sink, and real SES. One message reached a real inbox with DKIM passing.
  • Raw SMTP sessions against the limits. Many large messages at once, a slow send API, stalled handshakes of every kind, and slow uploads all left the relay up and answering other projects. Counters went back to zero, including after dropped connections.
  • TLS 1.0, TLS 1.1 and non-forward-secret ciphers are refused (openssl s_client). Nodemailer, smtplib and Go's net/smtp connect on TLS 1.3 and when capped at TLS 1.2.
  • Started the real mode with no reachable Redis, Postgres or Kafka and no inherited secrets: it reported ready, sent to a stand-in API, and exited cleanly.
  • Refused a message with a Bcc through real Django, then read Redis with LRANGE @posthog/messaging/smtp-activity/1 0 -1: To and Cc only. A customer key on the new route gets 401.
  • Not tested: Supabase, Rails, Laravel, WordPress, Keycloak (the cipher list covers their usual stacks, unconfirmed); bounce and delivery webhooks from SES, because no webhook tunnel was running; 4 listeners behind a real load balancer; a real certificate.
  • Simulated: feature flags were answered from a local file, and a stub answered person lookups, which code sends don't use.
Edge cases and results
Case Result
AUTH before STARTTLS / MAIL before AUTH 538 / 530
Bad key, wrong scope, key revoked mid-session 535 with the reason
Too many failed logins with one key 454, and another key from the same address still logs in
20 logins at once with one new key all accepted, one key check
Flag off 535 SMTP sending is not turned on
Wrong port pairing (Nodemailer, Django) connects
Unverified sender, another team's domain 550
Suppressed recipient 550
3 To + Cc + Bcc 3 emails, one Cc and Bcc copy
Bcc only sent, first address moved to To
51 To, 11 Cc 554
Quoted local part 501 at RCPT from the library (known gap)
Same Message-ID twice one job
Same key, different body 554
960 sends in under a minute with rate limits on 451 after the limit
Django paused 451 at 4.01 s; retry sent once
Django paused, new login 454
cdp-api down 451
Restart mid-session 421 in tests; process exits in under a second
Empty subject and body, HTML only, unicode, dot-stuffing, no Date sent

Note

For the platform team, nothing here is applied: which public ports to open (the report asks for 465, 587, 2465, 2587), whether an internet-facing load balancer is allowed, which certificate issuer works for the host names, and who owns the DNS. The chart's load balancer renders one listener today. Charts draft: linked in the stack's top layer.

Note

Open decisions for Daniel: tracking off by default; one email per To; a beta with attachments up to 10 MB; 250 when only some To addresses are refused. All follow the report's recommendation.

Test rationale: smtp-relay.service.test.ts uses real sockets to catch a port that refuses a mode, a login before TLS, a partial refusal lost as success, a shutdown that hangs, a limit that stops freeing its places, and a Bcc address kept in the list. smtp-message.test.ts catches lost recipients, unstable retry keys and forged headers. test_smtp_relay.py catches a key or flag check dropped from the login.

Release status

  • This change is behind a feature flag and is not available to users

Docs update

Docs land in #130796.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Agent: Claude Code, Opus 5.5 (claude-opus-5-5)

Skills: /improving-drf-endpoints, /verifying-changes, /writing-code-comments, /adding-project-secret-api-key-auth, /instrumenting-first-party-metrics, /writing-tests, /posthog-local-e2e-qa, /writing-pr-descriptions, /writing-plainly

More context for AI agents
  • Decisions: Relay calls the public send API rather than the pipeline, so all checks still apply. Detects TLS per connection on every port.
  • Follow-ups: A published reply table on posthog.com, SES webhook check on a tunnel, provisioning SMTP_RELAY_ACTIVITY_JWT_SECRET for Django and the relay.
  • Read closely: accept(), admitConnection(), handleData() and stop() in smtp-relay.service.ts; reply mapping in smtp-replies.ts; InternalSmtpActivityView.

🤖 Generated with Claude Code

@dmarchuk
dmarchuk added this pull request to stack #130798 November 6, 2026 10:29
@dmarchuk dmarchuk added run-ci-backend Force ci-backend's full test matrices to run even on a draft PR run-ci-frontend Force ci-storybook's full visual-regression matrix to run even on a draft PR labels Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Trunk lane — backend Python lane, mixes lanes

This PR is assigned to the backend Python lane. It runs backend Python tests and may merge in parallel with PRs in other lanes.

This PR changes Python or frontend code (posthog/jwt.py, posthog/settings/data_stores.py, posthog/urls.py and 13 more) and Node or Rust code (nodejs/package.json, nodejs/src/capabilities.ts, nodejs/src/cdp/cdp-api.serial.test.ts and 25 more) together. In the merge queue, every Node or Rust PR behind it in the same lane then runs the Django and frontend suites too, so the cross-lane check fails. Split it, or add the cross-lane-change label if the halves must land together.

✅ Complexity (TypeScript) — clean

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.

⚠️ Duplication (Python) — 3 new duplicated blocks (worst 517 tokens)

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.

First copy Second copy Lines Tokens
products/review_hog/eval/experiments/2026-08-model-glm53-flash/scripts/glm_mts_smoke.py:7 products/review_hog/eval/experiments/2026-09-model-kimi-k3/scripts/kimi_mts_smoke.py:12 107 517
products/review_hog/eval/experiments/2026-08-model-glm53-flash/scripts/glm_mts_smoke.py:7 products/review_hog/eval/experiments/2026-08-validator-model-sol/scripts/codex_mts_smoke.py:7 83 372
products/context_layer/backend/task_profiles.py:16 products/review_hog/backend/task_profiles.py:28 17 85
⚠️ Duplication (TypeScript) — 8 new duplicated blocks (worst 309 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
frontend/src/scenes/insights/insightLogic.tsx:502 frontend/src/scenes/insights/insightLogic.tsx:553 45 309
frontend/src/scenes/insights/insightDataLogic.tsx:334 frontend/src/scenes/insights/insightDataLogic.tsx:392 49 281
frontend/src/scenes/insights/insightDataLogic.tsx:391 frontend/src/scenes/insights/insightLogic.tsx:342 45 263
products/logs/frontend/components/LogsSqlEditor/logsSqlEditorTrackingLogic.ts:17 products/tracing/frontend/components/TracingSqlEditor/tracingSqlEditorTrackingLogic.ts:17 49 209
products/logs/frontend/components/LogsSqlEditor/logsSqlEditorTrackingLogic.ts:17 products/metrics/frontend/components/metricsSqlEditorTrackingLogic.ts:18 45 206
common/storybook/.storybook/test-runner.ts:562 packages/quill/apps/storybook/.storybook/test-runner.ts:32 41 137
frontend/src/scenes/insights/insightLogic.tsx:320 frontend/src/scenes/insights/insightLogic.tsx:372 16 82
frontend/src/scenes/insights/insightDataLogic.tsx:361 frontend/src/scenes/insights/insightLogic.tsx:318 15 79
⚠️ Comment density — 3% of added code lines are comments (61 of 1908)

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/smtp-relay/smtp-relay.service.ts 23 591
nodejs/src/cdp/smtp-relay/smtp-message.ts 10 253
nodejs/src/cdp/config.ts 8 52
nodejs/src/cdp/smtp-relay/smtp-send-api.ts 6 116
nodejs/src/cdp/smtp-relay/smtp-relay.service.test.ts 4 317
nodejs/src/cdp/smtp-relay/smtp-replies.ts 2 127
posthog/settings/data_stores.py 2 8
nodejs/src/cdp/smtp-relay/smtp-limits.ts 1 30

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

⚠️ Bundle size — 🔺 +113.7 KiB (+0.1%)

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

Total: 77.35 MiB · 🔺 +113.7 KiB (+0.1%)

File Size Δ vs base
render-query/src/render-query/render-query.js 21.64 MiB 🔺 +37.8 KiB (+0.2%)
exporter/src/queries/schema.js 1.35 MiB 🔺 +36.8 KiB (+2.7%)
posthog-app/src/queries/schema.js 1.35 MiB 🔺 +36.8 KiB (+2.7%)

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.03 MiB · 25 files no change █████████░ 89.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
3.00 MiB · 625 files no change ██████████ 99.1% of 3.03 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
3.83 MiB · 1,228 files no change ██████████ 95.5% of 4.01 MiB
dashboard scene
src/scenes/dashboard/Dashboard.tsx
7.60 MiB · 2,941 files 🔺 +220 B (+0.0%) ████████░░ 77.3% of 9.84 MiB
today home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
4.70 MiB · 1,756 files 🔺 +220 B (+0.0%) ██████████ 95.2% of 4.93 MiB
AI-first home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
5.99 MiB · 2,265 files 🔺 +220 B (+0.0%) ██████████ 95.6% of 6.27 MiB
events scene
src/scenes/activity/explore/EventsScene.tsx
6.68 MiB · 2,519 files 🔺 +220 B (+0.0%) ███████░░░ 71.0% of 9.41 MiB
web analytics scene
src/scenes/web-analytics/WebAnalyticsScene.tsx
7.70 MiB · 2,880 files 🔺 +220 B (+0.0%) ███████░░░ 73.4% of 10.49 MiB
replay detail scene
src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
6.90 MiB · 2,605 files 🔺 +220 B (+0.0%) ████████░░ 84.9% of 8.13 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 products/product_analytics/frontend/insights/activityDescriptions 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/lib/logic/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/kea-test-utils/ 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
🟢 products/product_analytics/frontend/insights/activityDescriptions stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/kea-test-utils/ 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
🟢 src/layout/panel-layout/ProjectTree/menus/SessionReplayMenuItems.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/taxonomy/core-filter-definitions-by-group.json stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/navigation/projectNoticeLogic.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/navigation/ImpersonationNotice/ImpersonationNotice.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/cohorts/cohortSavedToast.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
🟢 products/subscriptions/frontend/components/Subscriptions/exportNudge/exportNudgeLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/billing/paymentEntryLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 products/conversations/frontend/components/SidePanel/sidepanelTicketsLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/zod/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/authentication/auth/loginLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/authentication/shared/passkeyLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/cohorts/CohortFilters/constants.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 products/persons/frontend/logics/personsLogic.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/surveys/constants.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/@tiptap/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/prosemirror- stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/re2js/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/react-markdown/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/Command/Command.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 packages/quill/packages/ 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
🟢 packages/quill/packages/quill/dist/ stays out of src/scenes/dashboard/Dashboard.tsx
🟢 node_modules/@tiptap/ stays out of src/scenes/dashboard/Dashboard.tsx
🟢 node_modules/prosemirror- stays out of src/scenes/dashboard/Dashboard.tsx
🟢 node_modules/marked/ stays out of src/scenes/dashboard/Dashboard.tsx
🟢 node_modules/frimousse/ stays out of src/scenes/dashboard/Dashboard.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/queries/web-analytics-validators.js stays out of src/scenes/dashboard/Dashboard.tsx
🟢 node_modules/re2js/ stays out of src/scenes/dashboard/Dashboard.tsx
🟢 node_modules/kea-test-utils/ stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/scenes/debug/HogDebug.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/queries/QueryEditor/QueryEditor.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 packages/quill/packages/quill/dist/ stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.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
🟢 packages/quill/packages/quill/dist/ 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/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
🟢 packages/quill/packages/quill/dist/ stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 node_modules/@tiptap/ stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 node_modules/prosemirror- stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 node_modules/marked/ stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 node_modules/frimousse/ stays out of src/scenes/activity/explore/EventsScene.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/queries/web-analytics-validators.js stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 node_modules/re2js/ stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 node_modules/kea-test-utils/ stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/queries/nodes/InsightViz/InsightViz.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/queries/nodes/DataVisualization/DataVisualization.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/queries/nodes/WebOverview/WebOverview.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/scenes/debug/HogDebug.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/queries/QueryEditor/QueryEditor.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 packages/quill/packages/quill/dist/ stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 node_modules/@tiptap/ stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 node_modules/prosemirror- stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 node_modules/marked/ stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 node_modules/frimousse/ stays out of src/scenes/web-analytics/WebAnalyticsScene.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/queries/validators.js stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 node_modules/re2js/ stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 node_modules/kea-test-utils/ stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 src/scenes/debug/HogDebug.tsx stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 src/queries/QueryEditor/QueryEditor.tsx stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 packages/quill/packages/quill/dist/ stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 node_modules/@tiptap/ stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 node_modules/prosemirror- stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 node_modules/marked/ stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.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
🟢 node_modules/re2js/ stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 node_modules/kea-test-utils/ stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 node_modules/d3/src/ 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.9 KiB src/lib/utils/bootTiming.ts
1.7 KiB src/index.tsx
1.7 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.2 KiB src/RootErrorBoundary.tsx
1.1 KiB src/lib/utils/deferredAppContext.ts
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
Size File
359.8 KiB ../node_modules/.pnpm/posthog-js@1.447.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 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
105.6 KiB src/products.tsx
87.4 KiB src/lib/api.ts
68.7 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
28.3 KiB src/lib/constants.tsx
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
359.8 KiB ../node_modules/.pnpm/posthog-js@1.447.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 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
105.6 KiB src/products.tsx
87.4 KiB src/lib/api.ts
68.7 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
32.5 KiB src/lib/utils/eventUsageLogic.ts
28.3 KiB src/lib/constants.tsx
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
Size File
359.8 KiB ../node_modules/.pnpm/posthog-js@1.447.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 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
115.8 KiB ../node_modules/.pnpm/posthog-js@1.447.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/surveys-preview.js
111.3 KiB ../packages/quill/packages/primitives/dist/index.js
105.6 KiB src/products.tsx
96.1 KiB ../packages/quill/packages/components/dist/index.js
87.4 KiB src/lib/api.ts
79.0 KiB src/scenes/dashboard/dashboardLogic.tsx
68.7 KiB src/lib/lemon-ui/icons/icons.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
359.8 KiB ../node_modules/.pnpm/posthog-js@1.447.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 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
111.3 KiB ../packages/quill/packages/primitives/dist/index.js
105.6 KiB src/products.tsx
87.4 KiB src/lib/api.ts
68.7 KiB src/lib/lemon-ui/icons/icons.tsx
68.5 KiB ../products/posthog_ai/frontend/logics/runStreamLogic.ts
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
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
359.8 KiB ../node_modules/.pnpm/posthog-js@1.447.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 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
111.3 KiB ../packages/quill/packages/primitives/dist/index.js
105.6 KiB src/products.tsx
87.4 KiB src/lib/api.ts
68.7 KiB src/lib/lemon-ui/icons/icons.tsx
68.5 KiB ../products/posthog_ai/frontend/logics/runStreamLogic.ts
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
41.7 KiB src/scenes/hog-functions/sub-templates/sub-templates.ts
Largest files eagerly shipped from src/scenes/activity/explore/EventsScene.tsx
Size File
359.8 KiB ../node_modules/.pnpm/posthog-js@1.447.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 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
111.3 KiB ../packages/quill/packages/primitives/dist/index.js
105.6 KiB src/products.tsx
96.1 KiB ../packages/quill/packages/components/dist/index.js
87.4 KiB src/lib/api.ts
79.0 KiB src/scenes/dashboard/dashboardLogic.tsx
68.7 KiB src/lib/lemon-ui/icons/icons.tsx
68.5 KiB ../products/posthog_ai/frontend/logics/runStreamLogic.ts
Largest files eagerly shipped from src/scenes/web-analytics/WebAnalyticsScene.tsx
Size File
359.8 KiB ../node_modules/.pnpm/posthog-js@1.447.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 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
115.8 KiB ../node_modules/.pnpm/posthog-js@1.447.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/surveys-preview.js
111.3 KiB ../packages/quill/packages/primitives/dist/index.js
105.6 KiB src/products.tsx
96.1 KiB ../packages/quill/packages/components/dist/index.js
87.4 KiB src/lib/api.ts
79.0 KiB src/scenes/dashboard/dashboardLogic.tsx
68.7 KiB src/lib/lemon-ui/icons/icons.tsx
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
Size File
359.8 KiB ../node_modules/.pnpm/posthog-js@1.447.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
321.1 KiB ../node_modules/.pnpm/posthog-js@1.447.1_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
224.1 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
111.3 KiB ../packages/quill/packages/primitives/dist/index.js
105.6 KiB src/products.tsx
96.1 KiB ../packages/quill/packages/components/dist/index.js
87.4 KiB src/lib/api.ts
68.7 KiB src/lib/lemon-ui/icons/icons.tsx
68.5 KiB ../products/posthog_ai/frontend/logics/runStreamLogic.ts

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

✅ Frontend layers — no new upward imports, 0 removed

Static imports from a lower frontend layer into a higher one (layers, lowest first: kernel, shell, query, features). Compared with the base branch, so an import that a PR also adds to layer-baseline.txt still shows here. Sizes are the source bytes on the eager path of every logged-in page.

Layer Eager source bytes Δ vs base
kernel 2.97 MiB 🔺 +702 B (+0.0%)
shell 2.36 MiB no change
query 56.2 KiB no change
features 273.8 KiB no change

Download the interactive layer graph for this PR.

Posted automatically by check-layers

✅ Toolbar bundle — eager 2.42 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.42 MiB · 19 files 🔺 +1.2 KiB (+0.0%) ████░░░░░░ 42.3% of 5.72 MiB
Deferred (lazy) 2.62 MiB · 42 files 🔺 +987 B (+0.0%) 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
964.0 KiB dist/toolbar/toolbar-app-PJRMEJI6.css
732.1 KiB dist/toolbar/chunk-chunk-6I55IVDU.js
290.5 KiB dist/toolbar/chunk-chunk-AY2TVGDN.js
131.8 KiB dist/toolbar/chunk-chunk-FQJO25M6.js
131.6 KiB dist/toolbar/chunk-chunk-SYHCDZT5.js
75.7 KiB dist/toolbar/toolbar-app-6XECII2Y.js
69.0 KiB dist/toolbar/chunk-chunk-BFAXZMC5.js
33.8 KiB dist/toolbar/chunk-chunk-QHPHB2X7.js
21.7 KiB dist/toolbar/chunk-chunk-COEOBQDQ.js
6.8 KiB dist/toolbar/chunk-chunk-RVLGRPCH.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

✅ Dist folder size — 🔺 +517.0 KiB (+0.1%)

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

Total: 987.90 MiB · 🔺 +517.0 KiB (+0.1%)

ℹ️ MCP UI apps size — 35 app(s), 29918.1 KB JS

Built size of each MCP UI app (main.js + styles.css).

App JS CSS
debug 864.4 KB 221.7 KB
explain-chart 1143.0 KB 221.7 KB
action 793.1 KB 221.7 KB
action-list 855.4 KB 221.7 KB
cohort 792.1 KB 221.7 KB
cohort-list 854.4 KB 221.7 KB
email-template 792.0 KB 221.7 KB
error-details 797.0 KB 221.7 KB
error-issue 794.1 KB 221.7 KB
error-issue-list 856.6 KB 221.7 KB
experiment 852.5 KB 221.7 KB
experiment-list 856.2 KB 221.7 KB
experiment-results 854.4 KB 221.7 KB
feature-flag 854.8 KB 221.7 KB
feature-flag-list 858.5 KB 221.7 KB
feature-flag-testing 795.4 KB 221.7 KB
inline-scan 792.6 KB 221.7 KB
insight-actors 853.9 KB 221.7 KB
llm-costs 851.2 KB 221.7 KB
session-recording 794.4 KB 221.7 KB
survey 793.7 KB 221.7 KB
survey-global-stats 851.2 KB 221.7 KB
survey-list 856.1 KB 221.7 KB
survey-stats 851.2 KB 221.7 KB
trace-span 792.5 KB 221.7 KB
trace-span-list 855.3 KB 221.7 KB
vision-observation-list 854.5 KB 221.7 KB
workflow 792.4 KB 221.7 KB
workflow-list 854.8 KB 221.7 KB
workflow-setup-status 792.1 KB 221.7 KB
query-results 1114.4 KB 221.7 KB
render-ui 1218.7 KB 221.7 KB
visual-review-snapshots 797.0 KB 221.7 KB
workflow-data-suggestions 794.8 KB 221.7 KB
workflow-sending-rules 793.5 KB 221.7 KB
⚠️ MCP snapshots — 1 updated (1 modified, 0 added, 0 deleted)

Snapshots: MCP unit test snapshots updated

Changes: 1 snapshots (1 modified, 0 added, 0 deleted)

What this means:

  • Snapshots have been automatically updated to match current output

Next steps:

  • Review the changes to ensure they're intentional
  • If unexpected, investigate what caused the output to change

Review snapshot changes →

@dmarchuk
dmarchuk force-pushed the workflows/smtp-3-relay branch from 2504c04 to e94a994 Compare November 6, 2026 13:51
@dmarchuk dmarchuk added the cross-lane-change Allow for cross lane (i.e. django + rust) changes to merge at once label Nov 6, 2026
@dmarchuk
dmarchuk force-pushed the workflows/smtp-3-relay branch from e94a994 to abd9ba8 Compare November 6, 2026 14:10
@dmarchuk
dmarchuk force-pushed the workflows/smtp-3-relay branch from 0223568 to 11faff1 Compare November 6, 2026 16:07
@dmarchuk dmarchuk closed this Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cross-lane-change Allow for cross lane (i.e. django + rust) changes to merge at once run-ci-backend Force ci-backend's full test matrices to run even on a draft PR run-ci-frontend Force ci-storybook's full visual-regression matrix to run even on a draft PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant