-
Notifications
You must be signed in to change notification settings - Fork 498
Email outbox backend #1030
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Email outbox backend #1030
Conversation
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (8)
docker/server/entrypoint.sh (1)
77-98: Unresolved: path quoting and word-splitting risks remain.The past review flagged two outstanding issues in this section that still need attention:
- Line 97:
find $WORK_DIR/appsis unquoted. Should befind "$WORK_DIR/apps"to safely handle paths with spaces.- Line 77:
for sentinel in $unhandled_sentinels; dorisks word-splitting if$unhandled_sentinelscontains newlines or whitespace. Although sentinels match[A-Z_], the pattern is fragile. Consider a newline-safe loop:-for sentinel in $unhandled_sentinels; do +while IFS= read -r sentinel; do + [ -z "$sentinel" ] && continueAnd update the
done:-done +done <<EOF +$unhandled_sentinels +EOF.github/workflows/docker-server-build-run.yaml (1)
35-76: Health-check hardening looks good; this addresses prior feedback.
You’re now (1) checking backend via/health, and (2) guarding against curl failures + empty status codes + quoting vars—matching the earlier review concerns.apps/e2e/tests/backend/endpoints/api/v1/emails/email-queue.test.ts (2)
101-101: Fixed sleep assertions can yield false positives.These tests use fixed
wait()delays followed by "no messages" assertions. This pattern cannot distinguish between "email correctly skipped" and "processor never ran," making tests both flaky and prone to false positives.Consider driving the processor explicitly or asserting against outbox status via the delivery APIs to confirm skip logic.
Also applies to: 166-166, 228-228, 301-301
Also applies to: 166-166, 228-228, 301-301
331-333: Placeholder snapshot must be replaced.The snapshot placeholder
"todo"needs to be replaced with the actual expected response body after running the test.apps/backend/src/lib/email-queue-step.tsx (2)
224-231: Verify themeProps is accepted by rendering function.The
buildRenderRequestincludesthemeProps.projectLogosin the input object. Past review flagged thatrenderEmailsForTenancyBatchedmay not accept this field in its type signature.Verify that the rendering function accepts
themePropsin the input:#!/bin/bash # Check the RenderEmailRequestForTenancy type definition rg -nP --type=ts -A10 'type\s+RenderEmailRequestForTenancy|interface\s+RenderEmailRequestForTenancy' apps/backend/src/lib/email-rendering.tsx
79-94: Missing automatic recovery for emails stuck in sending.
logEmailsStuckInSendingonly logs emails stuck in the sending state, whileretryEmailsStuckInRendering(lines 59-77) actively resets stuck rendering jobs. This asymmetry means emails that crash during sending remain stuck indefinitely withstartedSendingAtset butfinishedSendingAtnull, requiring manual intervention.Consider adding automatic recovery for stuck sending jobs similar to rendering.
apps/backend/prisma/schema.prisma (1)
726-729: Verify bidirectional constraints are enforced in migration.The comments document bidirectional constraints for
emailDraftIdandemailProgrammaticCallTemplateId:
- emailDraftId: "must be set if and only if createdWith is DRAFT"
- emailProgrammaticCallTemplateId: "Must be NOT set if createdWith is NOT PROGRAMMATIC_CALL"
Past review noted the migration may only enforce one direction of these constraints.
Verify the migration includes both directions of the constraint:
#!/bin/bash # Check if both directions of emailDraftId constraint exist rg -nP 'email_draft.*CHECK|EmailOutbox_email_draft' apps/backend/prisma/migrations/20251020180000_email_outbox/migration.sqlapps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsx (1)
67-68: Arrays accumulate duplicates from SQL join.The SQL query produces a row per failed email per team member. Both
emails(line 67) andtenantOwnerEmails(line 68) accumulate duplicate entries. The TODO comment acknowledges this but the issue remains unresolved.Consider deduplicating before the loop or using a Map/Set to track unique entries.
🧹 Nitpick comments (2)
.github/workflows/docker-server-build-run.yaml (1)
22-24: Avoidpostgres:latestin CI; pin and clean up containers.
Usingpostgres:latestcan break unpredictably. Pin to a major/minor (or at least major) version aligned with prod/dev (e.g.postgres:16). Also consider cleaning up thedbcontainer (andstackframe-server) at the end (or viatrap) to reduce flakiness on runners that reuse Docker state.apps/backend/src/lib/emails.tsx (1)
62-62: Remove unnecessary non-null assertion.
serializeRecipient(recipient)!uses a non-null assertion, but the function always returnsJson(nevernull). The assertion is unnecessary.Apply this diff:
- to: serializeRecipient(recipient)!, + to: serializeRecipient(recipient),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (10)
.github/workflows/docker-server-build-run.yaml(1 hunks)apps/backend/prisma/migrations/20251020180000_email_outbox/migration.sql(1 hunks)apps/backend/prisma/schema.prisma(4 hunks)apps/backend/src/app/api/latest/emails/send-email/route.tsx(5 hunks)apps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsx(4 hunks)apps/backend/src/lib/email-queue-step.tsx(1 hunks)apps/backend/src/lib/emails.tsx(3 hunks)apps/e2e/package.json(1 hunks)apps/e2e/tests/backend/endpoints/api/v1/emails/email-queue.test.ts(1 hunks)docker/server/entrypoint.sh(1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Always add new E2E tests when changing the API or SDK interface
For blocking alerts and errors, never use toast; use alerts instead as they are less easily missed by the user
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error); use loading indicators and async callbacks instead, or use runAsynchronously/runAsynchronouslyWithAlert for error handling
Use ES6 maps instead of records wherever you can
Files:
apps/backend/src/lib/email-queue-step.tsxapps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsxapps/e2e/tests/backend/endpoints/api/v1/emails/email-queue.test.tsapps/backend/src/app/api/latest/emails/send-email/route.tsxapps/backend/src/lib/emails.tsx
**/*.{ts,tsx,css}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,css}: Keep hover/click transitions snappy and fast; avoid fade-in delays on hover. Apply transitions after action completion instead, like smooth fade-out when hover ends
Use hover-exit transitions instead of hover-enter transitions; for example, use 'transition-colors hover:transition-none' instead of fade-in on hover
Files:
apps/backend/src/lib/email-queue-step.tsxapps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsxapps/e2e/tests/backend/endpoints/api/v1/emails/email-queue.test.tsapps/backend/src/app/api/latest/emails/send-email/route.tsxapps/backend/src/lib/emails.tsx
apps/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
NEVER use Next.js dynamic functions if you can avoid them; prefer using client components and hooks like usePathname instead of await params to keep pages static
Files:
apps/backend/src/lib/email-queue-step.tsxapps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsxapps/e2e/tests/backend/endpoints/api/v1/emails/email-queue.test.tsapps/backend/src/app/api/latest/emails/send-email/route.tsxapps/backend/src/lib/emails.tsx
{.env*,**/*.{ts,tsx,js}}
📄 CodeRabbit inference engine (AGENTS.md)
Prefix environment variables with STACK_ (or NEXT_PUBLIC_STACK_ if public) so changes are picked up by Turborepo and improves readability
Files:
apps/backend/src/lib/email-queue-step.tsxapps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsxapps/e2e/tests/backend/endpoints/api/v1/emails/email-queue.test.tsapps/backend/src/app/api/latest/emails/send-email/route.tsxapps/backend/src/lib/emails.tsx
apps/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Check existing apps for inspiration when implementing new apps or pages; update apps-frontend.tsx and apps-config.ts to add new apps
Files:
apps/backend/src/lib/email-queue-step.tsxapps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsxapps/backend/src/app/api/latest/emails/send-email/route.tsxapps/backend/src/lib/emails.tsx
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Prefer .toMatchInlineSnapshot over other selectors in tests when possible; check snapshot-serializer.ts for formatting details
Files:
apps/e2e/tests/backend/endpoints/api/v1/emails/email-queue.test.ts
apps/e2e/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Always add new E2E tests when changing API or SDK interface; err on the side of creating too many tests due to the critical nature of the industry
Files:
apps/e2e/tests/backend/endpoints/api/v1/emails/email-queue.test.ts
🧠 Learnings (5)
📚 Learning: 2025-12-04T18:03:49.984Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T18:03:49.984Z
Learning: Applies to **/*.{ts,tsx} : Always add new E2E tests when changing the API or SDK interface
Applied to files:
apps/e2e/package.jsonapps/e2e/tests/backend/endpoints/api/v1/emails/email-queue.test.ts
📚 Learning: 2025-12-04T18:03:49.984Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T18:03:49.984Z
Learning: Applies to apps/e2e/**/*.{ts,tsx} : Always add new E2E tests when changing API or SDK interface; err on the side of creating too many tests due to the critical nature of the industry
Applied to files:
apps/e2e/package.jsonapps/e2e/tests/backend/endpoints/api/v1/emails/email-queue.test.ts
📚 Learning: 2025-12-04T18:03:49.984Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T18:03:49.984Z
Learning: Run pnpm install to install dependencies, pnpm test run for testing with Vitest, pnpm lint for linting (use --fix flag to auto-fix), pnpm typecheck for type checking
Applied to files:
apps/e2e/package.json
📚 Learning: 2025-12-04T18:03:49.984Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T18:03:49.984Z
Learning: Applies to **/*.test.{ts,tsx} : Prefer .toMatchInlineSnapshot over other selectors in tests when possible; check snapshot-serializer.ts for formatting details
Applied to files:
apps/e2e/tests/backend/endpoints/api/v1/emails/email-queue.test.ts
📚 Learning: 2025-12-04T18:03:49.984Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T18:03:49.984Z
Learning: Applies to packages/stack-shared/src/config/schema.ts : Whenever making backwards-incompatible changes to the config schema, update the migration functions in packages/stack-shared/src/config/schema.ts
Applied to files:
apps/backend/src/app/api/latest/emails/send-email/route.tsx
🧬 Code graph analysis (2)
apps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsx (1)
apps/backend/src/lib/emails.tsx (1)
EmailOutboxRecipient(21-24)
apps/e2e/tests/backend/endpoints/api/v1/emails/email-queue.test.ts (6)
apps/e2e/tests/helpers/ports.ts (1)
withPortPrefix(6-6)packages/stack-shared/src/utils/strings.tsx (1)
deindent(235-238)apps/e2e/tests/helpers.ts (1)
it(12-12)apps/e2e/tests/backend/backend-helpers.ts (3)
backendContext(35-57)niceBackendFetch(109-173)bumpEmailAddress(179-187)packages/stack-shared/src/utils/promises.tsx (1)
wait(260-268)packages/stack-shared/src/utils/html.tsx (1)
html(30-32)
🪛 actionlint (1.7.9)
.github/workflows/docker-server-build-run.yaml
16-16: label "ubicloud-standard-8" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xl", "macos-latest-xlarge", "macos-latest-large", "macos-26-xlarge", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xl", "macos-14-xlarge", "macos-14-large", "macos-14", "macos-13-xl", "macos-13-xlarge", "macos-13-large", "macos-13", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file
(runner-label)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: Vercel Agent Review
- GitHub Check: Cursor Bugbot
- GitHub Check: build (22.x)
- GitHub Check: build (22.x)
- GitHub Check: lint_and_build (latest)
- GitHub Check: build (22.x)
- GitHub Check: setup-tests
- GitHub Check: restart-dev-and-test
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: all-good
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: docker
🔇 Additional comments (8)
docker/server/entrypoint.sh (1)
3-3: Good catch removing-xfromsetoptions.Removing the
-xflag fromset -exo pipefail→set -eo pipefailcorrectly prevents bash xtrace from leaking the generated secrets (lines 14–16) and environment variable values into Docker logs. This is a solid security fix.apps/e2e/package.json (2)
18-20: No action needed. The E2E test suite has a clean, single-point env-loading pattern viaglobal-setup.ts, which explicitly callsdotenv.config()with specific file paths. Vitest doesn't auto-load.envfiles without explicit configuration (not present), so there is no double-loading or conflict with Vitest/Vite env behavior. Bothdotenvandjs-beautifyare actively used:dotenvinglobal-setup.tsandjs-beautifyinemail-queue.test.tsfor HTML snapshot formatting.
19-23:js-beautify+@types/js-beautifyusage is appropriate and already stable.The dependency is used in
email-queue.test.tsto format HTML for snapshot comparisons via.toMatchInlineSnapshot(). Sincebeautify.html()is called with default options at a single location, output is already normalized and stable—no separate helper needed.Verify this PR introduces no breaking changes to email API or SDK interfaces that would require additional E2E test coverage per the guidelines.
apps/backend/prisma/migrations/20251020180000_email_outbox/migration.sql (1)
59-60: LGTM! Status logic correctly keys off error fields.The RENDER_ERROR condition now checks
renderErrorExternalMessage IS NOT NULLinstead ofrenderedHtml IS NULL, which properly handles text-only renders and aligns with the constraint logic.apps/backend/src/lib/email-queue-step.tsx (1)
592-602: LGTM! Empty email array now properly skipped.The code correctly handles the case where
user-custom-emailshas an empty array by checkingemails.length === 0and returningNO_EMAIL_PROVIDEDskip reason (line 595). This addresses the previous concern about empty arrays being marked as successfully sent.apps/backend/src/app/api/latest/emails/send-email/route.tsx (1)
143-160: LGTM! Duplicate user IDs properly handled.The code now deduplicates
user_idsvia[...new Set(body.user_ids)](line 144) before validation, preventing falseUserIdDoesNotExisterrors when duplicates are present in the request.apps/backend/src/lib/emails.tsx (1)
92-92: LGTM! Priority correctly set to true for default templates.The
isHighPriorityflag is now unconditionallytruefor emails sent viasendEmailFromDefaultTemplate, ensuring these critical emails (verification, password reset, etc.) jump the queue as intended.apps/backend/prisma/schema.prisma (1)
798-800: LGTM! sentAt comment now matches generated expression.The comment correctly describes the generated column logic: when
canHaveDeliveryInfois true,sentAtequalsdeliveredAt; when false, it equalsfinishedSendingAt. This aligns with the dbgenerated CASE expression.
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/backend/src/auto-migrations/index.tsx (1)
104-196: Avoid sleeping while holding the advisory xact lock (and a DB connection)
await wait(500)runs inside the Prisma transaction, so it holdspg_advisory_xact_lock(...)and the transaction connection for the extra 500ms, which can amplify contention/timeouts when multiple workers race migrations. Prefer committing first, then sleeping before the next retry.await options.prismaClient.$transaction(async (tx) => { + let requestedRepeatDelayMs: number | null = null; log(` |> Preparing...`); await tx.$executeRaw` SELECT pg_advisory_xact_lock(${MIGRATION_LOCK_ID}); `; @@ if (res[0].should_repeat_migration) { log(` |> Migration ${migration.migrationName} requested to be repeated. This is normal and *not* indicative of a problem.`); - await wait(500); // give the database a chance to catch up with everything else that's happening + requestedRepeatDelayMs = 500; // delay after commit to avoid holding locks/connections // Commit the transaction and continue re-running the migration return; } @@ newlyAppliedMigrationNames.push(migration.migrationName); shouldRepeat = false; - }, { + if (requestedRepeatDelayMs != null) { + // keep shouldRepeat=true, but sleep outside of the transaction + } + }, { timeout: 80_000, maxWait: 30_000, }); + // If we returned early to repeat, wait here after commit/connection release. + if (shouldRepeat) { + await wait(500); + } } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/backend/prisma/migrations/20251020183000_migrate_sent_email/migration.sql(1 hunks)apps/backend/src/auto-migrations/index.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/backend/prisma/migrations/20251020183000_migrate_sent_email/migration.sql
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Always add new E2E tests when changing the API or SDK interface
For blocking alerts and errors, never use toast; use alerts instead as they are less easily missed by the user
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error); use loading indicators and async callbacks instead, or use runAsynchronously/runAsynchronouslyWithAlert for error handling
Use ES6 maps instead of records wherever you can
Files:
apps/backend/src/auto-migrations/index.tsx
**/*.{ts,tsx,css}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,css}: Keep hover/click transitions snappy and fast; avoid fade-in delays on hover. Apply transitions after action completion instead, like smooth fade-out when hover ends
Use hover-exit transitions instead of hover-enter transitions; for example, use 'transition-colors hover:transition-none' instead of fade-in on hover
Files:
apps/backend/src/auto-migrations/index.tsx
apps/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
NEVER use Next.js dynamic functions if you can avoid them; prefer using client components and hooks like usePathname instead of await params to keep pages static
Files:
apps/backend/src/auto-migrations/index.tsx
{.env*,**/*.{ts,tsx,js}}
📄 CodeRabbit inference engine (AGENTS.md)
Prefix environment variables with STACK_ (or NEXT_PUBLIC_STACK_ if public) so changes are picked up by Turborepo and improves readability
Files:
apps/backend/src/auto-migrations/index.tsx
apps/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
Check existing apps for inspiration when implementing new apps or pages; update apps-frontend.tsx and apps-config.ts to add new apps
Files:
apps/backend/src/auto-migrations/index.tsx
🧠 Learnings (1)
📚 Learning: 2025-12-04T18:03:49.984Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T18:03:49.984Z
Learning: Applies to packages/stack-shared/src/config/schema.ts : Whenever making backwards-incompatible changes to the config schema, update the migration functions in packages/stack-shared/src/config/schema.ts
Applied to files:
apps/backend/src/auto-migrations/index.tsx
🧬 Code graph analysis (1)
apps/backend/src/auto-migrations/index.tsx (1)
packages/stack-shared/src/utils/promises.tsx (1)
wait(260-268)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: Vercel Agent Review
- GitHub Check: Cursor Bugbot
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: docker
- GitHub Check: build (22.x)
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: build (22.x)
- GitHub Check: restart-dev-and-test
- GitHub Check: lint_and_build (latest)
- GitHub Check: setup-tests
- GitHub Check: build (22.x)
- GitHub Check: all-good
🔇 Additional comments (1)
apps/backend/src/auto-migrations/index.tsx (1)
4-4:waitimport path/version stabilityImporting from
@stackframe/stack-shared/dist/...is fine if that path is treated as public API; otherwise it’s easy to break on package/build changes. Consider importing from a stable/public entrypoint if one exists.
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
apps/backend/prisma/migrations/20251212183000_migrate_sent_email/migration.sql (1)
4-15: Optional: make batching deterministic to reduce long-tail/dup work
Consider adding anORDER BY se."id"(or createdAt/id) into_migrateto avoid non-deterministic LIMIT selection across runs.apps/backend/prisma/migrations/20251212180000_email_outbox/migration.sql (2)
92-95: Priority ordering vs indexes:priority ASClikely fights “high priority first”
priorityassigns 100 for high-priority (Line 93), butEmailOutbox_sending_idxis(tenancyId, priority, scheduledAt)(Line 248) andEmailOutbox_ordering_idxstorespriority ASC(Line 256). If the processor selects “highest priority first”, these indexes won’t support that ordering well (and may encourage lowest-priority-first scans).Also applies to: 248-258
251-257: Double-checkscheduledAtIfNotYetQueued DESCaligns with “send oldest first”
scheduledAtIfNotYetQueuedisscheduledAtwhen not queued (Line 109-111), butordering_idxusesscheduledAtIfNotYetQueued DESC(Line 255). If you intend FIFO by scheduled time, this likely wantsASC(or you need matching query ORDER BY).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/backend/prisma/migrations/20251212180000_email_outbox/migration.sql(1 hunks)apps/backend/prisma/migrations/20251212183000_migrate_sent_email/migration.sql(1 hunks)apps/backend/prisma/migrations/20251212185000_add_no_email_provided_skip_reason/migration.sql(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/backend/prisma/migrations/20251212185000_add_no_email_provided_skip_reason/migration.sql
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: build (22.x)
- GitHub Check: lint_and_build (latest)
- GitHub Check: build (22.x)
- GitHub Check: build (22.x)
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: restart-dev-and-test
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: docker
- GitHub Check: setup-tests
- GitHub Check: all-good
- GitHub Check: Vercel Agent Review
- GitHub Check: Cursor Bugbot
🔇 Additional comments (2)
apps/backend/prisma/migrations/20251212183000_migrate_sent_email/migration.sql (2)
1-3: The migration runner properly gates statements 2/3 (INSERT metadata and DROP TABLE) on statement 1 completion. When statement 1 returnsshould_repeat_migration = true, line 155 ofindex.tsxreturns from the transaction callback, preventing execution of remaining statements in that iteration. Statements 2/3 only execute aftershould_repeat_migration = false. TheSPLIT_STATEMENT_SENTINELandCONDITIONALLY_REPEAT_MIGRATION_SENTINELare control-flow markers (not just SQL comments) that the runner uses to gate conditional logic—the outer loop (for (let repeat = 0; shouldRepeat; repeat++)at line 105) re-runs the migration until completion. This design eliminates the risk of prematureDROP TABLEexecution.Likely an incorrect or invalid review comment.
67-76: The legacy recipient mapping is correct and safe.SentEmail.tois defined asTEXT[](PostgreSQL text array of email address strings), andto_jsonb(se."to")correctly converts it to a JSONB array of strings, which matches the downstreamEmailOutbox.tocontract expecting{ type: "custom-emails", emails: string[] }. The concern about potential {email,name} object mismatches is invalid since the source data structure is always a string array, not objects with multiple properties.Likely an incorrect or invalid review comment.
apps/backend/prisma/migrations/20251212180000_email_outbox/migration.sql
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Unused fetch could throw if email env vars missing
The variables internalTenancy and emailConfig are fetched unconditionally but never used because the email sending functionality is disabled (line 72 throws immediately). The getSharedEmailConfig call invokes getEnvVariable for email configuration variables (STACK_EMAIL_HOST, etc.) without defaults, which will throw an error if those environment variables aren't set. This causes the endpoint to fail with a confusing error about missing email config even though email sending is disabled. These fetches should be removed or moved inside the conditional block.
apps/backend/src/app/api/latest/internal/failed-emails-digest/route.ts#L46-L48
stack-auth/apps/backend/src/app/api/latest/internal/failed-emails-digest/route.ts
Lines 46 to 48 in 1a4f46e
| const failedEmailsByTenancy = await getFailedEmailsByTenancy(new Date(Date.now() - 1000 * 60 * 60 * 24)); | |
| const internalTenancy = await getSoleTenancyFromProjectBranch("internal", DEFAULT_BRANCH_ID); | |
| const emailConfig = await getSharedEmailConfig("Stack Auth"); |
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
apps/backend/prisma/migrations/20251212183000_migrate_sent_email/migration.sql (2)
7-9: DroppingSentEmailwill discard “orphaned tenancy” rows (if any) without migrating them.
TheWHERE EXISTS (Tenancy…)filter intentionally skips anySentEmailwhosetenancyIdno longer exists, butDROP TABLE … "SentEmail"will still remove them. If that’s intended cleanup, consider making it explicit (e.g., pre-delete with a comment / sanity-count), or gate the DROP on “no remaining rows”.Also applies to: 140-140
14-15: Add a deterministic ORDER BY to the 10k batching selection.
LIMIT 10000withoutORDER BYmakes each batch nondeterministic, which can complicate debugging/rollbacks. Deterministic ordering is cheap here and keeps re-runs predictable.WITH to_migrate AS ( SELECT se."tenancyId", se."id" FROM "SentEmail" se @@ AND NOT EXISTS ( SELECT 1 FROM "EmailOutbox" eo WHERE eo."tenancyId" = se."tenancyId" AND eo."id" = se."id" ) + ORDER BY se."createdAt" ASC, se."tenancyId" ASC, se."id" ASC LIMIT 10000 ),Also applies to: 128-133
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/backend/prisma/migrations/20251212183000_migrate_sent_email/migration.sql(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
- GitHub Check: Vercel Agent Review
- GitHub Check: Cursor Bugbot
- GitHub Check: build (22.x)
- GitHub Check: lint_and_build (latest)
- GitHub Check: all-good
- GitHub Check: build (22.x)
- GitHub Check: restart-dev-and-test
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: docker
- GitHub Check: setup-tests
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: build (22.x)
🔇 Additional comments (2)
apps/backend/prisma/migrations/20251212183000_migrate_sent_email/migration.sql (2)
1-3: Sentinel placement is correct — the runner splits bySPLIT_STATEMENT_SENTINEL, so the first statement (lines 1–133) withCONDITIONALLY_REPEAT_MIGRATION_SENTINELrepeats in full, and the second statement (lines 136–140) without the sentinel executes once. The DROP at line 140 is safely outside the repeat block and will not execute after the first 10k batch.
88-88: No action needed. PostgreSQL 16.1 includesgen_random_uuid()as a built-in function (available since PostgreSQL 13), so pgcrypto extension is not required. This usage is consistent with the project's Prisma schema, which already uses the same function without extension setup.Likely an incorrect or invalid review comment.
Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
I'm sorry for whoever reviews this. I tried to split it up as much as possible so this is only the existing functionality migrated onto the new email outbox table; the new endpoints or UI changes are not here yet.
Tests not passing yet but already ready for reviews.
How to review:
The prompt that generated the first version (but note that the final version is somewhat different from what was generated, but it might help get some overview):
Note
Migrates email to an async outbox pipeline with queueing/rendering, delivery stats, updated APIs, and extensive tests.
EmailOutboxschema with generated columns, constraints, and indices; migrate data fromSentEmailand drop it.sendEmailToMany, batch render (Freestyle), queue, and send (low-level SMTP/provider) with robust error handling and skip reasons.runEmailQueueStepand internal endpoint/api/latest/internal/email-queue-stepwith locking, delta-based capacity, and stochastic quotas.POST /api/latest/emails/send-email(per-user enqueue, variables JSON, priority/scheduling, notification category validation).GET /api/latest/emails/delivery-info(hour/day/week/month stats and capacity).getEmailDeliveryStatsanduseEmailDeliveryStats; server interface method added.Written by Cursor Bugbot for commit 313c229. This will update automatically on new commits. Configure here.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.