# Email Verification Implementation Guide

## Status: Enabled — Production Key Required

Email verification **is implemented and enabled**. Verification and password-reset emails are sent via Resend when `RESEND_API_KEY` is set. **Production**: set `RESEND_API_KEY` in the production environment and verify domain in Resend (see issue #187 and `docs/launch/NEED-FROM-YOU.md`).

## Decision Rationale (from Issue #138)

- Verification is required for new signups: users must click the link to activate.
- Rate limiting on resend-verification and registration reduces abuse.

## What's Implemented

### 1. Database Schema ✅
- `VerificationToken` model in Prisma schema
- `User.emailVerified` (nullable DateTime)
- Migration: `20260217004851_add_email_verification`

### 2. Email Templates ✅
- `frontend/src/lib/email/templates.ts` — verification + password reset (HTML + plain text)
- Professional branding with LLMHosts colors

### 3. Email Sending ✅
- `frontend/src/lib/email/send.ts` — Resend SDK; single client for verification, password reset, resend-verification, and contact form
- `sendVerificationEmail()`, `sendPasswordResetEmail()`, `resendVerificationEmail()`, `sendContactEmail()`
- When `RESEND_API_KEY` is unset: console-only (dev); no emails sent in production until key is set

## How to Enable Email Verification

### Step 1: Set up Resend Account

1. Sign up at https://resend.com
2. Verify your sending domain (llmhosts.com)
3. Create an API key
4. Add to `.env`:
   ```
   RESEND_API_KEY=re_xxxxxxxxxxxx
   EMAIL_FROM="LLMHosts <noreply@llmhosts.com>"
   ```

### Step 2: Update Auth Configuration

Edit `frontend/src/lib/auth.ts`:

```typescript
import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import Email from "next-auth/providers/email"; // Add this
import { PrismaAdapter } from "@auth/prisma-adapter"; // Add this
import bcrypt from "bcryptjs";
import { db } from "@/lib/db";
import { authConfig } from "@/lib/auth.config";
import { sendVerificationEmail } from "@/lib/email/send"; // Add this

export const { handlers, auth, signIn, signOut } = NextAuth({
  ...authConfig,
  adapter: PrismaAdapter(db), // Add adapter for verification tokens
  providers: [
    // Email provider for passwordless + verification
    Email({
      server: {
        host: process.env.EMAIL_SERVER_HOST,
        port: process.env.EMAIL_SERVER_PORT,
        auth: {
          user: process.env.EMAIL_SERVER_USER,
          pass: process.env.EMAIL_SERVER_PASSWORD,
        },
      },
      from: process.env.EMAIL_FROM,
      sendVerificationRequest: async ({ identifier, url, provider }) => {
        // Use custom email template
        const expires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
        await sendVerificationEmail({
          email: identifier,
          url,
          expires,
        });
      },
    }),
    // Keep Credentials provider for password login
    Credentials({
      // ... existing credentials config
    }),
  ],
  // ... rest of config
});
```

### Step 3: Update Registration Flow

Edit `frontend/src/app/api/auth/register/route.ts`:

```typescript
// After creating user, send verification email
const expires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
const token = crypto.randomBytes(32).toString('hex');

await db.verificationToken.create({
  data: {
    identifier: email,
    token,
    expires,
  },
});

const verifyUrl = `${process.env.NEXTAUTH_URL}/auth/verify-email?token=${token}&email=${encodeURIComponent(email)}`;

await sendVerificationEmail({
  email,
  url: verifyUrl,
  expires,
});

return NextResponse.json(
  {
    message: "Account created. Please check your email to verify your account.",
    requiresVerification: true,
  },
  { status: 201 }
);
```

### Step 4: Add Verification Route

Create `frontend/src/app/auth/verify-email/route.ts`:

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/db';

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const token = searchParams.get('token');
  const email = searchParams.get('email');

  if (!token || !email) {
    return NextResponse.redirect(new URL('/auth/error?error=InvalidToken', request.url));
  }

  const verificationToken = await db.verificationToken.findUnique({
    where: { identifier_token: { identifier: email, token } },
  });

  if (!verificationToken) {
    return NextResponse.redirect(new URL('/auth/error?error=InvalidToken', request.url));
  }

  if (verificationToken.expires < new Date()) {
    return NextResponse.redirect(new URL('/auth/error?error=TokenExpired', request.url));
  }

  // Mark email as verified
  await db.user.update({
    where: { email },
    data: { emailVerified: new Date() },
  });

  // Delete used token
  await db.verificationToken.delete({
    where: { identifier_token: { identifier: email, token } },
  });

  return NextResponse.redirect(new URL('/dashboard?verified=true', request.url));
}
```

### Step 5: Update Middleware

Edit `frontend/src/middleware.ts` to block unverified users:

```typescript
export async function middleware(request: NextRequest) {
  const session = await auth();

  if (session?.user) {
    const user = await db.user.findUnique({
      where: { id: session.user.id },
      select: { emailVerified: true },
    });

    if (!user?.emailVerified && !request.nextUrl.pathname.startsWith('/auth/verify')) {
      return NextResponse.redirect(new URL('/auth/verify-pending', request.url));
    }
  }

  // ... rest of middleware
}
```

### Step 6: Add UI for Pending Verification

Create `frontend/src/app/auth/verify-pending/page.tsx`:

```typescript
'use client';

export default function VerifyPending() {
  return (
    <div className="container mx-auto py-20 text-center">
      <h1 className="text-3xl font-bold mb-4">Verify Your Email</h1>
      <p className="text-muted-foreground mb-8">
        We've sent a verification link to your email address.
        Please check your inbox and click the link to continue.
      </p>
      <button
        onClick={async () => {
          // Call resend verification email API
          await fetch('/api/auth/resend-verification', { method: 'POST' });
        }}
        className="text-indigo-600 hover:underline"
      >
        Resend verification email
      </button>
    </div>
  );
}
```

## Testing Email Verification

1. Register a new account
2. Check email for verification link
3. Click link to verify
4. Confirm redirect to dashboard with verified status
5. Try accessing dashboard before verification (should block)

## Monitoring & Metrics

Track these metrics to decide when to enable:

- Daily signup rate
- Spam/fake account ratio
- User feedback about friction
- Conversion rate (signup → first action)

## Rollback Plan

If email verification causes issues:

1. Remove `adapter: PrismaAdapter(db)` from auth.ts
2. Remove Email provider
3. Set `REQUIRE_EMAIL_VERIFICATION=false` in .env
4. Deploy

## Cost Estimate

- Resend free tier: 3,000 emails/month
- Soft launch (50 users): ~50 verification emails
- Production (500 users/month): ~500 verification emails
- Cost: $0 (within free tier)

## Next Steps

1. Monitor soft launch for abuse (first 3-7 days)
2. If abuse detected: Enable email verification following steps above
3. If no abuse: Keep disabled for production launch (lower friction)
