Skip to main content
On a transceiver or receiver bind, deliver_sm arrives from the gateway. It carries two different things, and your handler has to separate them before anything else.
Do not treat a receipt as an inbound message. A naive receiver that forwards every deliver_sm to an application inbox delivers id:… sub:001 dlvrd:001 stat:DELIVRD to a human, and — worse — usually also fails to close the record the receipt was there to close.

Reading a receipt

The body follows the conventional format:
id is the message id from your submit_sm_resp. That is the correlation key, and the only one. stat is the raw state — DELIVRD, UNDELIV, EXPIRED, DELETED, ACCEPTD, UNKNOWN, REJECTD, FAILED, BUFFRED, SEEN. EXPIRED, REJECTD and UNDELIV all bill as failures but mean different things.

err and why it may be a number you do not recognise

err is the failure code. 000 on a delivered message; something else when a carrier reported a reason. Historically every code FireFlo did not already know became 018, “unknown” — so a carrier that took the trouble to say exactly what went wrong had that answer replaced with the one value nobody can act on. That no longer happens. What you now receive depends on what your provider has configured for the carrier that carried the message:
Do not assume every err is in FireFlo’s documented set. Carriers number failures privately, so 630 from one is unrelated to 630 from another. Parse the codes you know, treat the rest as opaque, and quote the value when you raise a query — your provider can tell you what that carrier means by it, and map it so you get a stable code next time.
ACCEPTD and BUFFRED are not outcomes. Another receipt follows. If you asked for intermediate notifications with registered_delivery bit 4, your handler must wait for a final state rather than closing on the first receipt.

Three segments, three receipts

By default you get one receipt per submit_sm you sent, each quoting the id you were given for it. Your provider can collapse that to one with conf.dlr.multipart set to first or last, which carries the real sub:/dlvrd: totals. Every receipt reports the same outcome whichever they choose, because the outbound segments are aggregated before any receipt is built. So there is nothing to reconcile between them — pick one and ignore the rest, or ask your provider to send one.

Inbound messages

A deliver_sm without the receipt bits is a real message someone sent to your number. The gateway has to resolve which account owns the destination number before it can route it to you. If that mapping is missing, the message is recorded and delivered nowhere — and there is nothing observable on your side, because as far as routing is concerned it was never addressed to you.
When commissioning a new inbound number, send one message and have your provider confirm it resolved to your account rather than landing unmapped. It separates “my handler is broken” from “the number was never mine”, which are otherwise indistinguishable.

Responding

Answer every deliver_sm with a deliver_sm_resp. An unanswered PDU consumes a window slot at the gateway end, and enough of them stall the session in a way that looks like the gateway has stopped sending.

If receipts never arrive

1

Is your bind able to receive?

A transmitter-only bind cannot. This is the commonest cause and the easiest to miss, because sending works perfectly.
2

Did you ask for them?

registered_delivery at 0x00 means none. Some libraries default it to zero.
3

Does the vendor produce them?

If your provider has request.dlrs off for the vendor carrying your traffic, nothing ever reports an outcome and every message settles as EXPIRED at its deadline.

Submitting

registered_delivery and how it maps to receipt levels.

SMPP FAQ

Windows, throttling, reconnects and stale sessions.