A password reset and OTP email is different from any other message you send. The user is waiting, probably staring at your app, and every second of delay is a failed sign-in. An attacker is also interested in it, because the message is a path into an account. Getting these emails right means working on speed, on security and on delivery at the same time. This guide sets out a design that does all three.
Why these emails are special
| Property | Marketing email | Authentication email |
|---|---|---|
| Time sensitivity | Hours or days | Seconds to minutes |
| Recipient expectation | Optional | Awaited, often with the app open |
| Security impact | Low | High: a path into an account |
| Failure cost | A missed campaign | A locked-out user and a support call |
| Content | Rich, promotional | Minimal, plain and specific |
Step 1: Give them their own stream
Authentication mail should leave through a path that nothing else can slow down or harm. Use a separate sending subdomain, separate credentials and, where your setup allows, a separate queue. If a newsletter generates complaints, receivers may filter or defer mail from that subdomain, and you do not want your sign-in codes sitting in that queue. Our guide to separating transactional and marketing streams explains how, and verifying your sending domain covers the setup of each subdomain.
Step 2: Generate codes and tokens securely
- Use a cryptographically secure random generator, not a general random function or a timestamp.
- Pick the right size. A six-digit numeric code is easy to type but has few values, so it needs strict attempt limits and a short life. A reset link token should be long and unguessable, with enough randomness that guessing is impractical.
- Store only a hash of the token on your side, as you would a password, so that a database leak does not hand out working links.
- Bind the token to the account and purpose. A sign-in token must not work as a reset token.
- Invalidate earlier tokens when a new one is issued, and when the password changes.
Step 3: Limit lifetime and use
| Item | Suggested approach |
|---|---|
| Code lifetime | Minutes, chosen by risk; shorter for sign-in codes |
| Reset link lifetime | Up to about an hour for many products; choose by risk |
| Use | Single use; mark as used on first success |
| After a successful reset | Invalidate all outstanding tokens and consider ending other sessions |
| Expired token | Show a clear message and offer to send a new one |
These values are starting points, not rules. Your product and risk will decide the right numbers, and security teams often set them by policy.
Step 4: Rate limit requests and attempts
Two kinds of limit protect users. Limits on requests stop people from using your reset form to flood someone with emails, and protect your sending reputation. Limits on attempts stop guessing of short codes.
- Limit how often a code or link can be requested for one address, for example once every minute and a small number per hour.
- Limit requests per IP address and per account.
- Allow only a few wrong code attempts before the code is cancelled.
- Give the same response whether or not an account exists for the address, such as "If this address is registered, we have sent instructions."
- Add a challenge only when abuse is visible, so normal users are not slowed.
Apply the same ideas to your own sending calls, which can hit platform limits during a burst. See rate limits and retries.
Step 5: Write a clear, minimal message
Good authentication emails are short and specific. They say what they are, give the one thing the user needs, state the expiry and tell people what to do if they did not ask.
Subject: Your Example sign-in code is 482910
Use this code to sign in to Example. It expires in 10 minutes.
482910
If you did not try to sign in, you can ignore this message.
Never share this code with anyone. We will never ask you for it.
- Put the code in the subject or preheader if your product allows, so users can read it without opening the message. Some mail apps and phones can also offer to fill in codes, so keep the format simple.
- Include the app or site name, so people know which service it came from.
- Do not include marketing content or large images.
- Include a plain-text part. Many readers and notification previews use it.
- Use the same From name and address every time. A stable sender is easier to recognise and to allow-list.
- Do not put sensitive details such as full account numbers in the message.
Step 6: Be careful with links
Reset and magic links are valuable to attackers, and fragile in practice. Keep them direct.
| Do | Avoid | Why |
|---|---|---|
| Link straight to your own domain over HTTPS | Click-tracking redirects through another domain | Redirects add delay, can fail, and look like phishing |
| Use a single clear button plus a visible text link | Many links in one message | Focus reduces mistakes |
| Make the link work in any browser | Requiring the same device or session | Users often open mail on another device; offer a code if needed |
| Expire after use and after the time limit | Long-lived reusable links | A leaked link must not remain valid |
| Teach users to check the domain | Asking users to enter passwords on pages reached by email links if you can avoid it | Reduces phishing success |
Beware of link scanners. Some corporate security tools open links automatically to check for malware. If a link works with a simple visit, the scanner can consume a single-use token before the user does. A common fix is to make the link open a page that requires one extra action, such as pressing a button, before the token is spent.
Step 7: Measure and recover
Authentication mail needs its own monitoring.
- Time to accept. The time from request to your platform accepting the message. Alert when it passes your threshold.
- Bounce and deferral rate for this stream, by mailbox provider. See email deliverability metrics.
- Resend rate. If many users press resend, the first message is probably late or lost.
- Completion rate. The share of requested codes that are used. A fall can reveal a delivery problem before complaints arrive.
- Suppression hits. If an address was suppressed after a hard bounce, the code will not send. Show the user a clear message and offer another way to sign in. See suppression list management.
Give users help text on the page: check spam or junk folders, check the address for typos, wait a minute and use resend, or choose another sign-in method.
A worked example: codes arriving late during a sale
An online store sends sign-in codes and promotional emails through the same sending path. During a sale, it queues a large promotion at the same moment many customers try to sign in. Codes are queued behind promotions and arrive after several minutes. Users press resend, which creates more codes and more load, and support tickets multiply.
The team splits the traffic. Authentication mail moves to its own subdomain and credentials, and its own queue with higher priority. Requests are limited to one per minute for each address, and each new code invalidates the previous one. The code now appears in the subject, and a dashboard tracks time to accept and resend rate. In the next sale, promotions take longer but codes arrive in seconds, and resend presses fall. The example is illustrative, but mixing urgent and bulk mail in a single path is a very common cause of late codes.
Beyond email
Email is only as secure as the mailbox that receives it, so for valuable accounts consider stronger options such as passkeys or authenticator apps, with email as a recovery path. Whatever you choose, authenticate your sending domain, as in authenticating your sending domain, because spoofed reset emails are a common phishing tactic.
Sign-in emails arriving late or not at all? Send us the time of a request and the address domain and we will help trace it.
Further reading and sources
Frequently asked questions
How fast should a one-time code email arrive?
As fast as possible. Users are waiting with the app open, and a delay of a minute feels broken. Aim for delivery within seconds, measure the time from request to acceptance, and alert when it grows. Final delivery also depends on the receiving provider.
How long should a code or reset link stay valid?
Short enough to limit risk and long enough for a normal person. Codes are often valid for minutes, and reset links for up to an hour or so. Choose by risk, and make sure a new request invalidates earlier ones.
Should I use a code or a link?
Both work. A code is easy to type on another device and cannot be broken by link rewriting. A link is quicker on the same device. Some products offer both. Whichever you choose, make it single-use and short-lived.
Should I track clicks on reset links?
Usually not. Link tracking replaces the destination with a redirect through another domain, which can slow the path, trigger warnings and look like phishing. Use direct links to your own domain for authentication messages.
Why do some users never receive codes?
Common causes are a mistyped address, a full mailbox, the message landing in spam or promotions, a bounce that was suppressed earlier, or slow delivery. Provide a resend option, a check of the address and help text about spam folders.
How do I stop abuse of the reset form?
Rate limit by account, address and IP address, give identical responses whether or not the account exists, and add a challenge when abuse is visible. This prevents both guessing and harassment of other people with reset emails.
Should a reset email say whether the account exists?
On the web page, give the same message either way, such as: if the address is registered, we have sent instructions. This avoids revealing which addresses have accounts.
Can I use the same domain for marketing and authentication mail?
You can, but a subdomain for each stream is safer. A complaint problem in marketing mail then does not slow or filter your sign-in mail. See our guide on separating streams.
Are SMS codes better than email codes?
They have different risks and costs. Email is widely available and easy to audit, but depends on the security of the mailbox. For high-risk accounts, stronger methods such as passkeys or authenticator apps are better than either.