SMPP: Bind Types and Message Flow
Introduction¶
SMPP (Short Message Peer-to-Peer) is the TCP protocol an application (an ESME — External Short Messaging Entity) uses to send and receive SMS through an SMSC. Version 3.4 is near-universal; 5.0 exists but adoption is thin.
Session lifecycle¶
connect TCP (default port 2775)
-> bind_transmitter / bind_receiver / bind_transceiver
<- bind_*_resp (command_status 0 = OK)
... enquire_link / enquire_link_resp every ~30s (keepalive) ...
... submit_sm / deliver_sm traffic ...
-> unbind
<- unbind_resp
close TCP
The three bind types¶
| Bind | Can send (submit_sm) |
Can receive (deliver_sm: MO + DLR) |
|---|---|---|
| transmitter | yes | no |
| receiver | no | yes |
| transceiver | yes | yes |
- transceiver (
bind_transceiver) is the modern default — one connection, both directions. Use it unless the SMSC requires separate binds. - Older setups use a transmitter + receiver pair (two TCP connections, same
system_id), often because the SMSC routes MO/DLR by the receiver bind.
Bind parameters: system_id (username), password, system_type (often blank
or a category string), addr_ton/addr_npi, and address_range.
Sending: submit_sm¶
submit_sm:
service_type = ""
source_addr_ton=5 (alphanumeric) source_addr = "MyBrand"
dest_addr_ton=1 (international) dest_addr = "94771234567"
esm_class = 0x00
data_coding = 0 (GSM7) | 8 (UCS2 for non-Latin)
registered_delivery= 1 (I want a delivery receipt)
short_message = "Your code is 123456"
submit_sm_resp returns a message_id — keep it; the DLR references it.
Delivery receipts (DLR)¶
If registered_delivery=1, the SMSC later sends a deliver_sm with
esm_class bit 0x04 set, and short_message containing a receipt string:
id:1234567890 sub:001 dlvrd:001 submit date:2608271432 done date:2608271433
stat:DELIVRD err:000 text:Your code is...
stat values: DELIVRD, EXPIRED, DELETED, UNDELIV, ACCEPTD,
UNKNOWN, REJECTD. Match id against the message_id from submit_sm_resp
(watch for decimal vs hex representation differences between vendors).
MO (mobile-originated, a real inbound SMS from a handset) also arrives as
deliver_sm, without the receipt esm_class bit.
Sequence numbers and windowing¶
- Every PDU has a
sequence_number; the response echoes it. That's how you correlatesubmit_sm↔submit_sm_respon a pipelined connection. - Window = how many
submit_smyou send before waiting for responses. Window 1 = strictly synchronous (slow). A window of 10–100 pipelines for throughput. The SMSC advertises or enforces a max. - Throughput is also capped by a TPS limit (
submit_smper second) in your SMSC contract. Exceed it →ESME_RTHROTTLED(0x00000058); back off.
Concatenation (long SMS)¶
160 GSM-7 chars (or 70 UCS-2) splits into segments of 153 / 67. Either:
- UDH in
short_messagewithesm_class=0x40— a 6-byte header05 00 03 <ref> <total> <seq>, or - the
sar_msg_ref_num/sar_total_segments/sar_segment_seqnumTLVs.
Pick one per SMSC; sending both, or mixing, causes garbled multi-part messages.
Verification and troubleshooting¶
# quick interactive test
smppcli # from smpplib / or use "smpp" tools, jsmpp test client, Kannel's tools
tcpdump -i any -s0 -A 'port 2775' # PDUs are binary but headers/strings show
bind_*_respstatus 0x0000000D (ESME_RBINDFAIL) / 0x0E (ESME_RINVPASWD) / 0x0F (ESME_RINVSYSID) — credentials, or the source IP isn't allow-listed on the SMSC, or you tried a second bind with the samesystem_idwhile one is still open.- Bound but
submit_smreturnsESME_RSUBMITFAIL/ESME_RINVDSTADR— destination number format (TON/NPI), orESME_RINVSRCADRfor a sender ID that's not registered/permitted (alphanumeric sender rules vary by country). - No DLRs arriving —
registered_deliverywas 0, you're on a transmitter-only bind (DLRs come on the receiver/transceiver), or the SMSC routes DLRs to a different bind/connection than you're listening on. - DLR
iddoesn't match your storedmessage_id— hex vs decimal, or the SMSC truncates to a different length. Store both forms. - Connection drops every ~60s —
enquire_linknot being sent/answered; a firewall idle timeout. Sendenquire_linkevery 30s and respond promptly. ESME_RTHROTTLED— over your TPS. Implement a token-bucket rate limiter at your contracted rate; queue and retry with backoff.- Garbled non-Latin text —
data_codingshould be 8 (UCS-2/UTF-16BE) and the payload encoded accordingly; segment at 67 chars, not 153. - Duplicate messages — you resubmitted on a timeout but the original
succeeded. Use the window/sequence tracking; don't blind-retry
submit_sm.
Related tools and reading¶
- On-site: Port Checker (2775), Text diff.
- Related posts: SMSC basics for sysadmins, USSD session flow.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Telecom Systems
SMSC Basics for Sysadmins
The SMSC is the store-and-forward hub for text messages. Here's the message paths (MO, MT, AO, AT), how retries work, and a Kannel config to get started.
October 24, 2026eSIM and Remote SIM Provisioning
An eSIM is a reprogrammable chip (eUICC) that holds downloadable operator profiles. Here's the GSMA RSP architecture — SM-DP+, SM-DS, LPA — and the profile lifecycle.
October 24, 2026SIP Trunk Basics for Sysadmins
A sysadmin's view of SIP trunking — signalling vs media, the ports involved, and why one-way audio is almost always NAT.
October 24, 2026