Skip to content

feat(workflows): route sandbox email through its fixed ses identity - #428

Closed
Silthus wants to merge 1 commit into
workflows-sandbox/sender-rowfrom
workflows-sandbox/route
Closed

Silthus wants to merge 1 commit into
workflows-sandbox/sender-rowfrom
workflows-sandbox/route

Conversation

@Silthus

@Silthus Silthus commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Problem

A team cannot send any workflow email until it verifies its own sending domain, which can take up to two days. The sandbox sender row from #426 exists but cannot deliver yet. This layer lets it deliver through one fixed PostHog-owned identity.

Supersedes PostHog#111401 (left open as the original draft)

Supersedes PostHog#112071 (closed); this layer includes its empty-route settings fix.

Stack: layer 2 of 12, stacked on #426. Next: #430.

This fork PR moves to an upstream PostHog/posthog PR after review.

Caution

🔴 Merge risk: high · one-way door (merge-risk rubric)

  • This layer can send real email through the shared SES identity. Delivered email cannot be recalled and can affect shared deliverability.
  • The worker switch defaults off; send-path tests cover fixed routing, footers, snapshots, retries, and unavailable settings.
  • Workers older than this layer report sandbox sends as sent without delivering them, so this layer must be deployed before the flag turns on.

Changes

  • Delivery accepts sandbox senders only for workflow invocations or explicit test sends; destination invocations skip with a reason.
  • Sandbox emails use a fixed sender address (SES_SANDBOX_FROM_ADDRESS, which Django and the worker both read; see the rollout note in feat(workflows): provision a managed sandbox email sender #426), a shared SES tenant and configuration set, and an organization identification footer.
  • Sandbox emails disable tracking and ignore custom sender and Reply-To values.
  • Stored assets retain the footer. Root clipping is overridden while template typography and selectors remain intact. Already escaped preview text remains readable.
  • Master’s UTM tagging and SES throttle retry behavior remain intact; route settings are trimmed before use.
  • Unavailable route settings skip delivery before contacting SES and identify the missing setting at worker startup.
  • The parser dependency and lockfile entry support footer insertion into malformed HTML. Unrelated dependency upgrades were removed.

Before:

flowchart LR
    Send[Email send] --> Sender[Project identity] --> SES[Project SES tenant]
    classDef phYellow fill:#f9bd2b,stroke:#f9bd2b,color:#000;
    classDef phGray fill:#e5e7eb,stroke:#c7ccd1,color:#000;
    classDef phRed fill:#f54e00,stroke:#f54e00,color:#fff;
    class Send phYellow;
    class Sender phGray;
    class SES phRed;
Loading

After:

flowchart LR
    Send[Email send] --> Route[Choose sender route]
    Route --> Own[Project SES tenant]
    Route --> Guard[Sandbox availability] --> Footer[Fixed identity and footer] --> Shared[Shared SES tenant]
    classDef phYellow fill:#f9bd2b,stroke:#f9bd2b,color:#000;
    classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff;
    classDef phGray fill:#e5e7eb,stroke:#c7ccd1,color:#000;
    classDef phRed fill:#f54e00,stroke:#f54e00,color:#fff;
    class Send phYellow;
    class Route,Guard phBlue;
    class Footer phGray;
    class Own,Shared phRed;
Loading

How did you test this code?

Current evidence, 2026-10-24, head c9a0c736992.

Owed proofs:

  • Live run: rerun at the stack tip 8ac8853cc82, which contains this layer. The stack's cdp-api sent through the workflow test-run endpoint to a local SES fake that records each request. 9 of 9 cases pass. 12 of 13 recorded cases match the run at fa33d78fb36. Case 2c (an unverified member on To) now shows the verify-your-email guidance with reason recipient_unverified, and makes no SES request. For this layer: case 1 goes out from the fixed sandbox identity on the sandbox tenant and configuration set, and ignores the step's From, name and Reply-To with a log line. Case 5 routes master-shaped stored flows and new drafts the same way, and own senders keep the team route.
  • Cross-version: carried from the stack tip fa33d78fb36, because round 4 does not touch the own-sender path or the base worker. Fork master 448d802 against that tip. A base worker that meets a sandbox row logs "sent" and makes no SES request. The deploy-order decision in feat(workflows): provision a managed sandbox email sender #426's merge risk covers it: workers with feat(workflows): route sandbox email through its fixed ses identity #428 deploy before the flag turns on, and no worker rolls back below feat(workflows): route sandbox email through its fixed ses identity #428 while sandbox rows exist.
  • Shadow diff: carried from the stack tip fa33d78fb36, because round 4 does not touch the own-sender path or the base worker. Own-sender SES requests from fork master 448d802 and that tip are identical across 7 cases (stored flows, editor drafts, a custom From name, unverified and missing senders), apart from the per-run signatures of tracking tokens. One intended difference: an editor test run with an empty recipient reports skipped instead of success, because feat(workflows): test templates with the sandbox sender #451 reports skipped email steps in test runs. Live sends are unchanged.

Changes in this layer since the 2026-10-19 heads (restacked onto fork master 448d802: the email service keeps both master's empty-recipient skip and this layer's custom-sender log):

  • Round 1 fixed: test-only cleanup: inert senderConfig and preheader test parameters removed.
  • No change in this layer since round 2.
  • Final read: GPT-6.1 Sol read the G1 layer range-diffs and found nothing.
  • Round 4 (R2-11): the sending tiers doc now says Django reads the same SES_SANDBOX_FROM_ADDRESS as the worker. Docs only; the worker code is unchanged.
  • Final read 2: GPT-6.1 Sol read the round 4 range-diffs per group and found nothing.
Local gate at the stack tip 8ac8853cc82, shared by every layer

Test seams: EmailService.executeSendEmail through invocation outcomes, run logs and mocked SES.

Test rationale: The destination-invocation case failed before the delivery gate. Existing sandbox routes cover workflow and explicit test sends; footer cases stay database-free.

Gaps: real AWS SES delivery and the AWS-side tenant pause (every proof uses a local SES fake or an unreachable SES), clustered Valkey, the production data shape (prod-shape gap until production read access on 2026-11-02), and fork CI, which fails during setup: path selection fails without a GitHub App client ID, and the Security aggregate rejects its fork-skipped path-filter job. Application CI is a verification gap.

Earlier evidence (superseded heads)

Round 3 heads, 2026-10-23. This layer's head: e736d676fd3. Stack tip fa33d78fb36.

Whole-stack QA swarm, 2026-10-19. Reviewed tip cfee7e148ae. This layer's head: 8b1f99efba6.

  • Live run at b9bd346ed0e (this stack's cdp-api against a local SES fake, workflow test-run endpoint): a sandbox send to a verified member went out from the fixed identity through the sandbox tenant and configuration set. The step's From name, From address and Reply-To were ignored with a log line. A non-member on CC and an unverified member were blocked with the clear message. A paused tenant state skipped sandbox sends while own-sender sends continued.
  • Shadow diff: an own-sender send produced the same SES request and the same stored message on master and on this stack.
  • Cross-version: a worker on master reports a sandbox send as sent without calling SES. See the deploy order in feat(workflows): provision a managed sandbox email sender #426's merge risk.
  • Checks at the tip (Node suites at 1a0fe3663c2; later fixes touch no Node code): Node CDP messaging, members and rate limiter suites (including email-tracking.service.test.ts, which now runs), the cdp-api suite, the Python sandbox, tenant, tier, reputation and validation suites, the frontend Jest suites of this stack, ruff, and the strict pre-push preflight: pass.
  • Gaps: real AWS SES delivery, clustered Valkey, and the production data shape (no production read access yet). Fork CI still fails during setup.

Rebased onto master 734468db6f9 (2026-10-19), head 838503512a0.

  • Drift: Master's email service gained a sender default Reply-To, marketing frequency caps, and the replay vision step since the old base.
  • Adapted: No conflicts. The layer applies unchanged.
  • This layer: Node email.service.test.ts and sandbox-email-sender.test.ts pass at the tip (below).
  • Gaps at modernize time: email-tracking.service.test.ts did not run then. It runs now; see the QA swarm block above. Fork CI still fails during setup, as noted below. Real AWS delivery and clustered Valkey remain unchecked.
Checks at the stack tip

Stack tip b9bd346ed0e (#456), shared by every layer:

  • Python, 354 passed: sandbox sender, sender validation, CDP validation, reputation, SES tenant state and events, sending tier.
  • Node CDP messaging, managers, and rate limiter: 17 suites, 599 passed.
  • Frontend Jest, 37 suites, 487 passed. Five suites timed out under host load in the batch run and passed when rerun alone.
  • Frontend tsgo: no errors. Node tsgo: no errors in CDP. The 23 errors it reports are all in session replay files that need the unbuilt Rust @posthog/replay-anonymizer module.
  • ruff, oxfmt, oxlint, prettier, eslint, product:lint (workflows, cdp), IDOR coverage, hogli build:openapi (no diff), and the strict pre-push preflight: pass.
  • Review: GPT-6.1 Sol read every conflict resolution in the stack and found no issues.
Observed targeted verification after QA fixes
email.service.test.ts -t 'sandbox sender': 17 passed, 116 skipped
Observed combined-stack gate
Node.js typecheck and build: exit 0
Frontend typecheck and build: exit 0
Success: no issues found in 23388 source files
product:lint workflows: All checks passed
ci:preflight --strict: failures 0

👉 Stay up-to-date with PostHog coding conventions for a smoother review.

Release status

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

Docs update

Keep provisioning and the worker switch off until recipient restrictions, daily caps, and tenant pause enforcement are deployed.

The existing workflow email sending tiers document describes SES setting alignment, internal-test unsubscribe behavior, and HTML normalization.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Decisions

Question Answer Who Where
One sandbox From address in two settings (R2-11) Django and the worker read one setting, SES_SANDBOX_FROM_ADDRESS, so the address the UI shows is the address the worker sends from. Replaces the earlier call to set two settings to one value at rollout 👤 Human decision comment
Deploy order for sandbox rows Workers with #428 before the flag turns on, and no worker rollback below #428 while rows exist. Alternative: a default branch in the worker's provider switch, which cannot cover a rollback to master 🤖 Agent QA handoff
Keep the identification footer? Kept as built in this layer. Alternative: remove the footer and its HTML parser dependency, which the spec does not ask for 🤖 Agent QA handoff
Seam for the one From setting Django reads the env var the worker already reads, and the separate Django From setting is gone. The worker keeps its fixed From, so its SES identity check is unchanged. Alternative: Django passes the stored address to the worker, which changes the send path 🤖 Agent decision comment

Involvement: restack onto fork master 448d802 on 2026-10-23; round 1 with GPT-6.1 Sol lanes plus Claude Opus 5.5 fresh eyes for group G1; round 2 on the fix diff; final read 1 by GPT-6.1 Sol per group, with no findings; proofs captured; final read 2 by GPT-6.1 Sol on the round 4 decisions, with no findings; the round 4 live run and a UI walk of the two changed surfaces. Before that: the 2026-10-19 whole-stack QA swarm and the modernize rebase (see Earlier evidence). No review bots run on the fork.

Agent: Codex with GPT-6.1 Sol (gpt-6.1-sol) built the original layers, orchestrated by Claude Code with Claude Opus 5.5 (claude-opus-5-5). Restack, review fixes and this description refresh: Claude Opus 5.5 (claude-opus-5-5). Review: GPT-6.1 Sol (gpt-6.1-sol). Fresh eyes: Claude Opus 5.5 (claude-opus-5-5).

Skills: tdd, pr, stacking-prs, writing-pr-descriptions, merge-risk, writing-tests, running-ci-preflight, and writing-voice. The description refresh used pr-description, writing-pr-descriptions, writing-voice and merge-risk.

This rebuild adapts the public source draft to master’s UTM tagging and existing throttle rescheduling. The sandbox sender’s empty-settings guard belongs in this layer. Review fixes preserve message structure while overriding root clipping, keep preview parse and serialization modes consistent, normalize settings, and lower parser tests to the sender output seam.

🤖 Generated with Claude Code

@Silthus
Silthus added this pull request to stack #429 October 12, 2026 10:38
@Silthus Silthus self-assigned this Oct 12, 2026
@Silthus
Silthus force-pushed the workflows-sandbox/route branch 2 times, most recently from b2a27a5 to 6705af2 Compare October 12, 2026 23:12
@Silthus
Silthus force-pushed the workflows-sandbox/route branch from 6705af2 to 8385035 Compare October 19, 2026 10:58
@Silthus
Silthus force-pushed the workflows-sandbox/route branch 2 times, most recently from 8b1f99e to e736d67 Compare October 23, 2026 16:26
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

🤖 CI report

⚠️ Bundle size — 🔺 +2.2 KiB (+0.0%)

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

Total: 76.03 MiB · 🔺 +2.2 KiB (+0.0%)

No file changed by more than 1000 B.

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

⚠️ Eager graph — 2 over budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.01 MiB · 24 files no change █████████░ 88.6% of 1.14 MiB
logged-out boot: index + App + bootApp (preloaded by every page, including /login)
src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
2.94 MiB · 620 files 🔺 +119 B (+0.0%) ██████████ 96.9% of 3.03 MiB
🟡 authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
7.65 MiB · 2,633 files 🟢 -537 B (-0.0%) ██████████ 102.1% of 7.50 MiB
dashboard scene
src/scenes/dashboard/Dashboard.tsx
9.68 MiB · 3,703 files 🟢 -1.1 KiB (-0.0%) ██████████ 96.7% of 10.01 MiB
🟡 today home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
8.06 MiB · 2,977 files 🟢 -537 B (-0.0%) ██████████ 104.1% of 7.74 MiB
AI-first home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
7.90 MiB · 2,706 files 🟢 -537 B (-0.0%) █████████░ 88.1% of 8.96 MiB
events scene
src/scenes/activity/explore/EventsScene.tsx
9.15 MiB · 3,495 files 🟢 -500 B (-0.0%) ██████████ 95.4% of 9.59 MiB
web analytics scene
src/scenes/web-analytics/WebAnalyticsScene.tsx
10.01 MiB · 3,713 files 🟢 -500 B (-0.0%) █████████░ 92.4% of 10.83 MiB
replay detail scene
src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
8.22 MiB · 3,075 files 🟢 -432 B (-0.0%) █████████░ 86.8% of 9.47 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/svg/ stays out of src/index.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/components/ stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/layout/navigation-3000/navigationLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/scenes/dashboard/dashboardLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/lemon-ui/LemonMarkdown/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/RichContentEditor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/CodeSnippet/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/taxonomy/core-filter-definitions-by-group.json stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 node_modules/zod/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/scenes/billing/billingLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/ProductEmptyState/productSetupPreloadLogic.ts stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 products/dashboards/frontend/widgets/previews/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 products/wizard/frontend/runs/WizardRunDetailsDrawer.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 zod/v4/locales/de.js stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/svg/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/components/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/queries/validators.js stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/scenes/project-homepage/today/TodayReportPage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/queries/Query/Query.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 products/posthog_ai/frontend/components/tool/widgets/VisualizationWidget.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 products/posthog_ai/frontend/components/tool/widgets/RecordingsWidget.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 src/queries/Query/Query.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 products/posthog_ai/frontend/api/primitives.ts stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 src/scenes/notebooks/Notebook/notebookLogic.ts stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/queries/validators.js stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/scenes/web-analytics/LiveMetricsDashboard/LiveWebAnalyticsMetrics.tsx stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 products/product_analytics/frontend/insights/trends/WorldMap/countryVectors.tsx stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 src/scenes/settings/SettingsMap.tsx stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 @jspm/core/nodelibs/browser/fs stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.4 KiB src/index.tsx
1.3 KiB src/RootErrorBoundary.tsx
1.3 KiB src/lib/utils/bootTiming.ts
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
Size File
321.9 KiB ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
220.9 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
106.3 KiB src/products.tsx
98.9 KiB src/lib/api.ts
69.4 KiB src/lib/lemon-ui/icons/icons.tsx
38.7 KiB ../node_modules/.pnpm/@dnd-kit+core@6.0.8_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@dnd-kit/core/dist/core.esm.js
33.9 KiB ../node_modules/.pnpm/kea@4.0.0-pre.6_patch_hash=139b8d1f1304f9d9da452a9a1244c94ea679dbcb85687d8999563146879fb6f5_react@18.3.1/node_modules/kea/lib/index.cjs.js
29.4 KiB src/scenes/scenes.ts
26.9 KiB src/lib/constants.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
321.9 KiB ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.8 KiB src/taxonomy/core-filter-definitions-by-group.json
238.5 KiB ../packages/quill/packages/quill/dist/index.js
220.9 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
143.2 KiB ../packages/quill/packages/primitives/dist/index.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
106.3 KiB src/products.tsx
98.9 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
Size File
321.9 KiB ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.8 KiB src/taxonomy/core-filter-definitions-by-group.json
238.5 KiB ../packages/quill/packages/quill/dist/index.js
220.9 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
143.2 KiB ../packages/quill/packages/primitives/dist/index.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
106.3 KiB src/products.tsx
98.9 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
Size File
321.9 KiB ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.8 KiB src/taxonomy/core-filter-definitions-by-group.json
238.5 KiB ../packages/quill/packages/quill/dist/index.js
220.9 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
143.2 KiB ../packages/quill/packages/primitives/dist/index.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
106.3 KiB src/products.tsx
98.9 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
Size File
321.9 KiB ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.8 KiB src/taxonomy/core-filter-definitions-by-group.json
238.5 KiB ../packages/quill/packages/quill/dist/index.js
220.9 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
143.2 KiB ../packages/quill/packages/primitives/dist/index.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
106.3 KiB src/products.tsx
98.9 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
Largest files eagerly shipped from src/scenes/activity/explore/EventsScene.tsx
Size File
321.9 KiB ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.8 KiB src/taxonomy/core-filter-definitions-by-group.json
238.5 KiB ../packages/quill/packages/quill/dist/index.js
220.9 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
143.2 KiB ../packages/quill/packages/primitives/dist/index.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
106.3 KiB src/products.tsx
98.9 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
Largest files eagerly shipped from src/scenes/web-analytics/WebAnalyticsScene.tsx
Size File
321.9 KiB ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.8 KiB src/taxonomy/core-filter-definitions-by-group.json
238.5 KiB ../packages/quill/packages/quill/dist/index.js
220.9 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
183.2 KiB src/queries/validators.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
143.2 KiB ../packages/quill/packages/primitives/dist/index.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
106.3 KiB src/products.tsx
98.9 KiB src/lib/api.ts
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
Size File
321.9 KiB ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
316.1 KiB ../node_modules/.pnpm/posthog-js@1.438.18_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
286.8 KiB src/taxonomy/core-filter-definitions-by-group.json
238.5 KiB ../packages/quill/packages/quill/dist/index.js
220.9 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
106.3 KiB src/products.tsx
98.9 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 PostHog#32479

✅ Toolbar bundle — eager 2.29 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.29 MiB · 18 files no change ████░░░░░░ 39.9% of 5.72 MiB
Deferred (lazy) 2.11 MiB · 40 files no change n/a — loads on demand
Loader dist/toolbar.js 1.2 KiB no change █░░░░░░░░░ 6.0% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
899.3 KiB dist/toolbar/toolbar-app-T6M4L4D7.css
691.4 KiB dist/toolbar/chunk-chunk-DDSP4M6B.js
260.7 KiB dist/toolbar/chunk-chunk-HRRIOF34.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
130.4 KiB dist/toolbar/chunk-chunk-IT6XUU73.js
74.8 KiB dist/toolbar/toolbar-app-YPZZ2PIS.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
35.6 KiB dist/toolbar/chunk-chunk-CGDYYVIR.js
21.7 KiB dist/toolbar/chunk-chunk-PM4VBMO5.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 — 🔺 +4.4 KiB (+0.0%)

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

Total: 1020.63 MiB · 🔺 +4.4 KiB (+0.0%)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Silthus
Silthus force-pushed the workflows-sandbox/route branch from e736d67 to c9a0c73 Compare October 24, 2026 13:11
@Silthus

Silthus commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

Note

🤖 Claude Opus 5.5 responding on behalf of Michael

Decision lines for this PR. The Decisions table in the description links each row here.

  • 👤 One sandbox From address in two settings (R2-11) Django and the worker read one setting, SES_SANDBOX_FROM_ADDRESS, so the address the UI shows is the address the worker sends from. Replaces the earlier call to set two settings to one value at rollout.
  • 🤖 Seam for the one From setting Django reads the env var the worker already reads, and WORKFLOWS_SANDBOX_SENDER_FROM_ADDRESS is gone. The worker keeps its fixed From, so its SES identity check is unchanged. Alternative: Django passes the stored address to the worker, which changes the send path.

@Silthus

Silthus commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

Replaced by the re-sliced fork stack: #599. Tracked in Silthus/personal#71.

@Silthus Silthus closed this Oct 27, 2026
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