Skip to content

fix(auth): email verification for signup#149

Merged
seanhanca merged 2 commits into
mainfrom
fix/email-verification-for-sign-up
Feb 26, 2026
Merged

fix(auth): email verification for signup#149
seanhanca merged 2 commits into
mainfrom
fix/email-verification-for-sign-up

Conversation

@seanhanca
Copy link
Copy Markdown
Contributor

@seanhanca seanhanca commented Feb 24, 2026

Fixes #148

Summary

Email verification for account signup was not configured — users never received verification emails after registering.

Changes

  • Resend integration: Add lib/email.ts with Resend for sending verification and password reset emails
  • Wire register(): Call sendVerificationEmail(user.id) after creating user (non-blocking)
  • Professional templates: Livepeer community–friendly HTML email templates
  • Password reset: Wire requestPasswordReset to send real emails via Resend
  • Env config: Add RESEND_API_KEY and EMAIL_FROM to .env.example and .env.local.example
  • Dev fallback: Log links to console when RESEND_API_KEY is unset

Test plan

  • Resend API key validated (emails sent successfully)
  • Verification and reset email templates render and send
  • Dev fallback works when API key is missing
  • RESEND_API_KEY configured on Vercel for production

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Email verification is sent upon account registration.
    • Password reset emails are available to recover accounts.
    • Email delivery now uses a dedicated service for improved reliability.
  • Documentation

    • Example environment configuration updated with optional email settings (API key and default sender) to enable email flows.

@seanhanca seanhanca requested a review from eliteprox February 24, 2026 21:17
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Feb 24, 2026

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

Project Deployment Actions Updated (UTC)
naap-platform Ready Ready Preview, Comment Feb 26, 2026 9:29pm

Request Review

@github-actions github-actions Bot added the size/L Large PR (201-500 lines) label Feb 24, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 24, 2026

⚠️ This PR is large (350 lines changed). Consider splitting it into smaller PRs for easier review.

@github-actions github-actions Bot added the scope/shell Shell app changes label Feb 24, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 24, 2026

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

📥 Commits

Reviewing files that changed from the base of the PR and between ea0200b and 94ee096.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json, !package-lock.json
📒 Files selected for processing (5)
  • .env.example
  • apps/web-next/.env.local.example
  • apps/web-next/package.json
  • apps/web-next/src/lib/api/auth.ts
  • apps/web-next/src/lib/email.ts
 ________________________________________________________________________
< I'm CodeRabbit. I review the code you never knew you needed reviewing. >
 ------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ

✏️ Tip: You can disable in-progress messages and the fortune message in your review settings.

Tip

CodeRabbit can use Trivy to scan for security misconfigurations and secrets in Infrastructure as Code files.

Add a .trivyignore file to your project to customize which findings Trivy reports.

📝 Walkthrough

Walkthrough

Adds Resend-based email delivery: new email utility, integration into registration and password-reset flows, RESEND_API_KEY/EMAIL_FROM env placeholders, and the "resend" dependency—emails are sent for verification and password reset (with non-blocking sends and environment-based fallback logging).

Changes

Cohort / File(s) Summary
Environment Configuration
\.env.example, apps/web-next/.env.local.example
Inserted commented EMAIL configuration block with RESEND_API_KEY and EMAIL_FROM placeholders and explanatory comments for verification/password-reset emails.
Dependencies
apps/web-next/package.json
Added dependency: "resend": "^6.9.2".
Email Service Implementation
apps/web-next/src/lib/email.ts
New module implementing sendVerificationEmail(to, verifyUrl, displayName?) and sendPasswordResetEmail(to, resetUrl) using Resend client, HTML templates, error handling, and behavior when API key is absent.
Authentication Integration
apps/web-next/src/lib/api/auth.ts
Integrated email sending: registration fires off verification email (fire-and-forget, logs on failure), requestPasswordReset sends password-reset email (logs reset URL only in non-production on failure), and replaced prior console-only messaging with actual email sends.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WebApp
    participant AuthService
    participant ResendService

    User->>WebApp: Submit registration form
    WebApp->>AuthService: register(credentials)
    AuthService->>AuthService: create user & generate verify token
    AuthService->>ResendService: sendVerificationEmail(to, verifyUrl)
    ResendService->>ResendService: build HTML template
    ResendService->>ResendService: call Resend API
    ResendService-->>AuthService: success / error
    AuthService-->>WebApp: registration result
    WebApp-->>User: show confirmation
