> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting: SMPP bind rejects and command_status codes

> Decode SMPP bind rejects (wrong system_id or password, IP allow-list, suspended credential) and the ESME_* command_status values returned in submit_sm_resp nacks (throttle, bad destination, message too long) — plus receipt never arriving and receiver-side (MO) inbox handling.

# 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](/guides/smpp); for the model underneath the bind
(reconciliation, credential lifecycle, BYO carriers) see
[SMPP edge model](/concepts/smpp-edge-model).

## 1. Bind rejects: bind\_\*\_resp with a non-zero command\_status

A bind reject comes back in the `bind_*_resp` PDU as a non-zero
`command_status`; read the status from that PDU before assuming the network
ate it.

| command\_status                  | Meaning                  | Fix                                                                                                                              |
| -------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `ESME_RINVPASWD` (0x0E)          | Wrong password           | Re-copy it exactly — 8 characters, one reveal window. If the window has passed, rotate the credential and use the new password.  |
| `ESME_RINVSYSID` (0x0F)          | Unknown `system_id`      | Use the exact id returned at creation (`smpp_sys_…`), not your description. Also the observable when the credential was revoked. |
| `ESME_RINVBNDFMT` (0x15)         | Source IP not permitted  | Your egress IP is outside the credential's `allowedCidrs` allow-list. Widen the CIDR on the credential or clear the list.        |
| `ESME_RTHROTTLED` (0x58) on bind | Rebinding too frequently | Open one long-lived bind per sending process; a rapid rebind loop is throttled. Rebind with backoff, not in a tight loop.        |

Three non-decoder causes account for the "bind returns nothing at all"
symptom — work them before assuming a wire-level reject:

1. **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 `3550` without TLS (or at `2775` with 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_tls` flag matches
   the port, and verify against the `smpp.orbit.devotel.io` hostname.
2. **A blocked port drops, not refuses.** Only `2775` and `3550` are 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.
3. **Credential status flip hasn't reconciled.** A `suspended` credential
   rejects new binds and drops existing ones; a `revoked` one removes the
   `system_id` from the relay entirely. Flipping the status in the dashboard
   takes effect on the wire within one reconciliation pass — budget a
   minute. If the dashboard says `active` but binds still reject after that,
   escalate with the credential id.

## 2. ESME\_\* submit rejects: command\_status decoder for submit\_sm\_resp

These arrive on the `submit_sm_resp` nack — the bind is up; the edge refused
the message.

| command\_status           | Returned when                 | Fix                                                                                                                                     |
| ------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `ESME_RTHROTTLED` (0x58)  | Over the credential's TPS cap | Back off with jittered retry or raise `tpsLimit`; spread load across more than one bind if the ceiling still binds.                     |
| `ESME_RMSGQFUL` (0x14)    | Edge queue full               | Slow the enqueue rate; drain inbound `deliver_sm` receipts flooding the session first.                                                  |
| `ESME_RINVDSTADR` (0x0B)  | Bad destination format        | Normalize to E.164 digits; only set `dest_addr_ton=1` if the client requires it.                                                        |
| `ESME_RINVSRCADR` (0x0A)  | Bad source address            | Use an alphanumeric sender or a number the credential may use; comb through sender-pool/ownership pages if the source is a pool member. |
| `ESME_RINVMSGLEN` (0x04)  | Message too long              | Split into concatenated segments with the UDH marker (`esm_class=0x40`), or shorten the payload.                                        |
| `ESME_RSUBMITFAIL` (0x45) | Pre-send refusal downstream   | Read the row in the Delivery Log for the real gate — a compliance, balance, or routing refusal the wire cannot name.                    |

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 accepted `submit_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:

1. **`dlrMode` points the receipt elsewhere.** On the credential,
   `dlrMode = "bind"` (the default) streams the receipt back as a
   `deliver_sm` on 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 to `dlrWebhookUrl`. Point the mode where
   the listener is.
2. **The row was absorbed onto another channel, and the window closed.**
   When a termination rule absorbs your `submit_sm` onto another channel
   (WhatsApp OTP is the common case), no carrier emits a receipt — Orbit
   synthesizes one and the deadline is per-rule. If you see `stat:UNDELIV`
   with a reason like `no 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](/guides/smpp-receipt-timeout-window).
3. **The receipt arrived but your handler dropped it.** A client that nacks
   or ignores `deliver_sm` PDUs 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.

For the platform-side receipt model — `ACCEPTD` → `DELIVRD` → `UNDELIV` mapped
to canonical `sent`/`delivered`/`failed` states, err tokens `81`/`88`/`89`/`00` —
see [SMPP edge model](/concepts/smpp-edge-model).

## 4. Receiver-side (MO) path: inbox bind and opt-in

Traffic received on your SMPP bind — pass-through `submit_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_conversation` notification.
  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.

True carrier-side MO — a recipient's reply to a message you sent — threads
on the normal inbound path either way; the toggle gates only the
conversation creation tied to traffic that arrived on your bind. If
"replies on my bind do not appear in the Inbox" is the symptom, check the
toggle first — with it on the default `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](/guides/smpp-inbox-binding-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`+`webhook`
  receipt 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 `active` but binds still reject with
  `ESME_RINVSYSID` or `ESME_RINVPASWD` after one reconciliation pass (about a
  minute) — that is reconciler-side, not credential-side.
* `submit_sm` nacks with a `command_status` not in the decoder table.
* A `submit_sm` accepted on the bind never produces either a receipt or a
  `stat:UNDELIV` past the armed no-delivery window for that rule, and
  `dlrMode` already points at the live leg.

Include all three of these so support can trace the row without a
back-and-forth:

* Your **tenant ID** (Settings → Organization, or `organizationId` from
  `GET /api/v1/me`).
* The **credential id** (`smpp_…`) and the `system_id` (`smpp_sys_…`) returned
  at creation.
* A failing PDU trace: the `command_status` value and the PDU type (`bind_*_resp`
  or `submit_sm_resp`), plus the `message_id` from the `submit_sm_resp` if the
  submit was accepted.

## See also

* [Connect via SMPP](/guides/smpp) — ports, TTL/keepalive, and the diagnostic ladder
* [SMPP edge model](/concepts/smpp-edge-model) — the reconciliation model underneath
* [SMPP: the no-delivery receipt window](/guides/smpp-receipt-timeout-window) — tune the absorbed-receipt window
* [SMPP Inbox opt-in](/guides/smpp-inbox-binding-opt-in) — decide whether bind submits open Inbox conversations
* [SMPP recipes](/guides/smpp-recipes) — the rule-level how-tos for absorbed traffic
* [SMPP API reference](/api-reference/endpoints/smpp) — the full `/messaging/smpp` endpoint surface
