BetterStarter logo
BetterStarter
Docs
GuidesAuth Providers

OTP (Email Sign-in)

Set up email OTP authentication for sign-in and verification flows.

Docs are in beta — content is improving rapidly. Found something missing? Open an issue on GitHub or reach out on Twitter (X).

BetterStarter uses Better Auth with emailOTP. Users enter their email, receive a one-time code, and continue authentication without typing a password for OTP-based flows.

Local Development Expectations

Plunk is optional in local development.

  • Without Plunk, BetterStarter logs the OTP code to the server console in development
  • With Plunk configured locally, BetterStarter sends a real email
  • In production, you need Plunk or another real email provider

1. Enable In Config

In src/appConfig.ts set:

emailOTP: { enabled: true },

2. Fastest Local Test

Run the app without setting Plunk env vars:

pnpm dev

Then:

  1. Navigate to /auth/sign-in
  2. Enter an email and request a code
  3. Copy the OTP from the server console
  4. Enter the 6-digit code to continue

3. Optional: Set Up Plunk For Real Emails

  1. Create a free account at useplunk.com
  2. Verify your sending domain in the Plunk dashboard
  3. Copy your Secret API key

4. Set Environment Variables

# .env.local
PLUNK_SECRET_API_KEY=your_plunk_secret_api_key
TRANSACTIONAL_EMAIL=noreply@yourdomain.com

5. Test With Real Email Delivery

  1. Run pnpm dev
  2. Navigate to /auth/sign-in
  3. Enter an email and request a code
  4. In development, the OTP is still logged to the server console
  5. Enter the 6-digit code to continue

Configuration

OTP settings are in src/features/auth/constants.ts:

export const OTP_CONFIG = {
  LENGTH: 6,
  EXPIRES_IN_SECONDS: 300, // 5 minutes
}

The OTP plugin is enabled in src/features/auth/index.ts and calls:

  • sendOTPEmail from src/features/email/send.ts
  • getOTPEmailContent from src/features/email/templates/OTPEmail.tsx

In development, the auth layer also logs the generated OTP value before sending.

Supported OTP email types in the current implementation:

  • sign-in
  • email-verification
  • forget-password
  • change-email

Swapping Email Providers

To use Resend, SendGrid, or another provider, update sendEmail in src/features/email/transport.ts (or adapt sendOTPEmail in src/features/email/send.ts).

Troubleshooting

  • No email received in local development — check the server console for the OTP
  • No email received in production or with Plunk enabled locally — verify your Plunk API key and sending domain
  • Code expired — adjust EXPIRES_IN_SECONDS in src/features/auth/constants.ts
  • Spam folder — configure SPF, DKIM, and DMARC for your sending domain

On this page