Skip to main content

The DLR model: two planes, one vocabulary

Four concept pages each cover one slice of delivery receipts — Delivery lifecycle, the DLR and MO gateway pipeline, the Send-side DLR model, and the SMPP edge model. Each answers a different question, but none tells you which plane a given receipt behaviour lives on. When your integration reads a receipt, that distinction is the thing that decides what it means. Both planes operate on one vocabulary: the six canonical delivery states every channel maps into, defined in the Send-side DLR model. What differs is where the state came from and which surface carries it.

The two planes

The carrier plane is the path a carrier’s own receipt takes: a delivery receipt arrives from the carrier through the SMPP gateway, the DLR pipeline verifies the callback, deduplicates replays, maps the receipt onto a message-row status update, and fans the transition out to your webhook subscribers. Every question of the form “what did the carrier say about my message” belongs on this plane. Its definition of states and transitions is Delivery lifecycle; its definition of how the receipt physically crosses into Orbit is the DLR and MO gateway pipeline. The send-side plane is the other direction: for traffic Orbit terminated itself, or for every SMPP client receipt in general, Orbit holds the canonical vocabulary and encodes receipts back out — onto an SMPP bind as a deliver_sm, or to your endpoint as a webhook POST. Every question of the form “what does Orbit report to me, and in what encoded form” belongs on this plane. Its definition of the vocabulary and the encoders is the Send-side DLR model; its definition of where those encoded receipts leave Orbit — bind, webhook, or both — is the SMPP edge model. The reader’s rule: a receipt that names what a carrier returned makes no sense unless it maps into one of the six canonical states, and a receipt that came from Orbit’s own termination path has no carrier leg to consult at all — it crosses directly on the send side. Sort your mental model first; the individual pages then answer the specific question.

The canonical row set

Every delivery event from any channel resolves to exactly one of six states: Carrier stat and err tokens map into this set, and the same vocabulary is re-encoded back out on the send side: Because the same table decodes carrier tokens in and encodes receipt tokens back out, a token a carrier sends in means the same thing as the token Orbit returns on your bind. The Send-side DLR model page defines this mapping; the SMPP edge model page defines the err: token vocabulary (81 not on destination channel, 88 re-engagement required, 89 rate limit, 00 unspecified provider error) carried on the failure arm.

Absorbed traffic

When Orbit’s termination engine absorbs a submission onto another channel — your SMPP submit_sm resolves to a WhatsApp message, for example — there is no carrier to return a deliver_sm from, and the receipt crosses the send-side plane directly. The resolved canonical state is injected onto your bind as a deliver_sm in the same envelope a carrier receipt would have used. Three rules hold on absorbed traffic, and they follow from the canonical model rather than being a per-channel config:
  • Only DELIVRD ends a successful termination. A later read (a “blue ticks” event) is recorded on the message row, but it never produces a second deliver_smDELIVRD already closed the receipt stream for that submission, and a second terminal receipt would be a duplicate. On absorbed traffic, DELIVRD is the only receipt a successful termination produces.
  • Exactly one receipt per absorbed submission. The submission-to-receipt correlation is consumed by the first receipt. Events that arrive afterward are recorded but produce no second receipt.
  • A synchronously rejected submit never yields a returned receipt. If preflight declines the submission, the error travels back in the submit_sm_resp itself, inside the submission hold; the out-of-band receipt encoders never run, and no deliver_sm arrives later to contradict the synchronous answer.
If the submission was terminated as ordinary SMS — that is, genuinely carrier-routed — the receipt is a carrier-generated deliver_sm on the carrier plane with the same canonical vocabulary. Nothing about carrier-routed SMS changed when the absorbed-traffic encoder was added; it fills a hole rather than rewriting an existing path.

The timer contract

When a carrier never returns a receipt at all, a scheduler advances the message row after a per-channel grace window. The grace is part of the DLR contract, not an implementation detail you time against:
  • SMPP-backed channels. The no-DLR grace is 30 minutes. A sent SMS that never receives a receipt flips to submitted_no_receipt; because a genuine delivered, read, or failure receipt can still land afterward, the state is tagged wire-intermediate (is_terminal: false) rather than final. The 30-minute window is deliberately longer than the typical carrier receipt latency so a slow receipt usually wins the race.
  • Meta DM channels. Meta’s Send API never emits delivery receipts, so a Meta DM send is flagged no_dlr_channel and flips to submitted_no_receipt after 5 minutes. On those channels submitted_no_receipt behaves like a functional delivery signal on an opted-in recipient, because Meta guarantees delivery on accept.
The Delivery lifecycle page states the per-channel grace values; the DLR and MO gateway pipeline page describes where the receipt-ingress loop those timers protect actually lives.

Reading SMPP receipts as a REST caller

If your integration does not hold an SMPP bind, you still see the same canonical outcomes — the mode split decides which surface carries each receipt. Per credential, the dlrMode field (from the SMPP edge model) is: A REST consumer receiving a receipt on the webhook leg reads the same canonical status, the mapped Orbit status, and (when they exist) the same stat and err tokens verbatim — the webhook leg and the bind leg describe one event identically, because both start from the same canonical state. Choose webhook or both if your SMPP client can be offline while receipts arrive, or if you want receipts without a bind at all.