Prompt
How do I send password reset emails reliably?
Latest observation
To send password reset emails reliably, focus on two parts: deliverability and system design.
1) Use a proper email delivery provider
Don’t send password reset emails directly from your app server or a consumer SMTP account.
Use a transactional email service such as:
- Amazon SES
- SendGrid
- Mailgun
- Postmark
- Resend
These are built for high-delivery, transactional messages like password resets.
2) Set up domain authentication
This is one of the biggest factors in email reliability.
Configure:
- SPF
- DKIM
- DMARC
Also use a sending domain like:
no-reply@yourdomain.comsecurity@yourdomain.com
Avoid sending from free domains like Gmail/Yahoo.
3) Make password reset emails simple and recognizable
To reduce spam filtering and user confusion:
- Use a clear subject:
Reset your password - Keep the content short
- Include your brand name
- Avoid lots of images, buttons, or promotional language
- Use one clear call to action
4) Generate secure, short-lived reset links
Best practice:
- Generate a random token
- Store only a hashed version of the token
- Expire it quickly, e.g. 15–60 minutes
- Make it single-use
Never put the actual password in the email.
5) Send reset emails asynchronously
Don’t make the user wait on the email provider in the login/reset request.
Instead:
- User requests reset
- Your app saves token and queues email
- A background worker sends the email
- Show a generic success message immediately
This prevents user-facing failures from temporary provider issues.
6) Add retries and a queue
For reliability, send through a job queue and retry failures.
Recommended pattern:
- Put email jobs on a queue
- Retry transient failures with exponential backoff
- Use a dead-letter queue for repeated failures
- Log provider response codes
This protects you from temporary outages.
7) Make the request endpoint safe
To avoid account enumeration:
- Always return the same message:
- “If an account exists, we’ve sent a reset email.”
- Don’t reveal whether the email is registered
Add rate limiting to prevent abuse:
- Per email address
- Per IP address
- Per device/session if possible
8) Monitor delivery
Track:
- Sent / delivered / bounced / complained
- Open rates, if available
- Click-through on reset links
- Queue failure rates
- Provider outages
Set alerts for:
- Spike in bounces
- Spike in complaints
- Queue backlog growth
- Sudden drop in sends
9) Handle edge cases well
Common issues:
- Email provider delay
- User mistypes email
- Link expired
- Link already used
- User requests multiple resets
Best practice:
- Only the latest token should be valid, or explicitly support multiple valid tokens with clear rules
- Show a friendly “link expired, request a new one” page
- Consider making links work without requiring login first
10) Test deliverability
Use:
- Seed test inboxes at Gmail, Outlook, Yahoo, Proton, etc.
- Mail-tester tools
- DMARC aggregate reports
- Spam folder checks
Test from the actual production domain, not just staging.
A solid architecture
A reliable flow looks like this:
- User requests password reset
- App creates token and stores hashed token + expiry
- App enqueues email job
- Worker sends via transactional email provider
- Provider returns delivery response
- App logs result and retries if needed
- User clicks link, token is verified, password is reset
Short checklist
- Use SES/SendGrid/Postmark/Mailgun/Resend
- Configure SPF, DKIM, DMARC
- Use a dedicated sending domain
- Queue email sends
- Retry transient failures
- Use short-lived, single-use reset tokens
- Rate-limit reset requests
- Return generic responses
- Monitor bounces/complaints
- Test deliverability regularly
If you want, I can also give you:
- a reference implementation in Node/Python/Ruby, or
- a production-ready password reset flow with database schema and email queue design.