Loading
sequenceDiagram
    participant User
    participant WebApp
    participant AuthService
    participant ResendService

    User->>WebApp: Request password reset
    WebApp->>AuthService: requestPasswordReset(email)
    AuthService->>AuthService: generate reset token & URL
    AuthService->>ResendService: sendPasswordResetEmail(email, resetUrl)
    ResendService->>ResendService: build reset HTML
    ResendService->>ResendService: call Resend API
    ResendService-->>AuthService: success / error
    AuthService-->>WebApp: reset requested
    WebApp-->>User: instruct to check email
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix(auth): email verification for signup' directly addresses the main objective from issue #148 and accurately summarizes the primary change: implementing email verification in the signup flow.
Linked Issues check ✅ Passed The PR fully addresses the requirements in issue #148 by integrating Resend email service, sending verification emails on user signup, implementing password reset emails, and providing a development fallback when API keys are missing.
Out of Scope Changes check ✅ Passed All code changes are directly related to implementing email verification and password reset functionality required by issue #148, with no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/email-verification-for-sign-up

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread apps/web-next/src/lib/email.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
.env.example (1)

79-84: Email config is placed under the STORAGE section header.

The Resend email variables are documented under the # STORAGE section (Line 71). Consider adding a dedicated # EMAIL section or moving these above/below the storage block to avoid confusion, since email sending is functionally distinct from blob storage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.env.example around lines 79 - 84, The RESEND_EMAIL settings (RESEND_API_KEY
and EMAIL_FROM) are incorrectly documented under the STORAGE header; move those
lines into a new dedicated "# EMAIL" section (or place them adjacent to STORAGE
but under an "# EMAIL" header) so email config is clearly separated from blob
storage. Update the .env.example by cutting the RESEND_API_KEY and EMAIL_FROM
lines from under the STORAGE block and pasting them under a new "# EMAIL"
heading with the same comments, ensuring the variable names (RESEND_API_KEY,
EMAIL_FROM) remain unchanged.
apps/web-next/src/lib/email.ts (1)

13-16: getResendClient() creates a new Resend instance on every call.

This is a minor inefficiency. Consider caching the client in a module-level variable (lazy singleton) so it's only instantiated once per cold start.

