Skip to content

feat(messaging): send supabase auth emails through transactional templates - #123966

Merged
trunk-io[bot] merged 1 commit into
masterfrom
posthog/messaging-supabase-auth-emails
Oct 28, 2026
Merged

trunk-io[bot] merged 1 commit into
masterfrom
posthog/messaging-supabase-auth-emails

Conversation

@luke-belton

@luke-belton luke-belton commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Problem

  • Teams on Supabase get their sign-up, magic link and password reset emails from Supabase's own sender or their SMTP provider, so those emails sit outside PostHog's transactional email sends, tracking and templates.
  • The request was to integrate Supabase's auth emails with PostHog's transactional emails. No further need was given.

This is the backend layer. The UI is stacked on top in #123968.

Changes

  • Supabase's Send Email Hook can now post each auth email to /webhooks/messaging/supabase/<team_id>/send-email.
  • PostHog checks the Standard Webhooks signature with the team's stored hook secret, then sends from the transactional template mapped to that email type.
  • Each send is recorded against the Supabase user's ID and uses the existing transactional send path.
  • Templates get confirmation_url (Supabase's /auth/v1/verify link), token, email, new_email, redirect_to and user_metadata.
  • Secure email change sends one email to each address, using Supabase's swapped token/token_hash_new pairs.
  • An unmapped notification type (password changed and similar) answers 200 and sends nothing. An unmapped auth email fails the user's request.
  • A verified failure answers 200 with {"error": {"http_code", "message"}}, because Supabase replaces the message of any non-2xx answer with a generic one. The person signing in sees a neutral message, and the reason goes into the usage event.
  • An unverified request gets a real 401, so an unsigned caller never gets a 200.
  • The idempotency key comes from the payload's metadata.uuid. Supabase gives each retry a new webhook-id, so that header can't dedupe.
  • New endpoints: messaging_supabase_auth_emails/settings (GET, PATCH) and starter_templates (POST). Starter templates are six transactional templates marked sensitive.
  • The settings API is behind workflows-supabase-auth-emails and answers 404 with the flag off. The hook has no flag check, so a flag rollback can't break sign-ups for a project that is already connected.
  • PostHog has no on/off switch. The Send Email Hook in Supabase is the switch: with it on, an "off" in PostHog could only fail every sign-up.
  • DELETE .../settings disconnects: it removes the project URL, hook secret and mappings, so the hook refuses every call. The templates stay.
  • A hook call with no saved project URL fails with project_url_missing, because every confirmation link would be broken.
  • The hook event records failures only. Successful sends show in each template's own stats.
  • Usage events: supabase auth emails settings saved, supabase auth emails starter templates created, supabase auth email hook.

Mechanical: standard_webhooks_scheme in posthog/ingress/verify/schemes.py builds the HmacSha256 that account_audits built by hand, with the same arguments. The new table has no DB-level FKs to posthog_team or posthog_user. The generated API types are regenerated.

Warning

The hook is synchronous, and Supabase rolls the sign-up back if it takes more than 5 seconds. The hook's sends share a 4-second budget, so a slow pipeline fails the request cleanly instead of timing out. Locally each transactional send took about 2 s, and a secure email change (two sends) took 4.5 s. Production latency is not measured.

How did you test this code?

  • Ran end to end against a local Supabase CLI stack, with the hook pointed at this branch's Django.
  • All eight paths delivered or skipped as expected: signup, magic link, recovery, invite, secure email change, reauthentication, and an unmapped password-changed notification.
  • Following the signup link confirmed the user. Following both email-change links completed the change.
  • The end-to-end run caught a NOT NULL failure on the first save, now fixed and covered by a test.

Test rationale: test_supabase_auth_emails.py is new, because no existing test covers this endpoint. Each case targets one regression:

  • the email-change token swap
  • an idempotency key that changes between retries
  • each error answer, including a deleted mapped template
  • the flag gate
  • the first save before a secret exists
  • starter templates and settings validation

account_audits tests cover the scheme refactor unchanged.

Not checked: the hosted Supabase dashboard flow, production latency, semgrep (not installed locally), and repo-wide mypy.

Release status

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

Docs update

None. No doc under docs/ covers transactional emails.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

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

  • Skills invoked: /adding-inbound-webhooks, /django-migrations, /improving-drf-endpoints, /writing-tests, /writing-user-facing-copy, /stacking-prs, /writing-pr-descriptions, /simplify, /code-review.
  • Decisions with the requester: a dedicated flag, a hard failure for unmapped auth emails with a skip for notifications, recording against the Supabase user ID, and a backend-then-UI stack.
  • A subagent built a local Supabase CLI project to record real hook payloads and how Supabase reacts to each status code. That drove the 200-with-error-body answers and the metadata.uuid idempotency key.
  • Open question for review: whether to move the sends off the request path, depending on production latency of the transactional send endpoint.

🤖 Generated with Claude Code

@trunk-io

trunk-io Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

😎 This pull request was merged.

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Trunk lane — backend Python lane

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

✅ 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) — 1 new duplicated block (worst 76 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/messaging/backend/models/message_suppression.py:36 products/messaging/backend/models/supabase_auth_email_config.py:12 11 76
✅ Duplication (TypeScript) — clean

New TypeScript code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

🚨 Comment density — 6% of added code lines are comments (82 of 1358)

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
products/messaging/backend/presentation/views/supabase_auth_emails.py 25 316
products/messaging/backend/services/supabase_auth_emails.py 25 338
products/messaging/backend/tests/api/test_supabase_auth_emails.py 19 339
products/messaging/backend/facade/contracts.py 5 42
products/messaging/backend/models/supabase_auth_email_config.py 4 26
posthog/ingress/verify/schemes.py 3 28
products/messaging/backend/services/supabase_auth_email_templates.py 1 209

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

✅ Bundle size — no change

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

Total: 77.19 MiB · no change

No file changed by more than 1000 B.

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

⚠️ Eager graph — 3 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 · 25 files no change █████████░ 88.2% 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.97 MiB · 621 files no change ██████████ 97.9% of 3.03 MiB
🟡 authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
7.59 MiB · 2,569 files no change ██████████ 101.2% of 7.50 MiB
dashboard scene
src/scenes/dashboard/Dashboard.tsx
9.95 MiB · 3,732 files no change ██████████ 99.4% of 10.01 MiB
🟡 today home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
7.77 MiB · 2,662 files no change ██████████ 100.4% 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
8.02 MiB · 2,655 files no change ██████████ 101.4% of 7.91 MiB
events scene
src/scenes/activity/explore/EventsScene.tsx
9.40 MiB · 3,515 files no change ██████████ 98.0% of 9.59 MiB
web analytics scene
src/scenes/web-analytics/WebAnalyticsScene.tsx
10.34 MiB · 3,765 files no change ██████████ 95.4% of 10.83 MiB
replay detail scene
src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
8.43 MiB · 3,110 files no change █████████░ 89.1% 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
🟢 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
🟢 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
🟢 packages/quill/packages/charts/ 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.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.7 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.7 KiB src/index.tsx
1.6 KiB src/lib/utils/bootTiming.ts
1.2 KiB src/RootErrorBoundary.tsx
949 B src/scenes/ChunkLoadErrorBoundary.tsx
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
Size File
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
217.5 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
102.5 KiB src/products.tsx
86.2 KiB src/lib/api.ts
68.3 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
27.0 KiB ../node_modules/.pnpm/tailwind-merge@2.2.2/node_modules/tailwind-merge/dist/bundle-mjs.mjs
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
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 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
153.8 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
102.5 KiB src/products.tsx
94.5 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.5 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
86.2 KiB src/lib/api.ts
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
Size File
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 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
153.8 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
144.1 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
102.5 KiB src/products.tsx
94.5 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.5 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
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 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
153.8 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
102.5 KiB src/products.tsx
94.5 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.5 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
86.2 KiB src/lib/api.ts
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
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 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
153.8 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
144.1 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
102.5 KiB src/products.tsx
94.5 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.5 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
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 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
153.8 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
144.1 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
102.5 KiB src/products.tsx
94.5 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.5 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
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 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
187.9 KiB src/queries/validators.js
153.8 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
144.1 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
102.5 KiB src/products.tsx
94.5 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/session-recordings/detail/SessionRecordingDetail.tsx
Size File
323.1 KiB ../node_modules/.pnpm/posthog-js@1.439.0_@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.439.0_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
286.4 KiB src/taxonomy/core-filter-definitions-by-group.json
239.9 KiB ../packages/quill/packages/quill/dist/index.js
217.5 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
153.8 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
102.5 KiB src/products.tsx
94.5 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.5 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.31 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.31 MiB · 18 files no change ████░░░░░░ 40.3% 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
915.2 KiB dist/toolbar/toolbar-app-SM4KM3FT.css
695.9 KiB dist/toolbar/chunk-chunk-NAL4RJKF.js
260.0 KiB dist/toolbar/chunk-chunk-2C5YXPVJ.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
130.4 KiB dist/toolbar/chunk-chunk-APZDIECC.js
75.7 KiB dist/toolbar/toolbar-app-ZJPHE5A2.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
35.6 KiB dist/toolbar/chunk-chunk-VJFL5LZF.js
21.7 KiB dist/toolbar/chunk-chunk-OWLNCSQP.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 — 🔺 +9.4 KiB (+0.0%)

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

Total: 972.68 MiB · 🔺 +9.4 KiB (+0.0%)

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

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

App JS CSS
debug 844.3 KB 211.1 KB
explain-chart 1005.6 KB 211.1 KB
action 767.7 KB 211.1 KB
action-list 835.3 KB 211.1 KB
cohort 766.7 KB 211.1 KB
cohort-list 834.3 KB 211.1 KB
email-template 766.6 KB 211.1 KB
error-details 771.6 KB 211.1 KB
error-issue 768.9 KB 211.1 KB
error-issue-list 836.8 KB 211.1 KB
experiment 832.4 KB 211.1 KB
experiment-list 836.1 KB 211.1 KB
experiment-results 835.0 KB 211.1 KB
feature-flag 834.7 KB 211.1 KB
feature-flag-list 838.4 KB 211.1 KB
feature-flag-testing 770.0 KB 211.1 KB
inline-scan 767.2 KB 211.1 KB
insight-actors 833.8 KB 211.1 KB
llm-costs 831.1 KB 211.1 KB
session-recording 769.0 KB 211.1 KB
survey 768.3 KB 211.1 KB
survey-global-stats 831.1 KB 211.1 KB
survey-list 836.0 KB 211.1 KB
survey-stats 831.1 KB 211.1 KB
trace-span 767.1 KB 211.1 KB
trace-span-list 835.2 KB 211.1 KB
vision-observation-list 834.4 KB 211.1 KB
workflow 767.1 KB 211.1 KB
workflow-list 834.7 KB 211.1 KB
workflow-setup-status 766.7 KB 211.1 KB
query-results 1087.6 KB 211.1 KB
render-ui 1187.2 KB 211.1 KB
visual-review-snapshots 771.6 KB 211.1 KB
workflow-data-suggestions 769.4 KB 211.1 KB
workflow-sending-rules 768.1 KB 211.1 KB
✅ Playwright — all passed

All tests passed.

View test results →

⚠️ Django migration SQL — 1 new migration to review

We've detected new migrations on this PR. Review the SQL output for each migration:

products/messaging/backend/migrations/0010_supabase_auth_email_config.py

/home/runner/work/tmp/tool_cache/Python/3.14.7/x64/lib/python3.14/site-packages/anyio/from_thread.py:119: SyntaxWarning: 'return' in a 'finally' block
  return result
2026-10-28T13:03:38.752404Z [error    ] Path must be a valid database or directory containing databases. [posthog.exceptions_capture] pid=6822 tid=140255448013696
Traceback (most recent call last):
  File "/home/runner/work/posthog/posthog/posthog/geoip.py", line 15, in <module>
    geoip: Optional[GeoIP2] = GeoIP2(cache=8)
                              ~~~~~~^^^^^^^^^
  File "/home/runner/work/tmp/tool_cache/Python/3.14.7/x64/lib/python3.14/site-packages/django/contrib/gis/geoip2.py", line 116, in __init__
    raise GeoIP2Exception(
        "Path must be a valid database or directory containing databases."
    )
django.contrib.gis.geoip2.GeoIP2Exception: Path must be a valid database or directory containing databases.
/home/runner/work/tmp/tool_cache/Python/3.14.7/x64/lib/python3.14/site-packages/sshtunnel.py:1040: SyntaxWarning: 'return' in a 'finally' block
  return (ssh_host,
/home/runner/work/tmp/tool_cache/Python/3.14.7/x64/lib/python3.14/site-packages/langchain_core/_api/deprecation.py:27: UserWarning: Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater.
  from pydantic.v1.fields import FieldInfo as FieldInfoV1
System check identified some issues:

WARNINGS:
?: (axes.W001) You are using the django-axes cache handler for login attempt tracking. Your cache configuration is however invalid and will not work correctly with django-axes. This can leave security holes in your login systems as attempts are not tracked correctly. Reconfigure settings.AXES_CACHE and settings.CACHES per django-axes configuration documentation.
?: (staticfiles.W004) The directory '/home/runner/work/posthog/posthog/frontend/dist' in the STATICFILES_DIRS setting does not exist.
BEGIN;
--
-- Create model SupabaseAuthEmailConfig
--
CREATE TABLE "posthog_messaging_supabase_auth_email_config" ("id" uuid NOT NULL PRIMARY KEY, "created_at" timestamp with time zone NOT NULL, "updated_at" timestamp with time zone NOT NULL, "supabase_url" varchar(200) NOT NULL, "hook_secret" text NULL, "templates" jsonb NOT NULL, "created_by_id" integer NULL, "team_id" integer NOT NULL, CONSTRAINT "unique_supabase_auth_email_config_per_team" UNIQUE ("team_id"));
CREATE INDEX "posthog_messaging_supabase_created_by_id_7877cd0f" ON "posthog_messaging_supabase_auth_email_config" ("created_by_id");
CREATE INDEX "posthog_messaging_supabase_auth_email_config_team_id_7f227277" ON "posthog_messaging_supabase_auth_email_config" ("team_id");
COMMIT;

Last updated: 2026-10-28 13:04 UTC (a0e5c50)

✅ Django migration risk — migration analysis complete

We've analyzed your migrations for potential risks.

Summary: 1 Safe | 0 Needs Review | 0 Blocked

✅ Safe

Brief or no lock, backwards compatible

messaging.0010_supabase_auth_email_config
  └─ #1 ✅ CreateModel
     Creating new table is safe
     model: SupabaseAuthEmailConfig
  │
  └──> ℹ️  INFO:
       ℹ️  Skipped operations on newly created tables (empty tables
       don't cause lock contention).

Last updated: 2026-10-28 13:04 UTC (a0e5c50)

@luke-belton
luke-belton added this pull request to stack #123969 October 27, 2026 11:58
@luke-belton luke-belton changed the title posthog/messaging supabase auth emails feat(messaging): send supabase auth emails through transactional templates Oct 27, 2026

@posthog posthog 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.

PostHog Review · Standard · reviewed 02ec0a0: 3 Must fix, 5 Should fix.

This is the only automatic review while this pull request is a draft. Comment @posthog review to review new changes sooner. Otherwise, a review of the new changes runs when you mark it ready for review.

See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.

Comment thread posthog/ingress/verify/schemes.py Resolved
@trunk-io

trunk-io Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

View Full Report ↗︎ ⋅ Docs

@parameterai

parameterai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Risk: Medium · 1 medium, 1 low

This increment since the last review contains no new hand-written logic: it adds ProviderImportRun to the semgrep team-scoped-model sink lists (correct — the model is TeamScopedRootMixin with a team FK, so this widens detection), a one-line migration tweak whose current content matches the model exactly, rebase noise around the pre-existing provider-imports registration, and pure OpenAPI codegen output. No new security surface is introduced.

Sentinel reviewed a0e5c50 · Review settings

@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested review from a team, MattBro, fercgomes and rafaeelaudibert and removed request for a team October 27, 2026 16:32

@posthog posthog 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.

PostHog Review · Standard · reviewed 9142533: 1 Must fix.

@posthog posthog 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.

PostHog Review · Standard · reviewed deffcfd: no findings. This pull request is large, so the review ran in 4 parts with less depth than usual.

@mayteio mayteio 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.

this is really nice work, the idempotency stuff and the 200-with-error-body handling are super well thought through 🙂

three small things before we open the flag past internal, all a few lines each (two are replies on the existing bot threads, one inline). then one bigger question that's totally optional:

do we need enabled at all? if the hook's set up in supabase, "off" fails sign-ups anyway, so the supabase hook kind of is the switch. same with first_email_sent_at + migration 0010, feels like a lot for one analytics event. happy to chat about it, could easily be a follow up

@luke-belton

luke-belton commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor Author

do we need enabled at all? if the hook's set up in supabase, "off" fails sign-ups anyway, so the supabase hook kind of is the switch. same with first_email_sent_at + migration 0010, feels like a lot for one analytics event. happy to chat about it, could easily be a follow up

actually that's a really good challenge, I don't think we do need them. I'm going to drop enabled and first_email_sent_at to keep it all a bit simpler. We might be able to pull in send stats from the templates as a proxy for usage - I'll put that in a follow up PR if that looks like it might be a good approach

edit: this looks possible, I've drafted a fourth PR: #124453

@posthog posthog 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.

PostHog Review · Standard · reviewed d60b4c0: 1 Should fix.

This is review round 3 on this pull request. When findings keep coming back in the same code, the approach is usually the problem. Before the next fix, check whether a different design would remove the whole class of findings.

@mayteio mayteio 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.

nice, this is looking really clean now 🙂 thanks for taking the enabled stuff on board too

@luke-belton
luke-belton force-pushed the posthog/messaging-supabase-auth-emails branch from e205cf7 to b704e4e Compare October 28, 2026 11:18
@trunk-io

trunk-io Bot commented Oct 28, 2026

Copy link
Copy Markdown

Stacked PR 124168 failed testing in the merge queue. Please investigate the failure and re-submit the stack.

…lates

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@luke-belton
luke-belton force-pushed the posthog/messaging-supabase-auth-emails branch from b704e4e to a0e5c50 Compare October 28, 2026 13:00
@trunk-io
trunk-io Bot merged commit 7f0e3e6 into master Oct 28, 2026
270 checks passed
@trunk-io

trunk-io Bot commented Oct 28, 2026

Copy link
Copy Markdown

This pull request was merged into master as part of stacked PR 124168.

@trunk-io
trunk-io Bot deleted the posthog/messaging-supabase-auth-emails branch October 28, 2026 14:20
@deployment-status-posthog

deployment-status-posthog Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-10-28 14:44 UTC Run
prod-us ✅ Deployed 2026-10-28 15:24 UTC Run
prod-eu ✅ Deployed 2026-10-28 15:21 UTC Run

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.

2 participants