Skip to content

fix(email): don't send from auth@undefined and report when email isn't configured - #3003

Merged
richiemcilroy merged 9 commits into
CapSoftware:mainfrom
07rjain:split/email-delivery-status
Oct 24, 2026
Merged

richiemcilroy merged 9 commits into
CapSoftware:mainfrom
07rjain:split/email-delivery-status

Conversation

@07rjain

@07rjain 07rjain commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Split out of #2580 (email delivery status).

Self-hosted installations with a Resend key but no sender domain could send from auth@undefined while the UI claimed success. Email configuration and provider errors are now reported explicitly; an explicit sender override still works without a default domain.

  • Self-hosted login codes go to server logs when email is unavailable. Both the verification screen and resend toast reflect that delivery mode.
  • Organization invite results return skippedEmails separately from failedEmails. Skipped invite records remain usable, and the dialog tells admins the invites were created but not emailed and to share their links. Failed delivery warnings do not claim emails were sent, including batches with both skipped and failed messages. Only genuine delivery failures are cleaned up.
  • The v1 invitation API exposes not_configured delivery status. Mobile email sign-in uses the shared configuration check.
  • Production Cap Cloud login fails when email is unconfigured in both mobile and NextAuth flows, without printing a successful sign-in code.
  • Support notification results reflect provider errors. Messenger support errors reach its existing failure handler rather than reporting an unsent message as sent.
  • Mobile email tests mock getEmailDeliveryStatus and cover unconfigured self-hosted and hosted behavior. The vacuous sender test is removed.

The branch includes current main at 3614bdb692af7317c30c0ad7afaa496a6974c2eb, integrating 174 upstream commits without conflicts.

Validation: 107 focused tests passed across nine suites covering configuration/senders, mobile email sessions, authentication, NextAuth routes, invite record retention and cleanup, actual resend/invitation UI interactions, account deletion notifications, and Messenger support delivery and failure handling. The new mixed-batch UI regression and both support provider-error regressions failed before their fixes and passed afterward. Repository bun run typecheck, scoped Biome checks and self-hosting guide MDX compilation passed. No real emails were sent during validation.

All original reviewer findings and the follow-up P1 about NEXT_PUBLIC_IS_CAP="false" are addressed. Only "true" enables Cap Cloud email behavior; unset, empty and "false" values retain self-hosted login-code logging, sender-domain requirements and marketing suppression. Five explicit-false regressions failed before the fix and pass afterward. Review of this head and maintainer approval for fork CI remain pending.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; all three previous findings are addressed and no new blocking issue was found.

Summary

The PR reports missing email configuration and provider errors instead of claiming delivery succeeded.

  • Email calls now report setup gaps and provider rejections.
  • Login flows show when codes are logged instead of emailed.
  • Organization invites distinguish skipped email from failed delivery.
  • Notification callers now report when email delivery fails.

Reviews (3) · Last reviewed commit: "fix(email): require an explicit Cap Clou..." · Reviewed by Greptile

… and surface send failures

Self-hosts without RESEND_FROM_DOMAIN sent from auth@undefined, which Resend rejects, while the UI still said a code was sent. Email is now treated as configured only when the API key and sender are both available, login codes are logged otherwise, the verify screen says email is not configured, explicit fromOverride senders keep working without a default domain, and invite APIs report not_configured instead of accepted.
@vercel

vercel Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

@07rjain is attempting to deploy a commit to the Cap Software Inc Team on Vercel.

A member of the Team first needs to authorize it.

