SMPP production lifecycle
Connect via SMPP gets a bind on the wire and one message sent; the wire-level recipes show the exact bytes. This page is the layer after the first successfulsubmit_sm: the decisions and discipline that keep a bind healthy at 2 a.m. — which dlrMode to run, how to keep the session alive and re-establish it safely, how much to push through one bind, and the symptom-to-fix table for the failures an aggregator on-call actually sees.
1. Recap: bind and credentials in one paragraph
Create a credential withPOST /api/v1/messaging/smpp/credentials (or Developer → SMPP in the dashboard), copy the system_id and the one-time 8-character password, and bind a transceiver to smpp.orbit.devotel.io on 2775 (plain) or 3550 (TLS). The full walkthrough, runnable Python and Node clients, ports, and the diagnostic ladder live in Connect via SMPP; the byte-level bind and submit listings live in SMPP wire-level recipes. This guide assumes a bind that works and covers what changes when it must keep working.
Two facts from that page carry most of the weight here:
- One credential supports one live session at a time. A second bind while the first is alive is rejected with
ESME_RALYBND. Scaling out means more credentials, not more binds per credential. - Every
submit_smpasses the same compliance gates (opt-out suppression, quiet hours, consent) as the REST path. A gate rejection arrives as a nonzerocommand_statusinsubmit_sm_resp, never as a silent drop.
2. Choose a DLR delivery mode
ThedlrMode field on the credential decides where each delivery receipt leaves Orbit:
Which one to run:
Switching is a
PATCH on the credential row and propagates within one reconciliation pass (about 30 seconds); receipts generated before the change keep the mode in effect at generation time. If a termination rule moves your traffic off the SMPP ingress (the absorbed-traffic case in the receipt window guide), the rule can require dlrMode webhook or both (require_dlr_mode) so a bind-only credential never ends up as a receipt black hole.
The deliver_sm PDU and its webhook twin
A receipt on the bind is adeliver_sm whose message_id is the exact string your submit_sm_resp returned — that id is the join between your operation table and the delivery world:
stat: from short_message and join on message_id. On absorbed traffic (say, SMPP submits terminated onto WhatsApp) the same envelope carries the canonical Orbit failure vocabulary in err: — 81 (not on destination channel), 88 (re-engagement window lapsed), 89 (rate limit), 00 (unspecified provider error) — per Send-side DLR model.
With dlrMode webhook or both, the same receipt is POSTed as a lifecycle event carrying the same canonical status, so one table maps the two planes:
Two asymmetries to code around:
read has no SMPP stat (DELIVRD already fired on the bind), and submitted_no_receipt is a webhook-plane status with no bind-side stat. The bind and webhook encodings are otherwise identical by construction, per SMPP edge model.
3. Long-lived bind discipline
One transceiver per process, one process per credential. A transceiver carries sends and receipts on one session, which is what almost every integration wants. Split a receiver-only bind out into its own process only when you deliberately scale receipt consumption separately — and give that process its own credential, becauseESME_RALYBND means two processes can never share one.
The discipline that keeps the session alive:
- Keepalive. Send
enquire_linkevery 30–60 seconds and treat a missingenquire_link_respafter a 10-second deadline as a dead bind. Silence longer than the edge’s idle window (30 minutes by default) gets the session torn down server-side. - Reconnect with backoff and jitter. After any drop, rebind on an exponential schedule with full jitter — for example a 1 s base doubling to a 60 s cap,
delay = random(0, min(cap, base * 2^attempt)), resettingattempton a successful bind. A rapid rebind loop is the bind-abuse pattern every SMPP server throttles and flags; jitter also stops a fleet of clients from re-binding in lockstep after an edge restart. - Drain before unbind. On planned shutdown, stop enqueueing, wait for in-flight
submit_sm_respand pendingdeliver_sm, thenunbind. Receipts in flight across a TCP close do not re-queue. - Budget the reconciler. Credential changes — rotate, suspend,
dlrMode,tpsLimit,allowedCidrs— take effect within one reconciliation pass (about 30 seconds). Rotating a live credential invalidates the old password immediately and drops the live bind; schedule it in a maintenance window. - The receipt timeout window. When your submits are absorbed onto another channel, the UNDELIV you fail over on fires at the rule’s
receipt_timeout_seconds(10–120 s, default 30). Set it against your measured confirmation curve, not the default — SMPP: the no-delivery receipt window is the how-to.
Detecting a zombie bind
A zombie bind is a session that your monitoring believes is dead but that still holds the credential’s single slot — a half-killed replica, a client whose TCP connection is open but whose read loop is stuck. Three signals expose it:ESME_RALYBNDon (re)bind while “nothing else is connected.” Something holds the slot. This is the unambiguous one.- Your side is silent but the dashboard disagrees. The reconciliation loop stamps each credential’s live bind count and status back onto your row about every 30 seconds, and Developer → SMPP → Credentials renders it. If every one of your clients is down but the credential still shows a live bind, a zombie holds it.
- Your own keepalive telemetry. Track
enquire_link_respmisses andsubmit_sm_resprates per bind in your metrics; a TCP connection that is open but unanswered is a zombie on your side of the wire, even before the edge notices.
unbind leaves the slot occupied until the edge’s idle teardown.
4. Throughput tuning
Four knobs, in the order they bind:- The credential’s
tpsLimitis the contract — 50 submissions per second by default, adjustable per organization up to the platform ceiling. The cap counts per second per bind on a sliding window: a burst in one second doesn’t taint the next, and exceeding it answerssubmit_sm_respwithESME_RTHROTTLED. - The window is your pacing mechanism. SMPP has no batch PDU; batching is pipelining — many
submit_smin flight over one bind, drained by their responses. Keep a window of unacknowled submits (start in the low tens), and raise it only whilesubmit_sm_resplatency stays flat and noESME_RTHROTTLED/ESME_RMSGQFULappears. A full window means stop enqueueing, not drop. - Ramp, don’t blast. On a fresh bind or after a reconnect, ramp your enqueue rate over the first minutes instead of opening at full rate; a cold path absorbs less than a warm one, and
ESME_RMSGQFULis the signal you didn’t. - One bind has a practical ceiling. If a raised
tpsLimitstill binds your throughput, open several binds (several credentials) and spread load — that is the supported scale-out path, not pushing one session past its limit.
If
submit_sm_resp p99 climbs toward your deadline while the send rate holds, the window or the upstream path is saturating — back the enqueue rate off before the edge does it for you.
5. Failure runbook
Work the matching section, not the whole page.Bind rejected
Symptom:bind_transceiver_resp returns a nonzero command_status.
A suspended credential also rejects new binds and drops live ones within a reconciliation pass — if a bind that worked an hour ago now fails, check the credential status before the network.
submit_sm_resp with nonzero command_status
Symptom: The bind is healthy; individual submits are refused.Unacknowledged submit_sm flood
Symptom: The window fills with submits that get nosubmit_sm_resp; enqueue stalls.
- Stop enqueueing immediately; the window is your brake.
- Read the socket. A client that stopped reading (backpressure bug) sits on a healthy connection with a full window; draining responses and
deliver_smcan unstick it without a rebind. - Probe with
enquire_link. A response proves the session alive — keep draining. No response inside 10 s means the TCP path is half-dead: rebind with the backoff schedule above. - Reconcile, don’t blind-resend. Every submit that never got a response is in “unknown”: retrying risks duplicates. Record what you have (sequence number, and
message_idfor everything that did ack) and reconcile against the webhook plane or your message history — the statuses eventually land asdelivered,undelivered, orexpiredevents.
DLR loss
Symptom:submit_sm_resp said accepted; no receipt arrives inside your window plus margin.
dlrModeisbindand your client was down at receipt time. Receipts queue for the next bind. Bind again and drain, or move the credential towebhook/bothif client downtime is a normal state for you.dlrModeiswebhookand the endpoint is failing. Delivery retries run for about 4.3 hours before the event dead-letters; check your endpoint’s 2xx rate and the DLQ, per Build a DLR-only webhook consumer and Webhook delivery semantics.- Absorbed traffic. The synthesized UNDELIV fires at
receipt_timeout_seconds, not at carrier speed — a quiet bind on absorbed submits is often just the window still running. - The handset never receipts. Some MNOs confirm slowly or never; the row closes
expiredlate. The no-delivery receipt window and submitted-no-receipt cover the tuning and the diagnosis.
6. Observability
Where to look first. Developer → SMPP is the per-system_id console: the Credentials tab shows each credential’s live bind state and count (stamped by the reconciliation loop about every 30 seconds) beside the connection details; the Carriers tab shows each upstream connector’s bindStatus (BOUND / UNBOUND / BIND_FAILED) and lastSeenAt; the Compliance tab shows how the tenant gates apply to this wire. Per-message, the message row’s status timeline shows how long confirmations actually take on each channel — the curve you tune the receipt window against.
What to alert on, on your side:
Handing a capture to support. On the SMPP wire the tool is
tcpdump (sngrep belongs to SIP legs elsewhere in a CPaaS stack, not this one):
3550 the capture shows framing and timing but not payload, since the session runs inside TLS — pair it with your client’s PDU log (the logging.DEBUG trace in the Python example, the pdu event in the Node one). Send support the capture, your system_id (never the password), the client library and version, and the failing time window with timezone; the sequence numbers in the capture line the session up against the edge’s view.
See also
- Connect via SMPP — bind walkthrough, ports, credential lifecycle, diagnostic ladder
- SMPP wire-level recipes — the byte-level bind/submit/keepalive listings
- SMPP: the no-delivery receipt window —
receipt_timeout_secondson absorbed traffic - SMPP edge model — the reconciled relay underneath the bind
- Send-side DLR model — the canonical receipt vocabulary
- Build a DLR-only webhook consumer — the durable receiver for the webhook leg
- SMPP bind and command status codes — the full status table