A robust SMPP integration is about operational discipline, not just correct PDUs: pick the right bind mode, manage sequence_number and the window carefully, retry with jittered backoff, make submission idempotent to avoid double-billing, acknowledge every deliver_sm, and reconnect cleanly on failure. Get these right and the protocol details take care of themselves.
bind_transceiverOpen and authenticate the sessionbind_transceiver_respSession acceptedsubmit_smSend a messagesubmit_sm_respAccepted, message ID returneddeliver_smDelivery receipt or inbound messagedeliver_sm_respAcknowledgedenquire_linkKeepalive while idle
Bind strategy
Prefer a transceiver bind where the provider allows it, so one connection carries both submit and receive and simplifies DLR correlation. Agree the number of concurrent binds and per-bind TPS with the provider in writing before scaling out. Confirm the negotiated SMPP version from bind_resp, and handle bind failures explicitly — retry ESME_RALYBND-style transient states, but treat ESME_RINVPASWD/ESME_RINVSYSID as fatal configuration errors rather than looping.
sequence_number and the window
Assign a monotonically increasing sequence_number (1 to 0x7FFFFFFF, then wrap) and use it to correlate every response to its request. Track outstanding requests in a map keyed by sequence_number with a timeout, so a response that never arrives is detected. Respect the window: cap in-flight un-acked PDUs and pause submitting when it is full rather than firing blindly, which triggers throttling.
| State | Why |
|---|---|
| Outstanding-request map (by sequence_number) | Correlate responses, detect timeouts |
| Window occupancy counter | Backpressure before ESME_RTHROTTLED |
| Idempotency key per logical message | Prevent duplicate sends on retry |
| Bind state + last enquire_link_resp time | Detect dead sessions |
Retries, backoff and idempotency
Retry transient failures (ESME_RMSGQFUL, ESME_RTHROTTLED, system errors, timeouts) with exponential backoff and jitter so reconnect storms do not synchronise across workers. Do not retry permanent errors (invalid address, invalid password). Crucially, make submission idempotent: attach your own reference (for example via user_message_reference) and de-duplicate, so a retry after an ambiguous timeout does not double-bill or double-deliver.
Delivery-receipt handling
Request registered_delivery only where you consume the receipts. Always answer deliver_sm_resp promptly and process asynchronously. Correlate receipts on the receipted_message_id TLV, normalising hex/decimal id formats, and only close a message on a final stat. Map network err codes to a normalised taxonomy so quality is comparable across routes. Treat intermediate notifications separately from terminal states.
Keepalive and reconnection
Send enquire_link on an idle timer and treat a missed response as a dead session. On any disconnect, reconnect with backoff and jitter rather than hot-looping. On reconnect, resume from a clean state: re-bind, reset the outstanding-request map, and rely on idempotency to safely re-submit anything that was in flight. Instrument bind state, window occupancy, submit/DLR latency and error-code rates, and alert on all of them.
Frequently asked
How should I assign sequence_number?
Use a monotonically increasing counter from 1 up to 0x7FFFFFFF, then wrap. Keep a map of outstanding sequence_numbers with timeouts so you can correlate every response and detect requests that never got one.
How do I avoid double-billing on retries?
Make submission idempotent. Attach your own reference (e.g. user_message_reference), de-duplicate on it, and design retries so that a resend after an ambiguous timeout is recognised and suppressed rather than delivered and billed twice.
Which SMPP errors should I retry?
Retry transient ones — ESME_RMSGQFUL, ESME_RTHROTTLED, system errors and timeouts — with exponential backoff and jitter. Do not retry permanent errors like ESME_RINVDSTADR or ESME_RINVPASWD; fix the input or configuration instead.
What should I monitor on an SMPP bind?
Bind state, time since last enquire_link_resp, window occupancy, submit and DLR latency, and per-code error rates (especially ESME_RTHROTTLED). Alert on rising throttling or a stalled window as early signals of trouble.
How should I handle reconnection after a dropped session?
Reconnect with jittered exponential backoff, re-bind, reset your outstanding-request map, and re-submit in-flight messages relying on idempotency to prevent duplicates. Never hot-loop reconnects, which can get your IP blocked.
Source specifications
The published standards this page is grounded in. Opens in a new tab.