Skip to main content
Both directions — delivery receipts out to a dlr_url, and MO messages out to a forward.mo.url — share one delivery mechanism, so they share these rules.

The two rules that decide everything

At the default budget, one unreachable endpoint occupies a virtual thread for twenty minutes. Ten attempts, two minutes apart. A customer whose endpoint is down for an hour is not merely missing receipts — they are holding threads for the whole hour, and so is every other customer in the same state.If you run many customers with unreliable endpoints, lower the retry count before you raise the thread ceiling.

A receipt never arrives

1

Was a dlr_url supplied at all?

Over HTTP it must be present and URL-encoded, and dlr must be yes. Setting one without the other does nothing.
2

Is the endpoint reachable from the gateway host?

Not from your laptop — from the host or container the gateway runs on. Egress policy, split DNS and private subnets all bite here.
3

Does it return 200–399?

A 401 behind an authenticating proxy is the commonest silent failure. Since 0.11 a 302 to a login page, which does count as success and swallows the receipt.
4

Did the gateway log the failure?

Forwarding failures and retries are logged. Ten quiet attempts mean the endpoint answered.
5

What did their endpoint actually say?

Open the message in the control panel and read Customer exchange. Each callback attempt is a row carrying the endpoint, the try number, how long it took and the status.To see the answer itself rather than only its status code, set smsg.cdr.events.response = excerpt and reproduce. The Response cell then expands to the first 500 characters the endpoint returned, which is where a 401 behind a proxy stops looking like a 401 from the application.

The placeholder trap

In kannel format, the callback is a bare GET with placeholders substituted — nothing is appended.
A URL with no placeholders receives a request carrying nothing at all — no body, no query, no identifier. The endpoint is hit on schedule, returns 200, and the customer concludes receipts are “working but empty”.This is the single most common receipt integration mistake, and nothing in the system flags it: an empty callback is indistinguishable from a correctly configured one that happens to carry no data.

MO forwards never arrive

MO callbacks are POST, and the same 200–299 and retry rules apply.
  • forward.mo.url is set on the SMPP client worker receiving the MO — not on the listener, and not globally.
  • forward.mo.format is JSON or FORM.
  • The URL may carry placeholders, URL-encoded before replacement:
Full inbound MO detail needs print.mos = true temporarily. Treat that output as sensitive — it contains message content and both addresses.

Receipts that arrive but say the wrong thing

Not a delivery problem, and worth ruling out before you go looking at the network:
  • request.dlrs is off on the vendor. Every message then settles as EXPIRED at the deadline, because nothing ever reports an outcome. The delivery rate becomes meaningless rather than zero — which is worse, since a zero looks like a problem and a meaningless number looks like data.
  • A level 1 receipt is not the outcome. ACCEPTD and BUFFRED both arrive as level 1 and another receipt follows. A client that treats the first receipt as final will report the wrong thing forever.
  • Three segments, three receipts. conf.dlr.multipart defaults to all, so a customer gets one receipt per submit_sm they sent. first or last collapses that to one.

Delivery receipts

The payload, the levels and how to correlate.

Receipt quality

Reported rate against delivery rate.