Interactive figure
How fast is each channel, really
Wall-clock time to a delivered message is dominated by what happens before the send: template approval, queueing and carrier acceptance. The network is rarely the slow part.
Where the time actually goes#
For a one-time code — the most latency-sensitive message most businesses send — the budget usually looks nothing like people expect:
- Your own processing. Deciding to send, rendering, checking consent. Typically tens of milliseconds, occasionally seconds if the consent check hits a cold cache or a synchronous third-party call.
- Provider acceptance. The API call. Fast, and the part everyone measures.
- Queueing. The part nobody measures. Under load, or when a route is congested, this dominates everything else.
- Carrier handling. Out of your control, variable by market, and the reason the same code takes two seconds in one country and twenty in another.
- Handset display. Doze modes, battery optimisation and notification throttling on the device itself.
The API call — the only segment most teams instrument — is frequently the smallest term.
The part you control is the smallest part, which is why tuning it first is such a common and such an expensive mistake.
A one-time code, from button press to handset, in milliseconds
Three things that actually help#
Measure end to end, not to the API response. A timestamp when you decided to send and a timestamp when the delivery receipt arrives. Everything in between is one number, and it is the number the customer experiences.
Watch the tail, not the mean. Nobody complains about the median. They complain about the 99th percentile, and in messaging the tail is very long — a mean of two seconds routinely hides a p99 in the tens.
Do the slow work before you need to send. Consent state, template approval, route selection: precompute what you can. The send path should be short, and it should not be the first place a cache miss shows up.
Choose the route, not the code path. A direct operator connection in-market beats every optimisation you can make above it, by an order of magnitude. This is a purchasing decision that presents itself as an engineering one.
Send before you need to. For anything predictable — a scheduled reminder, a pre-authorised code — the latency budget starts when you decide, not when the user waits. Most of the time pressure in these systems is self-inflicted.
Stop measuring the average. The average is fine and nobody experiences it. The 95th percentile is the number a customer service team feels, and the gap between the two is usually the whole story.
The design consequence for codes#
If a code has to arrive in seconds, do not make its delivery the only path forward. Offer a fallback the user can trigger — resend, a different channel, an in-app confirmation. The cost of building that is far lower than the conversion lost to a carrier having a bad afternoon in a market you cannot see into.
A code screen that survives a slow network
- 01Show the wait
A visible timer beats a spinner. The user needs to know how long is normal.
- 02Offer the alternative early
A "call me instead" route at ten seconds, not at sixty. Voice arrives on a different path.
- 03Never expire before arrival
A code valid for sixty seconds on a network that takes eight is a support ticket waiting to happen.
- 04Allow one resend, then change route
The second attempt down the same slow path is not a different attempt.
- 05Log the round trip
Per market, per hour. This is how you find out that Tuesday afternoons in one country are broken.
And the one about retries#
Retry with backoff and a cap, and make retries idempotent from the recipient's point of view. The failure mode here is not a slow message; it is four copies of the same code arriving at once, two of which are already invalid. That is worse than the original problem, and it is entirely self-inflicted.
A retry that goes down the same route that just failed is not a retry, it is a second attempt at a failure. It doubles the load on the path that is already struggling and roughly doubles the time before the user gets anything.
Retry on a different route or a different channel, or do not retry at all — tell the user instead. And put a ceiling on it: three attempts, then stop and offer something else. Systems without a ceiling produce the outage-day pattern where every user is retrying and nothing gets through.
What to take away#
Almost none of the latency is yours. Buy the route, send early where you can, measure the 95th percentile per market, design the interface for the slow case, and never retry down the path that just failed.