The short version
Implementing two-factor authentication with SMS requires more than sending a code and verifying it. You need to handle phone number formatting, delivery failures, rate limiting, retry logic, and user experience edge cases—all while keeping your auth flow secure and performant. This guide walks through a production-ready 2FA SMS implementation using a transactional SMS API, with code patterns you can adapt to your existing auth system. We focus on the practical decisions: how to structure API calls, when to generate codes, how to handle delivery states, and what observability you need before scaling.
- Generate cryptographically secure OTPs with configurable length and expiration
- Validate phone numbers before sending to reduce failed deliveries
- Handle delivery receipts asynchronously via webhooks to track message state
- Implement idempotent send requests to prevent duplicate OTPs on retry
- Design fallback flows for users who don't receive codes
Implementation Pattern
The core 2FA flow involves three steps: generate a one-time code, deliver it via SMS, and verify the user's submitted code. Here's a practical implementation pattern using a transactional SMS API.
First, generate your OTP. Use a cryptographically secure random generator—never use predictable sequences. A six-digit code is standard for balance between usability and security. Store the code with a short TTL (time-to-live), typically 5-10 minutes, and associate it with the user ID and a unique request ID for idempotency.
Next, format and validate the phone number. Apply E.164 formatting before sending. Most SMS APIs accept E.164, but validating upfront lets you catch bad numbers early and provide immediate feedback to the user. Reject obviously invalid formats rather than burning an API call.
When sending, include a unique idempotency key in your request. This prevents duplicate OTPs if your code retries after a network timeout or 5xx error. The SMS provider should return the message ID immediately, then deliver delivery receipts asynchronously via webhook.
Finally, verify the submitted code. Check that it matches, hasn't expired, and hasn't exceeded the maximum retry attempts. After successful verification, invalidate the code immediately to prevent replay attacks. On failure, implement progressive delays or account lockout after repeated attempts.
Operational Considerations
- Delivery observability: Subscribe to webhook events for delivered, failed, and undelivered statuses. Track delivery rates per phone number prefix to identify carrier-specific issues. Build dashboards showing OTP delivery latency and failure rates.
- Sender identity: Depending on your traffic volume and regions, you'll need to configure sender IDs (alphanumeric for US), short codes, or toll-free numbers. US traffic requires A2P 10DLC registration for best delivery rates. Factor sender identity planning into your launch timeline.
- Error handling: SMS delivery can fail for reasons outside your control—number is disconnected, carrier filters, temporary network issues. Design your auth flow to handle these gracefully. Provide users with fallback options: email verification, retry after cooldown, or support contact.
- Rate limiting: Implement per-user rate limits on OTP requests to prevent abuse. A common pattern allows 3-5 OTP requests per phone number per hour. Track request counts and return appropriate error messages when limits are exceeded.
- Testing: Use test phone numbers provided by your SMS API to verify your integration without sending real messages. Test happy paths, validation errors, delivery failures, webhook retries, and code expiration.
Building for Production
A reliable 2FA SMS implementation requires attention to the details that matter in production: idempotent sends, webhook-driven delivery tracking, proper error handling, and sender identity configuration. The transactional SMS API you choose should provide clear delivery receipts, sensible retry behavior, and the routing controls needed for consistent deliverability.
Before scaling your 2FA flow, validate that your sender identity is registered appropriately for your traffic volume and regions. Monitor delivery rates and latency as you grow, and establish alerts for anomalous failure patterns. The operational foundation you build now determines the reliability of your authentication system at scale.
How long should OTP validity last?
5-10 minutes is the standard range. Shorter windows improve security but increase user frustration on slow networks. 5 minutes balances security with usability for most consumer apps.
Should I implement OTP resend rate limiting?
Yes. Implement per-user rate limits—typically 3-5 OTPs per hour per phone number. This prevents abuse while allowing legitimate retries. Return clear error messages when rate limits are hit.
What's the difference between delivery receipts and read receipts?
Delivery receipts confirm the message reached the carrier network, not that the user saw it. Treat delivery receipts as carrier signals indicating the message was accepted for delivery, not as proof of user engagement.
Do I need A2P 10DLC for US SMS delivery?
A2P 10DLC (Application-to-Person 10-Digit Long Code) is required for US carriers to send A2P traffic. Registration improves delivery rates and reduces filtering. Plan for the registration timeline when launching US traffic.
Read API docs
Use Notilify to build transactional SMS with clearer delivery state, sender planning, and support visibility.
Read API docs