Prompt
How do I send password reset emails reliably from my app?
Latest observation
To send password reset emails reliably, focus on three things:
- Don’t send the email directly from your web request
- Use a dedicated email delivery service
- Make the reset flow secure and retry-safe
Here’s a practical approach.
1) Generate a secure reset token
When a user requests a password reset:
- Generate a cryptographically secure random token
- Store a hashed version of it in your database
- Set an expiration time (commonly 15–60 minutes)
- Make it single-use
Example flow:
- User enters email
- Your app creates token:
random(32 bytes) - Store
hash(token)+user_id+expires_at - Email the user a link like:
https://yourapp.com/reset-password?token=...&email=...
Better: include only the token, and look up the record server-side.
2) Send emails asynchronously
Don’t make the password reset endpoint depend on SMTP or the email provider being fast.
Instead:
- API receives reset request
- Save token
- Put an email job on a queue
- A background worker sends the email
This gives you:
- retries if the provider is down
- faster API responses
- less chance of user-visible failures
Examples of queues:
- Sidekiq / Redis
- Celery / RabbitMQ
- SQS
- BullMQ / Redis
- Cloud Tasks / Pub/Sub
3) Use a proper email provider
For reliability, use a transactional email provider rather than your own SMTP server.
Good options:
- Amazon SES
- Postmark
- SendGrid
- Mailgun
- Resend
Why:
- better deliverability
- bounce handling
- retries/monitoring
- SPF/DKIM/DMARC support
- reputation management
4) Configure SPF, DKIM, and DMARC
If you want reset emails to actually reach inboxes:
- SPF: authorizes your mail servers
- DKIM: signs messages cryptographically
- DMARC: tells providers how to handle spoofing
Also:
- send from a domain you control
- use a clear
From:address likeno-reply@yourdomain.com - avoid free email providers for app email
5) Make the reset endpoint idempotent and safe
If the user clicks “Forgot password” multiple times:
- Don’t reveal whether the email exists
- Return the same generic response:
- “If an account exists, we sent a reset email.”
This prevents account enumeration.
Also:
- invalidate older reset tokens when a new one is issued
- accept only one valid token at a time, if possible
6) Make the token verification robust
When the user clicks the link:
- hash the token from the URL
- compare to stored hash
- ensure it hasn’t expired
- ensure it hasn’t been used
- then allow password change
- mark token as used immediately after success
Use constant-time comparison if applicable.
7) Add retry logic for transient failures
Your email worker should retry on temporary failures like:
- timeouts
- 5xx responses
- rate limiting
- network glitches
Use:
- exponential backoff
- max retry count
- dead-letter queue or failure alerting
Don’t retry forever.
8) Handle bounces and suppression
If emails bounce or users mark them as spam:
- track bounce events via provider webhooks
- suppress bad addresses
- monitor deliverability
This helps keep your sender reputation high.
9) Include good UX
Your reset email should:
- be short and clear
- include the reset link prominently
- state expiration time
- say to ignore if not requested
Example:
If you requested a password reset, click the link below.
This link expires in 30 minutes.
10) Recommended architecture
A solid setup looks like this:
- User submits email
- API validates input
- API creates reset token record
- API enqueues email job
- Worker sends email through transactional provider
- Provider delivers or retries
- User clicks link
- App validates token and resets password
- Token is invalidated
Common mistakes to avoid
- Sending mail synchronously in the request thread
- Using your personal SMTP server
- Not setting SPF/DKIM/DMARC
- Revealing whether the account exists
- Storing reset tokens in plaintext
- Making tokens long-lived
- Not expiring or invalidating tokens
- No retry / no monitoring
Minimal checklist
If you want this to be reliable, do these:
- Generate strong random token
- Store only token hash
- Expire token quickly
- Queue email send
- Use transactional email provider
- Configure SPF/DKIM/DMARC
- Generic response to requests
- Add retries + monitoring
- Invalidate token after use
If you want, I can also give you:
- a reference implementation in Node, Python, or Go
- a database schema for reset tokens
- a production-ready password reset flow diagram