Troubleshooting: SMPP bind rejects and command_status codes
Two failure classes arrive on the wire itself when you run a raw SMPP client against Orbit’s edge (smpp.orbit.devotel.io:2775 plain TCP, :3550 TLS):
a bind reject — the bind_transceiver / bind_transmitter /
bind_receiver response carries a non-zero command_status — and a
submit reject — the submit_sm_resp nack carries one of the ESME_*
status codes. This page decodes both, then covers the two receipt-path
failure classes (a DLR that never arrives, and receiver-side MO handling).
Work the relevant table top to bottom — the first row that matches is the
fix. For the full connect/SDK walkthrough see
Connect via SMPP; for the model underneath the bind
(reconciliation, credential lifecycle, BYO carriers) see
SMPP edge model.
1. Bind rejects: bind_*_resp with a non-zero command_status
A bind reject comes back in thebind_*_resp PDU as a non-zero
command_status; read the status from that PDU before assuming the network
ate it.
Three non-decoder causes account for the “bind returns nothing at all”
symptom — work them before assuming a wire-level reject:
- Port misadvice (TLS vs plain-TCP). Both ports speak the same protocol
and take the same credentials; the difference is transport only. A client
pointed at
3550without TLS (or at2775with TLS enabled) can complete the TCP connect and then stall on the handshake — it looks exactly like a hang, not a reject. Check the client library’s TLS/use_tlsflag matches the port, and verify against thesmpp.orbit.devotel.iohostname. - A blocked port drops, not refuses. Only
2775and3550are open; every other port is closed to the internet and a wrong-port connect sits open until your client gives up. Set an explicit connect timeout (5–10 s) so a wrong port fails in seconds instead of hanging. - Credential status flip hasn’t reconciled. A
suspendedcredential rejects new binds and drops existing ones; arevokedone removes thesystem_idfrom the relay entirely. Flipping the status in the dashboard takes effect on the wire within one reconciliation pass — budget a minute. If the dashboard saysactivebut binds still reject after that, escalate with the credential id.
2. ESME_* submit rejects: command_status decoder for submit_sm_resp
These arrive on thesubmit_sm_resp nack — the bind is up; the edge refused
the message.
A submit that is accepted (
command_status=0) on the bind and then never
produces an outcome is the receipt class below, not a reject — move on.
3. DLR never arrives after an accepted submit
An acceptedsubmit_sm returns message_id in the submit_sm_resp — the
row exists. A receipt that never lands has three canonical owners, in order
of how often they are the cause:
dlrModepoints the receipt elsewhere. On the credential,dlrMode = "bind"(the default) streams the receipt back as adeliver_smon the live session; if the bind that is asking is closed, the receipt waits for the next bind.dlrMode = "webhook"has nothing to do with the session — it POSTs todlrWebhookUrl. Point the mode where the listener is.- The row was absorbed onto another channel, and the window closed.
When a termination rule absorbs your
submit_smonto another channel (WhatsApp OTP is the common case), no carrier emits a receipt — Orbit synthesizes one and the deadline is per-rule. If you seestat:UNDELIVwith a reason likeno delivery within 45 s of acceptance, that is the armed window firing, not a missing receipt; tune it per SMPP: the no-delivery receipt window. - The receipt arrived but your handler dropped it. A client that nacks
or ignores
deliver_smPDUs because its handler crashed loses the receipt the same way a missing receipt reads. Log every inbound PDU at debug before assuming the edge held it.
ACCEPTD → DELIVRD → UNDELIV mapped
to canonical sent/delivered/failed states, err tokens 81/88/89/00 —
see SMPP edge model.
4. Receiver-side (MO) path: inbox bind and opt-in
Traffic received on your SMPP bind — pass-throughsubmit_sm PDUs your own
ESME clients and partner aggregators send you — lands in one of two Inbox
lanes by tenant choice:
- The default (off) records each bind-received submit as a Messages row
only — no Inbox conversation, no
inbox_new_conversationnotification. If agents do not work bind free-text in the Inbox, leave it off. - Turn the toggle on when a two-way aggregator workflow means agents genuinely reply from the Inbox; each bind submit then upserts a conversation per send and rings the new-conversation bell.
false, there is no conversation to
open, and it does not retro-open conversations for traffic recorded while
it was off. See SMPP Inbox opt-in for
the dashboard and API paths (GET/PUT /messages/smpp-bind-inbox).
Fixes you should NOT try
- Do not rebind in a tight loop after a credential reject. A rejected bind answered in milliseconds means the edge refuses the credential; more attempts only trip the throttled-rebind gate. Fix the credential, then rebind with backoff.
- Do not treat ESME_RTHROTTLED as a transient. A second submit at the
same rate re-fails; back off with jitter or raise
tpsLimit. - Do not open a fresh bind per message or per batch. Each bind costs a full handshake; the edge treats a rapid rebind loop the same way every SMPP server does — throttled and flagged. One long-lived bind per sending process.
- Do not send ESME_RINVDSTADR retries at the same destination. The destination is byte-rejected; only ever fix the address, never resend.
- Do not point dlrMode at both and then ignore one leg. A
bind+webhookreceipt delivered to a webhook endpoint your stack drops is still delivered; pick one leg and drain it.
When to escalate
Open a support ticket when one of these holds:- The dashboard shows the credential as
activebut binds still reject withESME_RINVSYSIDorESME_RINVPASWDafter one reconciliation pass (about a minute) — that is reconciler-side, not credential-side. submit_smnacks with acommand_statusnot in the decoder table.- A
submit_smaccepted on the bind never produces either a receipt or astat:UNDELIVpast the armed no-delivery window for that rule, anddlrModealready points at the live leg.
- Your tenant ID (Settings → Organization, or
organizationIdfromGET /api/v1/me). - The credential id (
smpp_…) and thesystem_id(smpp_sys_…) returned at creation. - A failing PDU trace: the
command_statusvalue and the PDU type (bind_*_resporsubmit_sm_resp), plus themessage_idfrom thesubmit_sm_respif the submit was accepted.
See also
- Connect via SMPP — ports, TTL/keepalive, and the diagnostic ladder
- SMPP edge model — the reconciliation model underneath
- SMPP: the no-delivery receipt window — tune the absorbed-receipt window
- SMPP Inbox opt-in — decide whether bind submits open Inbox conversations
- SMPP recipes — the rule-level how-tos for absorbed traffic
- SMPP API reference — the full
/messaging/smppendpoint surface