"I never got the code."
Every product with SMS verification has heard this. The support ticket usually includes a screenshot of an empty input field and a timestamp showing the user has been waiting for three minutes.
The instinct is to treat this as a bug -- something to reproduce and patch. It is not one bug. It is five separate failure points that all sit between "send the SMS" and "user types the code," and any one of them can eat the message.
The five places a code can disappear
1. Carrier spam filtering. Every SMS passes through carrier-level filters before it reaches a phone. A sender that is unregistered, has a poor reputation, or is sending from a number that recently changed use case can get silently filtered. The sender never sees an error. The message simply does not arrive. This is the most common cause and the hardest one to notice, because nothing in your logs says "blocked" -- it just says "sent."
2. Delivery delay. Carrier networks queue messages during high load. A code that takes 90 seconds to arrive is common on busy networks, especially internationally. Most OTP flows expire the code in 5-10 minutes, so a 90-second delay usually still works -- but the user has often already given up and clicked "resend" by then, which leads directly to the next problem.
3. Retry storms. A user who does not see the code within 15 seconds clicks "resend." Then again. Then again. Each new code invalidates the previous one -- so if the first code was just delayed and arrives 40 seconds later, it no longer works. Worse: carriers watching for SMS traffic pumping fraud see a pattern of many codes to one number in a short window and may throttle or flag the sender. The user's own impatience causes the actual failure.
4. Wrong channel for the destination. SMS delivery reliability varies enormously by country. Some markets have excellent SMS infrastructure. Others -- due to local carrier filtering, roaming restrictions, or destination-specific registration requirements -- see meaningfully worse delivery rates. A flow that only offers SMS has no fallback when the channel itself is the weak link for that specific user.
5. Phone state. Do Not Disturb, airplane mode, an app-level SMS filter, a full inbox, roaming disabled while traveling -- any of these silently drop the message on the receiving end, with zero signal back to the sender. From your side, the SMS was delivered successfully. From the user's side, nothing happened.
Why this matters more for OTP than for anything else
A marketing SMS that arrives 10 minutes late is mildly annoying. An OTP code that arrives 10 minutes late is useless -- the code has expired, the user has abandoned the signup flow, and you have lost a conversion.
OTP is the one SMS use case with zero tolerance for delay. That means the design has to assume delay and failure will happen, rather than hope they won't.
The fix is not "improve deliverability." It's "design for failure."
Give the user a second channel before they ask for one. Instead of a single "Resend SMS" button, offer "Call me instead" after 20-30 seconds. A voice call that reads the code aloud bypasses SMS filtering entirely -- if the SMS got silently dropped, the call still gets through.
Use a real wait, not a fixed timer. Rather than showing a static "Enter the code you received," have your backend block on message receipt and confirm on your end that a message was actually sent and delivered, not just queued. This turns "did it actually go out" from a guess into a fact you can act on.
Rate-limit resends server-side. Do not let a user generate five codes in ninety seconds. Enforce a minimum gap (15-20 seconds) between resend attempts. This protects the user from invalidating a code that was simply delayed, and protects your sender reputation from looking like a traffic-pumping pattern.
Offer WhatsApp where it's available. For international users, WhatsApp delivery does not pass through the same carrier SMS filtering pipeline. If your audience is global, a WhatsApp-first OTP with SMS fallback often outperforms SMS-first.
Extend the expiry window slightly. Five minutes is common. Ten is safer for international traffic where delivery delay is more likely. The security tradeoff of a slightly longer window is almost always worth the conversion gain.
What this looks like with Dial
Dial's verification flow is built around exactly this failure model: send, then block on actual delivery, not a fixed timer.
# Send the OTP and wait for it to actually arrive
curl -X POST https://getdial.ai/api/v1/messages/wait \
-H "Authorization: Bearer $DIAL_API_KEY" \
-d '{"phone_number_id": "clxxx...", "timeout": 30}'
# Response arrives when the OTP SMS is received -- not a guess
# { "body": "Your code is 847291", "from": "+15005550006" }Behind that one call: sender registration and reputation are Dial's responsibility, not yours, and if a channel or number is underperforming for a given destination, that is exactly the kind of thing worth flagging to us. Every deployment has a different mix of countries, volume, and urgency, and the right fallback strategy (voice-first vs. WhatsApp-first vs. SMS-only) depends on where your users actually are.
If OTP delivery is a live problem for your product -- especially for international users -- email us at [email protected] with your traffic mix and we will help you design the right fallback sequence for your specific case.
The short version
"I never got the code" is rarely one bug. It's carrier filtering, network delay, impatient retries, the wrong channel for that destination, or a phone in the wrong state -- and usually you can't tell which one happened from your logs alone.
The fix is not chasing 100% SMS deliverability, which does not exist anywhere. It's designing a flow that assumes some percentage of codes will not arrive on the first try, and gives the user a second path before they have to ask for one.