If your application logs status=delivered and you treat that as "the human read the code," you
have a silent failure mode. SMS delivery receipts (DLRs) are a chain of handoffs, and every link
can report success while the message never lands on a handset.
your API call ──▶ provider accepted ──▶ carrier accepted ──▶ handset ack ──▶ human reads it
│ │ │ │ │
201 Created "queued"/"sent" "delivered" (sometimes) (never observable)
The status you receive usually reflects the furthest point that bothered to report back. Several
carriers return a positive receipt when the message enters their system, not when the phone
acknowledges it. Some return nothing at all and your provider synthesises a value. So:
sent / accepted — your provider has it. Says nothing about the carrier.delivered — someone downstream said OK. Depending on the route, that "someone" may be anaggregator, not the handset.
undelivered / failed — reliable when present, but absence of failure is not success.Model status as pending | confirmed | failed, and make pending a first-class state with a
deadline rather than an optimistic assumption.
STATUS_MAP = {
"delivered": "confirmed",
"undelivered": "failed",
"failed": "failed",
"rejected": "failed",
"expired": "failed",
"sent": "pending",
"queued": "pending",
"accepted": "pending",
}
def on_callback(evt):
state = STATUS_MAP.get(evt.status, "pending") # unknown status → pending, never confirmed
store.update(evt.message_id, state=state, raw=evt.status, at=evt.timestamp)
Mapping unknown strings to pending rather than confirmed is the entire trick. Providers add new
status values; a permissive default silently turns new failure modes into fake successes.
Delivery webhooks are public endpoints that assert things about your money and your auth flow.
def handle(request):
if not verify_signature(request.headers["X-Signature"], request.body, SECRET):
return 403 # 1. authenticate
evt = parse(request.body)
if store.seen(evt.event_id):
return 200 # 2. idempotent: retries are guaranteed
if evt.timestamp < store.last_timestamp(evt.message_id):
return 200 # 3. out-of-order: don't regress state
apply(evt)
return 200 # 4. ack fast, process async
At-least-once delivery is the norm — you will get the same event twice, and you will get a
delivered after a failed for the same message. Without the ordering guard, a late callback can
flip a correctly-failed message back to confirmed.
Don't wait for a failure callback that may never come. Set a timer at send time:
schedule(check_in=timedelta(seconds=45), message_id=msg.id)
def on_timer(msg_id):
if store.get(msg_id).state == "pending":
offer_alternative(...) # voice call, email, authenticator app, "try again"
Forty-five seconds of a spinner is the ceiling before people leave. A pending-with-deadline model
gives you a place to hang that fallback; a two-state model doesn't.
Once a day, pull the provider's own record for anything still pending past its window and settle
it. Webhooks get lost — dropped by your load balancer during a deploy, blackholed by a firewall
rule, silently 500'd during an incident. A reconciliation job is thirty lines and turns "we think
it worked" into "we checked."
A meaningful share of international non-delivery isn't a technical failure at all — it's a
regulatory one. Many countries require pre-registration of alphanumeric sender IDs before
traffic is accepted; unregistered senders get filtered, often with a positive-looking receipt
upstream. Others ban alphanumeric senders entirely and rewrite them.
We publish what we verify about those rules as an open dataset — regime, registration requirement,
regulatory basis, last-verified date, plus the encoding class per country:
github.com/SMSRoute-cc/sms-sender-id-regulations (CC-BY-4.0, JSON + CSV)
If your delivery rate is fine in nine countries and terrible in the tenth, check the registration
column before you rewrite your retry logic.
*I work on smsroute.cc — the dataset above comes out of operating these
routes. The patterns here are provider-agnostic; MIT-licensed clients if useful: