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
What SMPP is
SMPP — Short Message Peer-to-Peer — is the binary, session-based protocol that aggregators and operators expose for high-throughput A2P messaging. Where a REST API is convenient and stateless, SMPP is a persistent TCP connection built for volume and low latency.
An ESME (External Short Messaging Entity — your side) opens a session to the SMSC and exchanges PDUs (Protocol Data Units). The protocol is compact and efficient, which is why it remains the backbone for large senders even as REST covers everyday use.
The most widely deployed version is SMPP v3.4; v5.0 adds congestion-state signalling and enhanced parameters but is backward compatible. You should never assume a v5.0 feature is available until the bind response confirms the negotiated version.
Bind types and sessions
Before sending anything, an ESME must bind — authenticate and declare a role. A transmitter bind sends messages only; a receiver bind receives mobile-originated messages and delivery receipts only; a transceiver bind does both over one connection.
Transceiver is usually preferred because it halves the number of connections and simplifies correlation of DLRs to submissions. The number of concurrent binds and the per-bind messages-per-second (TPS) are provisioned by the provider and must be honoured.
The bind carries a system_id, password, system_type, and address_range. A mismatched address_range silently breaks MO and DLR routing on receiver binds — a classic integration bug that produces no errors, only missing traffic.
PDUs and the submit_sm
Every SMPP interaction is a PDU with a command header (length, command id, status, sequence number) and a body. The workhorse is submit_sm, which carries a single outbound message.
Two field pairs decide addressing: source_addr with its TON/NPI (type-of-number / numbering-plan-indicator) and dest_addr with its own. An alphanumeric sender ID uses TON 5; an international MSISDN uses TON 1, NPI 1. Get these wrong and the sender ID renders incorrectly or the message is rejected.
The data_coding field sets the encoding — GSM 7-bit (0), 8-bit (4), or UCS-2 (8) for Unicode. Long messages are concatenated either with UDH bytes in the body or with the SAR optional parameters (sar_msg_ref_num, sar_total_segments, sar_segment_seqnum); pick one method and stay consistent, because routes differ on which they accept.
The window, throttling, and keepalives
SMPP uses a transmit window: a cap on the number of submit_sm PDUs sent before their responses come back. Exceeding the window, or the provisioned TPS, triggers ESME_RTHROTTLED (0x58). The correct response is to back off with jitter, not to hot-loop retries.
To detect a half-open TCP connection, each side sends enquire_link on an idle timer (commonly every 30–60 seconds) and expects an enquire_link_resp. A missed response means the session is dead and should be re-bound with exponential backoff.
Graceful shutdown uses unbind / unbind_resp. Unexpected disconnects should trigger a reconnect with backoff and idempotent retries, so a dropped window doesn't cause double submissions and double billing.
Delivery receipts over SMPP
Delivery receipts arrive as deliver_sm PDUs. The receipt text in the short_message field carries id, sub, dlvrd, submit date, done date, the terminal stat (DELIVRD, UNDELIV, EXPIRED, REJECTD), and an err code.
Correlate the DLR to your original submission using the provider's message_id, not the receipt text alone — message_id may be returned in hex on submit_resp but decimal in the DLR, so normalise both formats.
Always answer with a deliver_sm_resp. An unacknowledged DLR is re-sent by the SMSC and can stall the receive window, so acknowledge promptly even if your downstream processing is asynchronous.
- SMPP is a persistent binary session for high-volume A2P; v3.4 is the common baseline, v5.0 is backward compatible.
- Bind as transmitter, receiver, or transceiver; transceiver halves connections and eases DLR correlation.
- TON/NPI decide sender-ID rendering and data_coding decides encoding — both are easy to get wrong.
- Respect the window and TPS, back off on ESME_RTHROTTLED, and keep the session alive with enquire_link.