· 3 min read

How SMS delivery reports actually work (SMPP DLRs explained)

What really happens between "message sent" and "delivered" on an A2P SMS route — submit_sm, message IDs, delivery receipts and the statuses that matter.

By Kushal Sapkota

How SMS delivery reports actually work (SMPP DLRs explained) — Kushal Sapkota

If you sell SMS — OTPs, alerts, marketing — your clients will eventually ask one question: "Did it arrive?" The answer lives in the delivery report, or DLR. Here's how it actually works on an SMPP route, from the point of view of someone who runs one.

Sending: submit_sm

Your platform talks to an upstream SMSC (the carrier or an aggregator) over SMPP, a long-lived TCP session. To send a message you issue a submit_sm with the sender, the destination number, the text and — importantly — a flag asking for a delivery receipt (registered_delivery = 1).

The SMSC answers with submit_sm_resp. This is the first trap: a successful response does not mean the message was delivered. It only means the SMSC accepted it. What you get back is a message ID — the SMSC's own reference for that message.

Store that ID next to your own message record. Everything that follows depends on it.

Waiting: the receipt comes back later

Seconds or minutes later (sometimes hours, if the phone is off), the SMSC sends you a deliver_sm on the same session. It looks like an incoming SMS, but the esm_class marks it as a delivery receipt, and its text follows a well-known pattern:

id:A1B2C3D4 sub:001 dlvrd:001 submit date:2510251430 done date:2510251431 stat:DELIVRD err:000 text:Your OTP is...

You match id against the message ID you stored, and update the status. Many SMSCs also put the ID and state in optional TLVs (receipted_message_id, message_state) — prefer those when present, they're less ambiguous than parsing text.

The statuses that matter

stat Meaning Final?
DELIVRD Reached the handset Yes
UNDELIV Couldn't be delivered (bad number, blocked…) Yes
EXPIRED Validity period ran out (phone off too long) Yes
REJECTD Refused by the network or route Yes
ACCEPTD / ENROUTE Still in progress No
UNKNOWN The SMSC doesn't know Treat as final, flag it

The err field carries a network-specific error code — worth keeping, because it's how you diagnose a bad route.

Things that bite in production

  • IDs in different formats. Some SMSCs return the ID in hex in submit_sm_resp and decimal in the receipt (or the other way round). Normalise before matching.
  • Receipts before responses. On a fast route the DLR can arrive before you've finished saving the submit_sm_resp. Queue unmatched receipts and retry the match for a short while instead of dropping them.
  • Long messages. A long SMS is split into parts, each with its own message ID and its own receipt. Decide whether the message is "delivered" when all parts are, or when any is — and be consistent.
  • Fake DLRs. Some low-quality routes return DELIVRD for everything. If one route's delivery rate is suspiciously perfect, test it with real numbers you control.

Why it matters for billing

A DLR isn't just a status badge. Once every message has a route, a message ID and a final status, you can answer the questions that run an SMS business: delivery rate per route, cost per delivered message, and exactly how much credit each client has used. That's the core of what I built into Bhairav SMS.

If you're building on SMPP and stuck on delivery reports, open a ticket — happy to help.