Set up email sign-in
Skip the password. Users enter their email, get a 6-digit code, and they're in. No password to remember, no reset links to click. Just a quick code that expires in 10 minutes.
Email OTP is disabled by default for new installations. Password sign-in is the default method. Enable Email OTP from Admin → Settings → Portal Authentication if you prefer passwordless login.
How It Works
- User enters email - On the login or signup page, the user enters their email address
- Code sent - A 6-digit verification code is sent to their email
- User enters code - The user enters the code within 10 minutes
- Session created - Upon verification, a session is created and the user is logged in
The email OTP system is powered by Better Auth's emailOTP plugin.
Configuration
Required Environment Variables
# Auth configuration (required)
SECRET_KEY="your-32-character-secret" # Generate with: openssl rand -base64 32
BASE_URL="https://feedback.yourcompany.com"Email Delivery Options
Without email configuration, OTP codes are logged to the console (useful for development). For production, configure one of the following:
Option 1: SMTP (Recommended for Self-Hosted)
EMAIL_SMTP_HOST="smtp.sendgrid.net"
EMAIL_SMTP_PORT="587"
EMAIL_SMTP_USER="apikey"
EMAIL_SMTP_PASS="your-api-key"
EMAIL_FROM="Quackback <noreply@yourdomain.com>"
# For implicit TLS (port 465), uncomment:
# EMAIL_SMTP_SECURE="true"Common SMTP providers:
| Provider | Host | Port | Notes |
|---|---|---|---|
| Gmail | smtp.gmail.com | 587 | Use App Password |
| SendGrid | smtp.sendgrid.net | 587 | Use API key as password |
| Amazon SES | email-smtp.{region}.amazonaws.com | 587 | IAM credentials |
| Mailgun | smtp.mailgun.org | 587 | Domain API key |
| Postmark | smtp.postmarkapp.com | 587 | Server API token |
Option 2: Resend API
EMAIL_RESEND_API_KEY="re_xxxxxxxx"
EMAIL_FROM="Quackback <noreply@yourdomain.com>"Get your API key at resend.com/api-keys.
Priority Order
The email system uses this priority:
- SMTP - If
EMAIL_SMTP_HOSTis set - Resend - If
EMAIL_RESEND_API_KEYis set - Console - Logs OTP to server console (development only)
User Flow
Portal Users (Feedback Submitters)
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Enter Email │────▶│ Receive Code │────▶│ Enter Code │
│ │ │ (6 digits) │ │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Logged In │
│ Session: 7 days│
└─────────────────┘
Code Specifications
| Property | Value |
|---|---|
| Code length | 6 digits |
| Expiration | 10 minutes |
| Resend cooldown | 60 seconds |
| Session duration | 7 days |
Email Template
The sign-in code email uses a clean, branded template:
- Subject: "Your Quackback sign-in code is XXXXXX"
- Preview text: "Your sign-in code is XXXXXX"
- Content: Clear instructions with the code prominently displayed
- Footer: Security notice about ignoring unexpected emails
The template is built with React Email components located at:
packages/email/src/templates/signin-code.tsx
Admin Configuration
Administrators can enable or disable email OTP for portal users:
- Navigate to Admin → Settings → Portal Authentication
- Find the Email OTP toggle
- Enable or disable as needed
At least one authentication method must remain enabled. Email OTP is disabled by default. Enable it if you want to offer passwordless sign-in alongside or instead of password authentication.
Troubleshooting
OTP Code Not Received
- Check spam/junk folder - Email providers may filter transactional emails
- Verify email configuration - Ensure SMTP or Resend is properly configured
- Check server logs - In development, codes are logged to console:
┌──────────────────────────────────────────────────────────── │ [DEV] Sign-in Code Email ├──────────────────────────────────────────────────────────── │ To: user@example.com │ Code: 123456 └──────────────────────────────────────────────────────────── - Verify EMAIL_FROM - Ensure the sender address is verified with your email provider
Invalid Code Errors
- Code expired - Codes are valid for 10 minutes only
- Typo in code - Verify the 6-digit code matches exactly
- Code already used - Each code can only be used once
Resend Cooldown
To prevent abuse, the UI enforces a 60 second cooldown between resend attempts. This cooldown is client-side only; there is no server-side rate limiting on OTP requests. Consider using a reverse proxy (nginx, Cloudflare) for server-side rate limiting in production.
SMTP Connection Issues
# Test SMTP connectivity
openssl s_client -connect smtp.provider.com:587 -starttls smtp
# Common issues:
# - Firewall blocking port 587 or 465
# - Invalid credentials
# - IP not whitelisted with providerResend API Issues
- Verify API key - Ensure
EMAIL_RESEND_API_KEYis correct - Check domain verification - The sending domain must be verified in Resend
- Review Resend dashboard - Check for delivery failures or bounces
Security Considerations
Best Practices
- Use HTTPS - Always serve your Quackback instance over HTTPS
- Verify sender domain - Set up SPF, DKIM, and DMARC records
- Monitor for abuse - Watch for unusual sign-in patterns
- Keep secrets secure - Never commit
SECRET_KEYto version control
Why OTP Over Passwords
- No password storage - Eliminates risk of password database breaches
- No password fatigue - Users don't need to remember another password
- Automatic verification - Email ownership is verified on each login
- Resistant to phishing - Codes are short-lived and single-use
Development Mode
During development, if no email provider is configured:
- OTP codes are printed to the server console
- Look for the formatted log output with the code
- Copy the code and enter it in the verification form
This allows testing the full authentication flow without email configuration.
Related Documentation
- OAuth Providers - Social login with GitHub and Google
- Authentication Overview - General auth architecture