Why OTP SMS Doesn't Arrive: 10 Causes and How to Fix Each
A missing verification code is rarely one problem — it's one of ten. A debugging guide that separates send failures from delivery failures, and the fixes for each.
First, find out which stage failed
Before changing anything, establish where the message died. 'It didn't arrive' covers three very different failures: the gateway never accepted the send, it accepted but the carrier never delivered it, or it delivered and the user didn't see it. Each has a different fix, and guessing wastes hours.
A good provider gives you a per-message trail to tell them apart. On Koipabo every send returns a message id and a price, and the delivery report lands seconds later with a terminal status — delivered, failed, or skipped. If your provider can't answer 'what happened to message abc123?', fix that first; everything below is guesswork without it.
- Accepted but never delivered → destination-side problem (causes 3–7)
- Rejected at send → account, balance, or validation problem (causes 1–2)
- Delivered but user can't find it → handset-side problem (causes 8–10)
Causes 1–2: the send never left
A send is rejected outright when the destination number is misformatted — anything that isn't strict E.164 (a +, country code, no spaces or leading zero) is the single most common false alarm. A number stored as 072159921 instead of +46720159921 looks fine in your database and fails at the gateway every time.
The second rejection cause is capacity: an exhausted SMS quota, an empty pay-as-you-go balance, or no sender enabled for the account. These are sticky and obvious — every message fails, not one in twenty. If exactly one user reports a missing code, it is almost never this.
Causes 3–4: route and country mismatch
International SMS is where most 'missing code' tickets actually originate. You can't reliably send a Bangladeshi number through a Swedish SIM, or vice versa — the carrier on the receiving end decides whether to accept a foreign-origin message, and many route it straight to the bin. A pooled gateway solves this by routing each message through a SIM in the destination's own country; if you're sending everything through one origin, mismatched routes are your problem.
The subtler version is coverage drift: the route that worked last month is gone, or the device serving that country went offline. Watch whether failures cluster on one destination country — that points at the route, not your code.
Causes 5–6: carrier filtering and message content
Carriers filter aggressively. Mobile networks worldwide drop messages that look like bulk marketing, and OTP traffic rides alongside it — a code buried inside promotional copy is far more likely to be flagged than a bare 'Your verification code is 482913'. Keep verification messages transactional: the code, the brand, the expiry, nothing else.
The related trap is sender reputation. Shared short codes and long codes accumulate spam complaints; an alphanumeric sender ID carrying your brand typically clears filters more reliably where the destination supports it. Some markets, though — the US and Canada in particular — force numeric senders, so what your user sees on the handset varies by country.
Cause 7: the message got split
A single SMS segment holds 160 characters in the GSM-7 alphabet — and only 70 if the message contains non-GSM characters such as emoji, accented letters, or Bangla and other non-Latin scripts. Cross that boundary and the message splits into concatenated segments; if one segment is delayed or dropped in transit, the handset may display a partial message, or nothing at all.
This is why a 90-character code message in plain ASCII arrives reliably at a fraction of the cost of the same message with a decorative emoji. For OTP, stay in plain GSM-7 and under 160 characters.
Causes 8–10: the user's handset
When the delivery report says delivered and the user still can't find the code, the problem is almost always local. Do Not Disturb and message filtering on both iOS and Android route unknown senders into a separate 'Unknown Senders' or spam folder — a growing share of 'I never got it' tickets. Ask the user to check that folder before you resend.
Rounding out the list: a full message inbox silently refusing new SMS, a phone that was powered off or out of coverage when the code arrived and expired before it reconnected, and a number that has ported to a network that doesn't forward to the original carrier. None of these are your app's bug — but all of them look identical to the user.
- Check the spam / unknown-senders folder before resending
- Confirm the inbox isn't full
- Shorten the expiry window if users frequently enter stale codes
A five-minute debugging checklist
When a code goes missing, work through this in order. Ninety percent of cases resolve in the first three steps:
- Validate the number is strict E.164 with the country code — not a local-format string
- Pull the message id and read the delivery status: failed, delivered, or still pending
- Check whether the destination country matches the route that handled the message
- Confirm the message is one segment and free of non-GSM characters
- Rate-limit nothing into a false failure — five attempts per code, three codes per number per hour
- If delivered but unseen: ask about the spam folder, then resend once after 60 seconds
Build the fix in, don't patch it per ticket
The teams with the fewest 'code never arrived' tickets are the ones that made the fixes structural: strict E.164 validation before the number is ever stored, a resend button on a 60-second timer so anxious users don't double-send, expiry enforced server-side, and delivery status logged for every message so a support ticket has a factual answer instead of a resend.
On the delivery side, the platform matters too. Koipabo routes each message through a SIM in the destination country so routes aren't mismatched, auto-pauses devices whose heartbeat goes stale so traffic never hits a dead handset, allows only OTP and transactional content — which keeps carrier filters calm — and refunds failed sends automatically, so a bad route costs you nothing but the retry.
Register, top up from $10, and send a test code to your own phone; the delivery report tells you within seconds whether the route, the content, and the number all check out.