delivery receipt (DLR) is a status message, returned after you submit an SMS, that reports its final state — DELIVRD, UNDELIV, EXPIRED, DELETED, ACCEPTD, UNKNOWN or REJECTD — as defined by SMPP. A genuine DLR originates from the terminating operator, not the gateway, so it tells you whether the handset received the message and, via its err field, why it did not.
What a delivery receipt is — and what it is not
The moment you submit a message you get two different signals, and confusing them is the most common source of phantom outages. The first is the submission acknowledgement: on SMPP the SMSC returns a submit_sm_resp with command_status ESME_ROK (0x00000000); on an HTTP API you get a 202 and a message ID. That only means the platform accepted the message for processing. The second signal, which arrives later, is the delivery receipt — an out-of-band report of what the network ultimately did with the message. On SMPP it comes back as a deliver_sm PDU whose esm_class flags it as a receipt; on a REST API it is delivered to your webhook. Wire your system against the DLR, not the submission response — see the SMPP developer guide for the bind and receipt-request flags.
Gateway ack versus a real operator DLR
Not every DLR is created at the same place on the wire, and this is where route quality hides. A real operator-level DLR is generated by the terminating network after the handset acknowledges the short message at the SS7/MAP or Diameter layer; the operator SMSC maps that network-layer report into the SMPP receipt you receive. An intermediate or gateway DLR, by contrast, is synthesised by an aggregator when it hands the message to the next hop — before the terminating operator has confirmed anything. On a clean direct route the two coincide; on a grey route they do not.
This is why "fake DLRs" are dangerous. A grey-route aggregator that cannot obtain a genuine operator receipt will often fabricate a DELIVRD at handoff so its dashboards look healthy — even when the message was silently dropped downstream, filtered, or SIM-box terminated. The tell is a suspiciously perfect delivery rate paired with real users who never received the message, or a route that returns UNKNOWN in bulk (the SMSC has no valid state because a downstream hop never returned a real report). Paste any receipt string into the DLR decoder to see exactly what a route is claiming versus what a genuine operator receipt looks like.
How DLRs flow back to you
There are two transports and one underlying source. Over SMPP, the receipt returns asynchronously as a deliver_sm PDU on your bound session, with the final state carried in the short_message body (the classic id:… stat:… err:… string) and, on v5.0 and many v3.4 implementations, in the message_state and network_error_code TLVs. Over an HTTP API, the same information is POSTed to a webhook you register, usually as JSON. Both are ultimately derived from the network's own status report: 3GPP TS 23.040 defines the SMS-STATUS-REPORT and its TP-Status field, which the terminating SMSC translates into the SMPP message state you read. The SMPP layer is a faithful carrier for a report that is really born on the mobile network.
Whatever the transport, log the same fields for every receipt so you can diagnose by pattern rather than anecdote:
- The message ID, so you can join the DLR to the original submit_sm.
- The stat value (the final message state) and the err value (the network/operator error code).
- The destination operator (MCC/MNC), the sender ID, and the route or bind used.
- The submit-date and done-date timestamps, so you can measure end-to-end latency and spot EXPIRED patterns.
id:1a2b3c4d5e sub:001 dlvrd:001 submit date:2608031200 done date:2608031201 stat:DELIVRD err:000 text:Your BroadNet codeid:9f8e7d6c5b sub:001 dlvrd:000 submit date:2608031200 done date:2608031205 stat:UNDELIV err:062 text:Your BroadNet codeTwo fields carry the diagnosis. The stat field is the standardised final message state (below). The err field is a network/operator-specific code — the same UNDELIV can arrive with different err values on different operators, so err is only meaningful in the context of its terminating network. Note that this err code is distinct from the SMPP command_status you get on submit_sm_resp (ESME_RINVDSTADR, ESME_RTHROTTLED and friends); the two are decoded differently. Use the SMPP error-code lookup for command_status values and the DLR decoder for the receipt string.
The SMPP message states, decoded
SMPP defines a small, closed set of final states plus the transient ENROUTE state you may see before a message settles. The seven states below are the ones that end a message's life, with the typical cause and the action each should trigger. Read them as a distribution across operator, sender ID and route — a single UNDELIV is noise; a route that is 40% UNDELIV on one operator is a route problem.
| DLR state (stat) | What it means | Typical cause | What to do |
|---|---|---|---|
| DELIVRD | The handset confirmed receipt | Message reached the device | Nothing — this is success; spot-check against real user reports to catch fabricated DELIVRDs |
| ACCEPTD | Accepted on the subscriber's behalf (e.g. read by customer care) | Operator marked it accepted without a true handset acknowledgement | Treat as soft-success; investigate any route that returns ACCEPTD in bulk — it often masks non-delivery |
| EXPIRED | Validity period elapsed before delivery | Handset off or out of coverage for the whole window, or SMSC retries exhausted | Retry later or fall back to another channel; confirm your validity period is not too short |
| DELETED | Message removed at the SMSC before delivery | Manual/admin deletion, or spam-filter removal | Check operator content filtering and your sender-ID/content policy |
| UNDELIV | The message is undeliverable | Invalid or unallocated MSISDN, no route, network-layer reject, or firewall block | Validate the number (HLR lookup), then check route quality and sender-ID registration |
| REJECTD | The operator refused the message | Unregistered sender ID, content filter, spam classification, or DND/consent list | Register the sender ID, fix the template, and confirm opt-in for the destination |
| UNKNOWN | The SMSC has no valid state for the message | A downstream hop never returned a real report — often a grey-route black hole | Question the route; a healthy direct route rarely returns UNKNOWN |
Reading DLRs as a route-quality signal
Once you accept that only an operator-level DLR is trustworthy, the receipt feed becomes your best route-quality instrument — better than any coverage map or sales deck. Direct routes return specific, consistent err codes and a stable DELIVRD rate; grey routes return generic failures, a scatter of UNKNOWN, and a delivery rate that swings week to week as operator firewalls catch the traffic and the aggregator silently re-routes. Slice the DLR distribution by terminating operator and sender ID over time: the operators where you see rising UNDELIV/UNKNOWN or vanishing err detail are the ones riding a degrading route. For regulated GCC markets — UAE under the TDRA, Saudi Arabia under the CST — REJECTD concentrated on one sender ID almost always means a registration or template-approval gap rather than a network fault.
BroadNet returns real operator-level DLRs — not gateway acks — across 1,000+ operator connections in 200+ countries, with the stat and err fields exposed per message so route quality is visible in real time, backed by a 99.99% platform SLA and direct A2P routes maintained since 2003. Explore the SMS API or the SMPP developer guide.
Frequently asked questions
What is a delivery receipt in SMS?
A delivery receipt (DLR) is a status report returned after you submit an SMS, telling you the message's final outcome — delivered, undeliverable, expired, rejected and so on. It arrives after the submission acknowledgement, either as an SMPP deliver_sm receipt PDU or an HTTP webhook. A genuine one originates from the terminating operator, not the gateway, so it confirms whether the handset actually received the message.
What does DLR mean?
DLR stands for delivery receipt (sometimes "delivery report"). In SMPP it is the message-state report the SMSC returns for a submitted message, carrying a stat field (DELIVRD, UNDELIV, EXPIRED, DELETED, ACCEPTD, UNKNOWN, REJECTD) and an operator-specific err code. It is the difference between knowing a message was accepted for processing and knowing it was actually delivered to a device.
Why does my SMS show UNDELIV / REJECTD?
UNDELIV usually means the message could not reach the handset — an invalid or unallocated MSISDN, no route, or a network-layer or firewall block. REJECTD means the operator refused it outright, typically for an unregistered sender ID, a content-filter hit, spam classification, or a DND/consent list. Read the err field and correlate failures by sender ID, operator and route to isolate the real cause.
How is a gateway ack different from an operator DLR?
A gateway acknowledgement (submit_sm_resp with ESME_ROK, or an HTTP 202) only confirms the platform accepted your message for processing. An operator DLR confirms what the terminating network actually did with it. Aggregators on grey routes sometimes fabricate DELIVRD receipts at handoff, so an operator-sourced DLR is the only trustworthy signal that the handset received the message.
References
- SMPP v3.4 and v5.0 specifications — delivery receipt format, message_state and network_error_code: smpp.org
- 3GPP TS 23.040 — technical realization of the Short Message Service (SMS-STATUS-REPORT, TP-Status): 3gpp.org
- BroadNet tools — DLR decoder and SMPP error-code lookup.
- BroadNet — SMPP developer guide and SMS API.
