BroadNet
SMPPIntegration

SMPP integration best practices

Updated 3 min readStandards-based
Direct answer

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.

DiagramAn SMPP session between your platform and BroadNet
Your platform (ESME)
BroadNet SMSC
One persistent TCP/TLS connection
  1. bind_transceiverOpen and authenticate the session
  2. bind_transceiver_respSession accepted
  3. submit_smSend a message
  4. submit_sm_respAccepted, message ID returned
  5. deliver_smDelivery receipt or inbound message
  6. deliver_sm_respAcknowledged
  7. enquire_linkKeepalive while idle
§01

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.

§02

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.

Correlation and flow-control state to maintain
StateWhy
Outstanding-request map (by sequence_number)Correlate responses, detect timeouts
Window occupancy counterBackpressure before ESME_RTHROTTLED
Idempotency key per logical messagePrevent duplicate sends on retry
Bind state + last enquire_link_resp timeDetect dead sessions
§03

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.

§04

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.

§05

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.

FAQ

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.

References

Source specifications

The published standards this page is grounded in. Opens in a new tab.

BNMaintained by the BroadNet engineering team·Carrier-grade messaging since 2003
Put it on the wire

Build with a real SMPP bind and REST API.

Request test access with real test keys: send to your own verified number first, then move to production across 200+ countries once your sender identity and routes are approved.