Email Verification

Sent on registration. Optional — users aren't blocked from signing in if unverified.

Files

  • Schema: src/lib/db/schema/email-verifications.ts (token PK, user_id, email, expires_at, used_at).
  • Helpers: src/lib/auth/email-verification.tscreateVerificationToken, consumeVerificationToken, sendVerificationEmail.
  • Send route (automatic on registration): src/app/api/auth/register/route.ts.
  • Verify route: GET /api/auth/verify-email?token=....

Flow

  1. User registers → server creates users row, fires sendVerificationEmail(userId, email, baseUrl).
  2. sendVerificationEmail calls createVerificationToken (48-char nanoid, 24h expiry) and dispatches an email via the app's own sendEmail() provider using SYSTEM_FROM_EMAIL.
  3. Email contains <baseUrl>/api/auth/verify-email?token=<token>.
  4. On click, the handler:
    • Looks up token — rejects if expired, used, or unknown.
    • Sets users.email_verified = now().
    • Marks token used_at.
    • Writes audit log (user.email_verified).
    • Redirects to /overview?verified=true.

Env

  • SYSTEM_FROM_EMAIL — verified SES identity. Fallback: no-reply@sendoka.com (won't work without DKIM set up in SES for that domain).
  • NEXTAUTH_URL — base URL for link construction.

Security

  • Tokens are 48-char random (nanoid(48)) — ~276 bits. Safe to use in a URL.
  • Single-use via used_at column.
  • 24h TTL.
  • No enumeration protection needed — tokens aren't email-derived.

Limitations

  • No UI for "resend verification email" — add later if needed.
  • No gating: unverified users have full access. Change by adding a check in dashboard layout or specific routes if required.