Skip to content

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

Open
07rjain wants to merge 5 commits into
CapSoftware:mainfrom
07rjain:split/email-delivery-status
Open

07rjain wants to merge 5 commits into
CapSoftware:mainfrom
07rjain:split/email-delivery-status

Conversation

@07rjain

@07rjain 07rjain commented Oct 23, 2026 •

Copy link
Copy Markdown

Split out of #2580 (email delivery status).

Self-hosted installs 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. The verification screen and resend toast both 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. Only genuine delivery failures are cleaned up.
  • Mobile and v1 invite APIs expose not_configured delivery status.
  • Production Cap Cloud login fails when email is unconfigured, in both mobile and NextAuth flows, without logging a successful sign-in code.
  • Main's new mobile tests mock getEmailDeliveryStatus and cover unconfigured self-hosted and hosted behavior. The vacuous sender test is removed.

Main has been merged.

Validation: 74 focused email/configuration, mobile-session, authentication, NextAuth route and invite-delivery tests pass. Scoped Biome checks and the repository's bun run typecheck pass.

RetriggerView in GreptileConfidence Score: 4/5

The PR appears safe to merge, with two non-blocking messages that still claim skipped emails were sent.

Findings

  1. P2 Resend still promises an email ▶
  2. P2 Skipped invites look sent ▶
Fix with agent prompt
### Issue 1
apps/web/app/(org)/verify-otp/form.tsx:176-184
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.

### Issue 2
apps/web/actions/organization/send-invites.ts:190-191
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.

Summary

The PR stops self-hosted email from using auth@undefined and reports missing email settings.

  • Self-hosted email only sends with a usable sender.
  • Login screens point users to logs when email is off.
  • Invites stay usable when email is not configured.
  • Support request results reflect whether email was sent.

07rjain explicitly intends login codes to appear in server logs when email is not configured.

Reviews (1) · Last reviewed commit: "fix(email): never send from auth@undefin..." · 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 23, 2026 11:57pm 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

This branch was previously deployed

1 inactive deployment
Preview – cap-status — 7de36ea9 Deployed Oct 23, 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