Comment on lines +176 to +184
{emailDeliveryConfigured ? (
<p className="text-sm text-gray-10">
We sent a 6-digit code to {normalizedEmail}
</p>
) : (
<p className="text-sm text-gray-10">
Email (Resend) is not configured on this server. The 6-digit code
for {normalizedEmail} was printed to the server logs. Ask your
administrator, or set RESEND_API_KEY and RESEND_FROM_DOMAIN.

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.

P2 Resend still promises an email

The new emailDeliveryConfigured flag fixes the page text, but the resend toast still says “A new code has been sent to your email!” when email is disabled. Requesting another code only prints it to the server logs. Use the flag in handleResend.onSuccess too, so users are not sent back to their inbox.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/app/(org)/verify-otp/form.tsx
Line: 176-184

Comment:
**Resend still promises an email**

The new `emailDeliveryConfigured` flag fixes the page text, but the resend toast still says “A new code has been sent to your email!” when email is disabled. Requesting another code only prints it to the server logs. Use the flag in `handleResend.onSuccess` too, so users are not sent back to their inbox.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +190 to +191
const error = result.value?.error;
return Boolean(error && error.name !== "email_not_configured");

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.

P2 Skipped invites look sent

Excluding email_not_configured from failedInvites keeps the invite links usable, but it also leaves failedEmails empty. InviteDialog then shows “Invites sent successfully” without warning that no email was sent. Return a separate delivery status and show the skipped-email notice without deleting the invite records.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/actions/organization/send-invites.ts
Line: 190-191

Comment:
**Skipped invites look sent**

Excluding `email_not_configured` from `failedInvites` keeps the invite links usable, but it also leaves `failedEmails` empty. `InviteDialog` then shows “Invites sent successfully” without warning that no email was sent. Return a separate delivery status and show the skipped-email notice without deleting the invite records.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@richiemcilroy

Copy link
Copy Markdown
Member

Thanks @07rjain for splitting this out of #2580, the auth@undefined fix and returning Resend's error instead of swallowing it are both good changes. A few things before we can merge:

  1. Resend toast still says the code was emailed (apps/web/app/(org)/verify-otp/form.tsx:138). Greptile is right here: the heading now explains that email isn't configured, but resending still shows "A new code has been sent to your email!". Please use emailDeliveryConfigured in onSuccess and show something like "A new code was printed to the server logs" when it's false.

  2. Skipped invites still look sent (apps/web/actions/organization/send-invites.ts:187-193). Also valid. Keeping email_not_configured invites out of failedInvites is right (the links stay usable), but InviteDialog.tsx:117-126 then shows "Invites sent successfully". Since the point of this PR is to report when email isn't configured, could you return those emails separately (for example skippedEmails, or an emailNotConfigured: true flag) and have the dialog say the invites were created but not emailed, so the admin knows to share the links?

  3. The new mobile email tests on main will break after merging main. fix(web): apply rate limits to email sign-in codes #2994 added apps/web/__tests__/unit/mobile-email-session.test.ts, which mocks @cap/database/emails/config as { sendEmail } only. With this PR the mobile route calls getEmailDeliveryStatus(), which isn't on that mock, so every send in those tests throws and the route returns 500. Please merge main and add getEmailDeliveryStatus: () => ({ configured: true }) to that mock (a case with { configured: false } that checks the code is logged and sendEmail isn't called would be nice too).

  4. Keep failing on Cap Cloud when the key is missing (apps/web/app/api/mobile/[...route]/route.ts:156). Main deliberately throws in production when RESEND_API_KEY is missing, and this PR drops that, so a hosted build with a missing key would write sign-in codes to the logs and report success. Logging the code is fine for self-hosters, but please keep the throw when buildEnv.NEXT_PUBLIC_IS_CAP is set (and ideally do the same in sendVerificationRequest in packages/database/auth/auth-options.ts).

  5. Small one: the never sends from auth@undefined test in apps/web/__tests__/unit/email-config.test.ts:52 passes without checking anything, because send is never called when RESEND_FROM_DOMAIN is missing (the previous test already covers that). Feel free to drop it or fold it into the previous case.

Once that's pushed, it needs to reach Greptile 5/5 with green CI before we can merge. You can mention @greptileai in a comment to get a re-review after pushing. Thanks!

@greptile-apps

greptile-apps Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Agreed—these are valid follow-ups. The resend toast should reflect emailDeliveryConfigured; invite results should distinguish unconfigured delivery while preserving the invite records and let the dialog tell admins to share the links; and the mobile test mock should include getEmailDeliveryStatus() when that test from #2994 is brought in.

The missing-key behavior also needs to retain the production failure for Cap Cloud (buildEnv.NEXT_PUBLIC_IS_CAP) in both the mobile route and sendVerificationRequest; otherwise sign-in codes can be logged while the hosted flow appears successful. The never sends from auth@undefined test is currently vacuous and can be removed or folded into the preceding missing-domain test.

These changes need to be pushed and CI rerun before merge. Once the updated revision is available, it can be re-reviewed.

@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
cap-status Skipped Skipped Oct 24, 2026 9:52pm UTC

@vercel
vercel Bot temporarily deployed to Preview – cap-status October 23, 2026 23:36 Inactive
@vercel
vercel Bot temporarily deployed to Preview – cap-status October 23, 2026 23:36 Inactive
@vercel
vercel Bot temporarily deployed to Preview – cap-status October 23, 2026 23:57 Inactive
Avoid claiming invitations were sent in mixed skipped and failed batches. Propagate returned support email errors to the existing failure handler. Cover resend destinations, invitation notices and support provider errors with regression tests.
@vercel
vercel Bot temporarily deployed to Preview – cap-status October 24, 2026 08:47 Inactive
@07rjain

07rjain commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review the latest changes on commit 7543750. The branch includes current main and fixes for the resend toast, skipped-invite notices, mixed delivery results, Cap Cloud missing-email behavior, and support delivery errors. The displayed 4/5 review predates these fixes. Validation: 97 focused tests and repository typecheck pass.

Comment on lines +264 to +268
if (
process.env.NODE_ENV === "production" &&
buildEnv.NEXT_PUBLIC_IS_CAP
) {
throw new Error(emailStatus.reason);

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.

P1 Self-hosted sign-in gets blocked

Production self-hosts with NEXT_PUBLIC_IS_CAP="false" and no Resend key now fail email sign-in instead of printing a code. NEXT_PUBLIC_IS_CAP is an optional string, so "false" passes this new Cap Cloud check. The same check in sendMobileEmailCode blocks mobile sign-in too.

Compare the flag with "true" in both places so self-hosts keep the promised server-log fallback.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/database/auth/auth-options.ts
Line: 264-268

Comment:
**Self-hosted sign-in gets blocked**

Production self-hosts with `NEXT_PUBLIC_IS_CAP="false"` and no Resend key now fail email sign-in instead of printing a code. `NEXT_PUBLIC_IS_CAP` is an optional string, so `"false"` passes this new Cap Cloud check. The same check in `sendMobileEmailCode` blocks mobile sign-in too.

Compare the flag with `"true"` in both places so self-hosts keep the promised server-log fallback.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Treat NEXT_PUBLIC_IS_CAP=false as self-hosted in web and mobile sign-in, email configuration, sender selection and marketing suppression. Cover unset, empty, false and true values while preserving production Cap Cloud email failures.
@vercel
vercel Bot temporarily deployed to Preview – cap-status October 24, 2026 08:57 Inactive
@07rjain

07rjain commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review the latest changes on commit 22a860e. The self-hosted sign-in P1 is fixed: only NEXT_PUBLIC_IS_CAP="true" enables Cap Cloud behavior in web/mobile sign-in and email configuration. Explicit "false" keeps login-code logging, the configured self-host sender, and marketing suppression. Five regressions reproduced the issue before the fix; all 107 focused tests, repository typecheck and scoped Biome now pass.

@vercel
vercel Bot temporarily deployed to Preview – cap-status October 24, 2026 21:52 Inactive
@richiemcilroy
richiemcilroy merged commit 8bf56fe into CapSoftware:main Oct 24, 2026
31 of 32 checks passed

This branch was previously deployed

1 inactive deployment
Preview – cap-status — 34ee0acd Deployed Oct 24, 2026 by vercel[bot]
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