♻️ Proposed refactor
-function getResendClient(): Resend | null {
-  if (!RESEND_API_KEY) return null;
-  return new Resend(RESEND_API_KEY);
-}
+let _resendClient: Resend | null | undefined;
+function getResendClient(): Resend | null {
+  if (_resendClient !== undefined) return _resendClient;
+  _resendClient = RESEND_API_KEY ? new Resend(RESEND_API_KEY) : null;
+  return _resendClient;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/lib/email.ts` around lines 13 - 16, getResendClient
currently new-allocates a Resend on every call; change it to a lazy singleton by
adding a module-level variable (e.g., let resendClient: Resend | null |
undefined) and have getResendClient check that variable: if undefined and
RESEND_API_KEY is present, assign new Resend(RESEND_API_KEY) to it, then return
the cached instance (or null when no key). Keep the function name
getResendClient and references to Resend and RESEND_API_KEY intact so the rest
of the module continues to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web-next/src/lib/api/auth.ts`:
- Around line 781-784: The email verification logging prints user.email
directly; update the logging in the email verification branch (the console.log
lines inside the conditional) to use sanitizeForLog(user.email) the same way
requestPasswordReset does (i.e., replace user.email with
sanitizeForLog(user.email)) so logs are consistent and protected against log
injection.

In `@apps/web-next/src/lib/email.ts`:
- Around line 24-26: The displayName value is interpolated raw into the HTML
email (via the greeting and later template usage), which allows HTML injection;
add an HTML-escaping helper (e.g., escapeHtml) and apply it to displayName
wherever used (replace direct uses of displayName with escapeHtml(displayName))
including the greeting variable and any other template interpolations, and
ensure the helper escapes &, <, >, ", and ' to their HTML entities before
returning the escaped string.
- Around line 162-166: When RESEND_API_KEY is missing the functions
sendVerificationEmail and sendPasswordResetEmail currently return { success:
true } and silently skip sending; change both to return { success: false } and
emit a warning in production (use processLogger.warn or console.warn) that the
API key is missing and the verification/reset URL (verifyUrl/resetUrl) was not
sent; update the branch that checks for !client in sendVerificationEmail (and
mirror the same change in sendPasswordResetEmail) so misconfigured production
deployments are observable.

---

Nitpick comments:
In @.env.example:
- Around line 79-84: The RESEND_EMAIL settings (RESEND_API_KEY and EMAIL_FROM)
are incorrectly documented under the STORAGE header; move those lines into a new
dedicated "# EMAIL" section (or place them adjacent to STORAGE but under an "#
EMAIL" header) so email config is clearly separated from blob storage. Update
the .env.example by cutting the RESEND_API_KEY and EMAIL_FROM lines from under
the STORAGE block and pasting them under a new "# EMAIL" heading with the same
comments, ensuring the variable names (RESEND_API_KEY, EMAIL_FROM) remain
unchanged.

In `@apps/web-next/src/lib/email.ts`:
- Around line 13-16: getResendClient currently new-allocates a Resend on every
call; change it to a lazy singleton by adding a module-level variable (e.g., let
resendClient: Resend | null | undefined) and have getResendClient check that
variable: if undefined and RESEND_API_KEY is present, assign new
Resend(RESEND_API_KEY) to it, then return the cached instance (or null when no
key). Keep the function name getResendClient and references to Resend and
RESEND_API_KEY intact so the rest of the module continues to work.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a90bbe1 and 963eac8.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json, !package-lock.json
📒 Files selected for processing (5)
  • .env.example
  • apps/web-next/.env.local.example
  • apps/web-next/package.json
  • apps/web-next/src/lib/api/auth.ts
  • apps/web-next/src/lib/email.ts

Comment thread apps/web-next/src/lib/api/auth.ts
Comment thread apps/web-next/src/lib/email.ts Outdated
Comment thread apps/web-next/src/lib/email.ts Outdated
Copy link
Copy Markdown
Contributor

@eliteprox eliteprox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good to me, just need to apply code suggestions to prevent html injection in emails and logs as suggested by coderabbit.

if (!client) {
if (process.env.NODE_ENV !== 'production') {
console.log('[EMAIL] (no RESEND_API_KEY) Verification URL:', verifyUrl);
}
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.

Add logging for when RESEND_API_KEY is not configured

Suggested change
}
} else {
console.error('[EMAIL] RESEND_API_KEY is not configured — verification email not sent');
return { success: false, error: 'Email service not configured' };
}

seanhanca and others added 2 commits February 26, 2026 13:27
Fixes #148

- Add Resend integration for sending verification and password reset emails
- Wire register() to send verification email after user creation
- Add professional Livepeer community email templates (HTML)
- Wire requestPasswordReset to send real emails via Resend
- Add RESEND_API_KEY and EMAIL_FROM to env examples
- Dev fallback: log links to console when RESEND_API_KEY is unset

Co-authored-by: Cursor <cursoragent@cursor.com>
- Escape displayName with HTML entities before interpolating into email
  templates to prevent XSS via user-controlled input
- Use lazy singleton for Resend client to avoid re-instantiation per call
- Return { success: false } when RESEND_API_KEY is missing so
  misconfigured production deployments are observable
- Sanitize user.email with sanitizeForLog() in verification logging,
  consistent with the password-reset path
- Move email env vars to dedicated EMAIL section in .env.example

Made-with: Cursor
@seanhanca seanhanca force-pushed the fix/email-verification-for-sign-up branch from ea0200b to 94ee096 Compare February 26, 2026 21:27
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/web-next/src/lib/api/auth.ts (1)

278-281: Consider replacing fire-and-forget email send with durable dispatch.

Line 279 launches an unawaited async send in a serverless-oriented module; this can be dropped if the request lifecycle ends early. Prefer a durable outbox/job queue, or at least a bounded await strategy, for better delivery reliability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/lib/api/auth.ts` around lines 278 - 281, The current
fire-and-forget call to sendVerificationEmail(user.id) is unreliable in
serverless lifecycles; replace it with a durable dispatch: either enqueue a job
to your existing job queue/outbox (e.g., push a record to
EmailOutbox.create({...}) or JobQueue.enqueue({type: 'sendVerification', userId:
user.id})) or perform a bounded await with timeout to ensure the function is
attempted before the request ends; update the auth registration flow where
sendVerificationEmail is invoked to write to the outbox/queue (or call a helper
like enqueueVerificationEmail(user.id)) instead of directly calling
sendVerificationEmail(...), and ensure the background worker consumes the outbox
to perform the actual send.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.env.example:
- Around line 83-88: Update the .env.example comment block for RESEND_API_KEY to
indicate it is required in production: change the RESEND_API_KEY comment to read
something like "[REQUIRED for production] Get API key from
https://resend.com/api-keys" and replace the "Without this, verification/reset
links are logged to console (dev only)" line with a clear warning that auth
emails will not be sent in production without RESEND_API_KEY; also mark
EMAIL_FROM as optional but note it won't override delivery if RESEND_API_KEY is
missing.

In `@apps/web-next/src/lib/api/auth.ts`:
- Around line 675-676: Replace the ad-hoc app origin construction that uses
process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000' with the existing
getAppUrl() helper so email links use the proper fallback (VERCEL_URL) in
deployments; update the code that builds resetUrl (and the similar verification
URL at the other site) to call getAppUrl() and then interpolate the token (e.g.,
const appUrl = getAppUrl(); const resetUrl =
`${appUrl}/reset-password?token=${token}`) so both resetUrl and the verification
link use getAppUrl().

---

Nitpick comments:
In `@apps/web-next/src/lib/api/auth.ts`:
- Around line 278-281: The current fire-and-forget call to
sendVerificationEmail(user.id) is unreliable in serverless lifecycles; replace
it with a durable dispatch: either enqueue a job to your existing job
queue/outbox (e.g., push a record to EmailOutbox.create({...}) or
JobQueue.enqueue({type: 'sendVerification', userId: user.id})) or perform a
bounded await with timeout to ensure the function is attempted before the
request ends; update the auth registration flow where sendVerificationEmail is
invoked to write to the outbox/queue (or call a helper like
enqueueVerificationEmail(user.id)) instead of directly calling
sendVerificationEmail(...), and ensure the background worker consumes the outbox
to perform the actual send.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 963eac8 and ea0200b.

📒 Files selected for processing (3)
  • .env.example
  • apps/web-next/src/lib/api/auth.ts
  • apps/web-next/src/lib/email.ts

Comment thread .env.example
Comment on lines +83 to +88
# Email (Resend) — verification and password reset
# Get API key from https://resend.com/api-keys
# Without this, verification/reset links are logged to console (dev only)
# RESEND_API_KEY=
# Optional: override sender (default: NaaP Platform <onboarding@resend.dev>)
# EMAIL_FROM=NaaP <noreply@yourdomain.com>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Mark RESEND_API_KEY as required in production docs.

The new auth flow depends on email delivery for verification/reset, but this block currently reads as optional. Please mark RESEND_API_KEY as [REQUIRED for production] to reduce misconfigured deployments where auth emails never send.

📝 Suggested doc tweak
-# Email (Resend) — verification and password reset
+# Email (Resend) — verification and password reset
 # Get API key from https://resend.com/api-keys
 # Without this, verification/reset links are logged to console (dev only)
-# RESEND_API_KEY=
+# [REQUIRED for production] RESEND_API_KEY=
 # Optional: override sender (default: NaaP Platform <onboarding@resend.dev>)
 # EMAIL_FROM=NaaP <noreply@yourdomain.com>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Email (Resend) — verification and password reset
# Get API key from https://resend.com/api-keys
# Without this, verification/reset links are logged to console (dev only)
# RESEND_API_KEY=
# Optional: override sender (default: NaaP Platform <onboarding@resend.dev>)
# EMAIL_FROM=NaaP <noreply@yourdomain.com>
# Email (Resend) — verification and password reset
# Get API key from https://resend.com/api-keys
# Without this, verification/reset links are logged to console (dev only)
# [REQUIRED for production] RESEND_API_KEY=
# Optional: override sender (default: NaaP Platform <onboarding@resend.dev>)
# EMAIL_FROM=NaaP <noreply@yourdomain.com>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.env.example around lines 83 - 88, Update the .env.example comment block for
RESEND_API_KEY to indicate it is required in production: change the
RESEND_API_KEY comment to read something like "[REQUIRED for production] Get API
key from https://resend.com/api-keys" and replace the "Without this,
verification/reset links are logged to console (dev only)" line with a clear
warning that auth emails will not be sent in production without RESEND_API_KEY;
also mark EMAIL_FROM as optional but note it won't override delivery if
RESEND_API_KEY is missing.

Comment on lines +675 to +676
const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
const resetUrl = `${appUrl}/reset-password?token=${token}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use getAppUrl() for email links to avoid broken localhost URLs.

Line 675 and Line 773 rebuild app origin from NEXT_PUBLIC_APP_URL || 'http://localhost:3000', which bypasses the existing getAppUrl() fallback logic (VERCEL_URL). In deployments where NEXT_PUBLIC_APP_URL is unset, reset/verification emails can contain localhost links.

🔧 Proposed fix
-  const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
+  const appUrl = getAppUrl();
   const resetUrl = `${appUrl}/reset-password?token=${token}`;
-  const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
+  const appUrl = getAppUrl();
   const verifyUrl = `${appUrl}/verify-email?token=${token}`;

Also applies to: 773-774

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/lib/api/auth.ts` around lines 675 - 676, Replace the ad-hoc
app origin construction that uses process.env.NEXT_PUBLIC_APP_URL ||
'http://localhost:3000' with the existing getAppUrl() helper so email links use
the proper fallback (VERCEL_URL) in deployments; update the code that builds
resetUrl (and the similar verification URL at the other site) to call
getAppUrl() and then interpolate the token (e.g., const appUrl = getAppUrl();
const resetUrl = `${appUrl}/reset-password?token=${token}`) so both resetUrl and
the verification link use getAppUrl().

// In production, send email. For now, log to console (never log token in production)
const safeEmail = sanitizeForLog(email);
if (process.env.NODE_ENV !== 'production') {
const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
Copy link
Copy Markdown
Contributor

@vercel vercel Bot Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Email links (password reset and verification) use incorrect URL fallback logic that bypasses the existing getAppUrl() function, causing broken links on Vercel preview deployments.

Fix on Vercel

@seanhanca seanhanca merged commit 86f8f33 into main Feb 26, 2026
22 of 23 checks passed
@seanhanca seanhanca deleted the fix/email-verification-for-sign-up branch February 26, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope/shell Shell app changes size/L Large PR (201-500 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

email verification for account signup is not configured

2 participants