OTP (Email Sign-in)
Set up email OTP authentication for sign-in and verification flows.
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 devThen:
- Navigate to
/auth/sign-in - Enter an email and request a code
- Copy the OTP from the server console
- Enter the 6-digit code to continue
3. Optional: Set Up Plunk For Real Emails
- Create a free account at useplunk.com
- Verify your sending domain in the Plunk dashboard
- Copy your Secret API key
4. Set Environment Variables
# .env.local
PLUNK_SECRET_API_KEY=your_plunk_secret_api_key
TRANSACTIONAL_EMAIL=noreply@yourdomain.com5. Test With Real Email Delivery
- Run
pnpm dev - Navigate to
/auth/sign-in - Enter an email and request a code
- In development, the OTP is still logged to the server console
- 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:
sendOTPEmailfromsrc/features/email/send.tsgetOTPEmailContentfromsrc/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-inemail-verificationforget-passwordchange-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_SECONDSinsrc/features/auth/constants.ts - Spam folder — configure SPF, DKIM, and DMARC for your sending domain