OTP delivery is both a security flow and a UX flow

An OTP SMS is tiny, but the system around it is not. The user is usually stuck until the code arrives. Attackers may be trying to enumerate accounts, trigger SMS pumping, bypass rate limits, or exploit account recovery. Carriers may delay, filter, or expire messages. Your product has to handle all of that without turning login into a support ticket.

The right mental model is not send code and hope. It is create a verification attempt, deliver through the best available channel, observe the lifecycle, limit abuse, and give the user a safe next step.

A reliable OTP flow is measured by successful verification, not by how many SMS requests your API accepted.

Use short-lived, single-use codes

NIST's current digital identity guidance allows OTP outputs as short as six decimal digits in the authenticator context and emphasizes replay resistance, throttling, and secure verifier behavior. For SMS-delivered out-of-band secrets, the practical product rule is: short enough to limit risk, long enough for real humans on real networks.

ControlRecommended approachWhy it matters
LengthUse 6 to 8 digits for most consumer flows.Six digits are familiar and usable; higher-risk flows may justify more friction.
LifetimeExpire codes after a short window, commonly 5 to 10 minutes.SMS can be delayed; a tiny window creates false failures, but a long window increases replay risk.
Use countInvalidate the code immediately after a successful verification.A one-time code should not work twice.
AttemptsLimit verification attempts per code and per account.Prevents brute force and reduces noisy support cases.
ReplacementWhen resending, decide whether to reuse the active code or replace it, then make that behavior consistent.Multiple active codes create confusion and extra attack surface.

For most apps, the best default is one active verification attempt per user action. If the user asks again too quickly, show a countdown and reuse the existing attempt rather than generating unlimited new codes.

Design resend logic before fraud finds it

Twilio's Verify guidance calls out retry buffers as a way to avoid spamming users, hitting rate limits, and wasting spend through abuse. The same principle applies regardless of provider: every resend button is also a cost and fraud surface.

  1. Start with a visible resend countdown instead of an always-active resend button.
  2. Throttle by phone number, account, IP address, device fingerprint, ASN, country, and verification purpose.
  3. Use idempotency keys so duplicate frontend requests do not create duplicate SMS messages.
  4. Block or challenge suspicious velocity patterns before they hit the messaging provider.
  5. Keep separate limits for send attempts and code-check attempts.
  6. Escalate gradually: wait, retry SMS, offer voice or another channel, then route to support for edge cases.
  7. Log enough context to explain why a user was throttled without exposing the code itself.
{
  "verificationAttempt": "va_01HX7RA7TZE14",
  "userId": "usr_123",
  "phone": "+14155550123",
  "purpose": "login",
  "expiresAt": "2026-04-26T09:20:00.000Z",
  "sendCount": 1,
  "checkCount": 0,
  "nextResendAt": "2026-04-26T09:16:00.000Z"
}

Write OTP messages carriers and humans understand

OTP copy should be boring in the best possible way. It should name the service, state the code, explain the action, and warn the user if they did not request it. NIST's usability guidance for out-of-band messages recommends contextual information and consistent style so users can spot suspicious authentication requests.

DoAvoid
Use the brand name users recognize.Sending from an unknown sender with a vague message.
State the action: login, signup, payment approval, or password reset.A generic your code is message with no context.
Keep it short enough to avoid unnecessary message segmentation.Stuffing legal text, marketing copy, and multiple URLs into the OTP.
Say not to share the code.Training users to paste OTPs into support chats or emails.
Use a consistent template for each purpose.Changing wording so much that users cannot recognize legitimate messages.
Acme login code: 482913. It expires in 10 minutes. Do not share this code.

For mobile apps, use platform features such as domain-bound SMS OTP autofill where available, but never make autofill the only path. Users change devices, travel, roam, and run into carrier delays.

Track delivery by attempt, not just by user

When a user says the code did not arrive, support needs a timeline: attempt created, SMS accepted, provider sent, carrier receipt delivered or failed, resend clicked, code checked, and final verification outcome. Without that, every incident turns into guesswork.

  • Store the verification attempt ID separately from the provider message ID.
  • Keep the destination country, normalized phone, sender identity, template, and provider route.
  • Store delivery state changes as events so out-of-order webhooks do not erase useful history.
  • Record provider error codes and raw webhook payloads for debugging.
  • Measure verification success rate, median time to verify, resend rate, timeout rate, and failure rate by country.

The difference between a consumer app that feels trustworthy and one that feels broken is often the fallback experience. If the first OTP is delayed, tell the user when they can retry. If retries fail, offer a different channel or a recovery path that does not train them to keep hammering the send button.

Know the security limits of SMS

SMS is widely reachable, but it is not the strongest authentication factor. NIST SP 800-63B-4 describes PSTN-based out-of-band authentication as restricted and not phishing-resistant, and recommends considering risk indicators such as SIM changes, number porting, and abnormal behavior before using PSTN delivery.

That does not mean never use SMS. It means be honest about the risk tier. SMS may be acceptable for phone verification, low-risk login, or fallback in some products. For admin accounts, financial actions, or users under attack, offer stronger options such as passkeys, authenticator apps, push approval, or hardware-backed authenticators.

RiskSMS-only postureBetter posture
New signup phone verificationOften acceptable with rate limits and abuse checks.Add device, IP, and velocity scoring.
Normal user loginUsable, but vulnerable to phishing and SIM-swap risk.Offer passkeys or authenticator apps as the preferred method.
Password resetRisky if phone number changes are weakly protected.Require existing session, email confirmation, stronger authenticator, or support review for sensitive accounts.
Admin or financial actionToo weak as the only control for many products.Use phishing-resistant or multi-factor authentication with step-up rules.

OTP SMS FAQ

How long should an SMS OTP last?

Many products use 5 to 10 minutes. The right value depends on risk, geography, and carrier latency. Very short windows frustrate users on slower networks; long windows increase replay risk.

Should resend generate a new OTP?

Either can work, but the behavior must be consistent. A common approach is to keep one active attempt during the resend cooldown, then rotate the code when the old attempt expires or when risk rules require it.

How many OTP attempts should be allowed?

Limit both send attempts and code-check attempts. The exact number depends on your risk model, but unlimited OTP checks are a brute-force bug, not a convenience feature.

What should we measure?

Measure time to first SMS, delivery rate, verification completion rate, resend rate, timeout rate, fraud blocks, and failure rate by country, carrier, template, and sender identity.