Skip to main content

DLR and MO gateway pipeline

Two kinds of carrier traffic enter Orbit at the SMPP edge:
  • Delivery receipts (DLRs) — the carrier’s acknowledgement of where an outbound message ended up. DLRs drive the status machine behind every message you send.
  • Mobile-originated (MO) SMS — inbound messages a handset sends to one of your numbers.
Both arrive over the same class of gateway connection, and once inside they fan out into different pipelines. This page explains the gateway-level model — how raw carrier traffic crosses into Orbit, and the reconciliation loop that keeps the gateway consistent with your SMPP configuration. Delivery lifecycle covers the status states a DLR can drive; Inbound message resolution covers the tenant-ownership lookup that runs on every inbound message. This page sits underneath both.

The DLR path: carrier to webhook

An outbound send hands off to the carrier at the gateway and returns a message id. From that moment, the carrier emits delivery receipts, and each receipt crosses the same chain on its way into your account:
  1. The carrier — generates the receipt (delivered, undelivered, failed, a read receipt, and so on) against the message id from the original submission.
  2. The SMPP gateway — receives the receipt delivered by the carrier and forwards it to Orbit’s DLR endpoint over a callback.
  3. The DLR pipeline — verifies the callback, deduplicates replays, maps the carrier’s receipt codes onto Orbit’s status vocabulary, and commits a status update on the message row. If the update cannot be committed at that moment, the receipt goes into an internal retry queue and is applied later — the gateway is always given an acknowledgement so it does not re-fire the same receipt indefinitely.
  4. Status fan-out — once the update commits, the matching webhook event is dispatched to your subscribers.
What a delivered receipt leaves behind is a normal lifecycle event; what a receipt code means and which states it can drive (delivered, undelivered, failed, read, and the rest) is defined in Delivery lifecycle. The same chain applies to every SMS-bind provider that returns receipts through the gateway. A DLR is also how terminal-failure fallback gets its trigger: if you stamped fallback_channels on the send, a terminal receipt commits and a new send on the next channel fires automatically. See Fallback on terminal DLR.

The MO path: deliver_sm to inbound message

An inbound (mobile-originated) SMS enters Orbit at the same gateway used for DLRs. The chain is pure ingress — the message moves from the carrier to your tenant’s pipeline without ever touching an outbound path:
  1. The carrier — a handset sends a message to one of your numbers and the carrier pushes it onto a receive bind it holds with the gateway.
  2. The gateway — receives the inbound PDU and forwards it to Orbit’s dedicated MO bridge endpoint over an authenticated callback.
  3. The MO bridge — validates the sender, decodes the body (handling non-Latin encodings and multi-part reassembly), and publishes a normalized inbound event idempotently — a replayed delivery of the same message is dropped as a duplicate rather than processed twice.
  4. Tenant resolution — the normalized event is handed to inbound resolution, which answers the ownership question — which tenant owns the destination number — and routes it accordingly.
What happens from that point (reverse-ownership lookup, per-number route override, webhook/agent/auto-reply dispatch) is covered in Inbound message resolution. The MO bridge’s job is the edge: it receives the inbound PDU, normalizes it, and puts it on the resolution path in an idempotent way.

When MO ingest fails transiently

The happy path above assumes the inbound resolution step can commit on the spot. Sometimes it cannot: a Postgres failover, a connection-pool spike, or contention on the per-sender conversation lock raises a transient error exactly as an inbound message is being written. The carrier already got its acknowledgement — the SMS accepted at the edge is gone from the network’s point of view — so without a second path, that inbound message would be silently lost. Orbit’s answer is an internal MO dead-letter queue:
  1. Enqueue instead of drop. When the ingest step fails transiently, the publisher enqueues a retry job carrying the message (tenant, sender, destination, body, and the original wire payload) instead of dropping it. One queue row per inbound message — a replayed attempt collapses onto the same job rather than fanning out parallel retries.
  2. Bounded retries. A background worker re-publishes the original payload back into the normal MO ingest path on a widening schedule — one minute, five minutes, thirty minutes, then two hours. If the ingest still fails after the fourth attempt, the row is held as a dead letter for seven days instead of being discarded, so the platform team can inspect and requeue it. Once a retry succeeds, the rest of the inbound chain (tenant resolution, webhook and agent dispatch) runs as usual.
Do not confuse this with the egress retry queue in Webhook delivery semantics: that one covers re-delivering an event to your subscriber endpoint when your server can’t accept it. The queue above covers the opposite direction — the inbound ingest path itself, before your webhook was ever in play. It is the MO-side mirror of the DLR retry queue from step 3 of the DLR path: same idea (a transient commit failure parks instead of dropping), applied to inbound messages rather than carrier receipts.

The gateway reconciliation loop

The SMPP gateway’s view of your account — your SMPP bind credentials and any upstream carrier connectors you bring — is not configured directly on the gateway. It lives in your account configuration, and a background reconciliation loop keeps the gateway consistent with it at all times. The loop runs continuously, roughly twice per minute, and does three things on every pass:
  1. Reads the desired state. Your active SMPP credentials and configured upstream carriers — the configuration you manage in the dashboard or via API.
  2. Compares against the gateway. It lists what the gateway currently holds and computes the difference.
  3. Applies the difference. New credentials are provisioned, revoked or suspended ones are disabled or removed, and changed configurations are re-applied. The loop is idempotent: the same pass running again with no difference is a no-op, so a pass interrupted mid-way is simply redone on the next tick.
One safety property is built into the loop: it only ever manages the credentials that came from your account configuration. Binds and connectors provisioned by the platform itself — the inbound carrier trunks that receive your MOs, for example — are explicitly protected so a reconciliation pass can never remove them.

Bind health, surfaced to your dashboard

On every pass the loop also reads the gateway’s view of each bind — how many live sessions your SMPP credential currently has, and whether an upstream carrier connector is bound — and stamps that back onto your account configuration. That is what powers the bind-count and bind-status fields the dashboard renders. The practical effect: a credential your SMPP client just connected flips from unbound to bound within about half a minute of the loop noticing, and an upstream carrier that silently lost its bind is flagged on the next pass. If a bound upstream connector drops without a status change making it obvious, the loop nudges a restart so the bind re-establishes — that rebind also shows up quickly in the dashboard status.

Provider scope: receive-only inbound providers

Orbit’s provider model draws a hard line for inbound-only providers (such as DIDWW): they receive inbound SMS and nothing else. The connector a receive-only provider holds with the gateway is used only for accepting inbound MO traffic; no outbound submission is ever routed onto it. For you, this means the set of providers you can receive inbound from is not the same question as which exit carries your outbound traffic. Outbound sending always goes over Orbit’s designated termination path. A purchased DIDWW number says it can receive SMS and it means exactly that — inbound; that capability never depends on, and never routes through, an outbound path on the same provider.

Where you observe it

Two surfaces let you see this pipeline without reading gateway internals at all:
  • Delivery webhooks. On the DLR side, every status transition that arrives from a carrier receipt fans out to your webhook subscribers. The webhook payload semantics, event types, and retry rules are covered in Webhook delivery semantics.
  • SMPP-edge bind accounting in the dashboard. On the gateway side, every registration of an SMPP credential you hold, and the status of any upstream carrier connector you configured, is stamped back by the reconciliation loop. When “my SMPP bind shows bound” or “my carrier connector says it’s connected” is the question, this is how the dashboard knows.
Together those two views tell you what the gateway thought happened, without needing to inspect it directly.