What is SMPP?
The Short Message Peer-to-Peer protocol (SMPP) is the open, binary protocol that SMS applications and gateways — called ESMEs, External Short Messaging Entities — use to exchange messages with an operator's or aggregator's Short Message Service Centre (SMSC). Version 3.4, published in 1999, is still the version almost everyone runs; v5.0 exists but is rarely required. Unlike an HTTP API, SMPP keeps one long-lived TCP session open — on port 2775 by convention, often inside TLS or a VPN — and streams PDUs over it in both directions.
Every PDU starts with a 16-byte header — command_length, command_id, command_status and sequence_number — followed by the body. An ESME first binds as transmitter, receiver or transceiver (bind_transceiver) with its system_id and password. It sends messages with submit_sm and gets a submit_sm_resp carrying the SMSC's message_id; inbound messages and delivery reports arrive as deliver_sm, which it must acknowledge with deliver_sm_resp. enquire_link keeps the session alive, and unbind closes it cleanly.
SMPP's speed comes from windowing: an ESME may have several requests in flight — commonly ten or more — before waiting for responses, matching each response to its request by sequence number. Operators cap throughput per bind and answer ESME_RTHROTTLED (0x00000058) when you exceed it. Most businesses never touch SMPP directly: they call an SMS gateway's HTTP API and the gateway speaks SMPP to operators. In India, binds also carry DLT details — the principal entity ID and the DLT content template ID — in operator-defined optional TLV parameters, so the operator can scrub each message before delivery.
Key takeaways
- SMPP is the binary protocol between an SMS application (an ESME) and an operator's SMSC, running over one long-lived TCP session.
- Version 3.4 from 1999 is still the version almost everyone uses; port 2775 is the convention.
- Messages go out as submit_sm; the SMSC's submit_sm_resp means accepted, not delivered.
- Inbound messages and delivery reports arrive as deliver_sm and must be acknowledged with deliver_sm_resp straight away.
- Throughput comes from windowing several requests in flight; exceeding the operator's rate returns ESME_RTHROTTLED (0x58).
Follow one OTP through an SMPP session
- Your app (ESME)SMPP client
- Operator SMSCstore and forward
- Handsetcustomer's phone
Step 9 of 9Response
Operator SMSCYour app (ESME)
enquire_link_resp
A slow answer here is the first sign of trouble.
- Request — solid arrow — a call that expects an answer
- Response — dashed arrow — the answer coming back
- Event — dotted arrow — something happened and is pushed out
- Async — double head — fire-and-forget, nobody waits
Step 9 of 9: Operator SMSC to Your app (ESME) — enquire_link_resp. A slow answer here is the first sign of trouble.
Why does SMPP matter?
SMPP is the wire underneath most A2P SMS. If you run an SMS gateway, connect directly to an operator for OTP volume, or chase delivery reports that never reach your database, you are dealing with binds, windows, throttling and deliver_sm handling. Those details — sequence numbers, response timeouts, registered_delivery, data_coding — decide your real throughput, whether Hindi text arrives readable, whether long messages reassemble and whether every SMS delivery report gets matched to the message it describes.
SMPP in code
import smpp from "smpp";const session = smpp.connect({ url: "smpp://smsc.example.net:2775", auto_enquire_link_period: 30000 });session.bind_transceiver({ system_id: process.env.SMPP_SYSTEM_ID, password: process.env.SMPP_PASSWORD }, (bind) => {if (bind.command_status !== 0) return console.error("bind failed", bind.command_status);session.submit_sm({source_addr: "ACMEBK", // DLT-registered header in Indiasource_addr_ton: 5,source_addr_npi: 0,destination_addr: "919812345678",dest_addr_ton: 1,dest_addr_npi: 1,data_coding: 0, // 0 = SMSC default (GSM-7), 8 = UCS-2registered_delivery: 1, // request a final delivery reportshort_message: "482913 is your OTP for ACME Bank. Valid for 5 minutes. Do not share it. -ACME",// India: add the DLT entity ID and content template ID as the// optional TLVs your operator specifies; tag numbers differ by operator.}, (resp) => {if (resp.command_status === 0) saveMessageId(resp.message_id); // accepted, not deliveredelse if (resp.command_status === 0x58) retryLater(); // ESME_RTHROTTLED});});session.on("deliver_sm", (pdu) => {session.send(pdu.response()); // acknowledge first, process laterconst text = pdu.short_message.message;if (pdu.esm_class & 0x04) queueReceipt(text); // "id:... stat:DELIVRD err:000 ..."else queueInbound(pdu.source_addr, text); // a mobile-originated message});
A transceiver bind that submits an OTP and handles both inbound messages and delivery reports on the same session.
How does SMPP work?
Connect and bind
Open TCP to the SMSC's host and port and send
bind_transceiver(or separate transmitter and receiver binds) with thesystem_idand password the operator issued. Abind_transceiver_respwithcommand_status0 (ESME_ROK) means you are bound.Submit with submit_sm
Set
source_addr(your sender),destination_addr,data_coding(0 for the SMSC default alphabet, usually GSM-7; 8 for UCS-2) andregistered_delivery1 to request a final receipt.short_messageholds up to 254 octets; longer texts go as concatenated segments with a UDH (esm_class0x40) or in themessage_payloadTLV.Window and match responses
Keep a fixed number of requests in flight, match each
submit_sm_respbysequence_number, and store the returnedmessage_idnext to your own reference so the receipt can be matched later.Receive deliver_sm
Mobile-originated messages and delivery reports both arrive as
deliver_sm; receipts haveesm_class0x04 and a text body withid:,stat:anderr:fields. Acknowledge each withdeliver_sm_respbefore doing anything slow.Keep alive and recover
Send
enquire_linkevery 30–60 seconds. On a timeout or dropped socket, rebind with backoff and resubmit only the messages that never got asubmit_sm_resp, keyed by your own ID so customers do not get duplicates.
Key facts at a glance
SMPP: key facts and limits
- Version in use
- SMPP v3.4 (1999); v5.0 rarely required
- Transport
- Persistent TCP, port 2775 by convention
- PDU header
- 16 bytes: length, command_id, status, sequence
- Bind modes
- Transmitter, receiver, transceiver
- Message field
- short_message up to 254 octets
- Delivery reports
- deliver_sm with esm_class 0x04
- Throttling error
- ESME_RTHROTTLED (0x00000058)
- Keep-alive
- enquire_link, typically every 30–60 s
Example: A direct bind for an OTP platform in Noida
An OTP platform in Noida runs two transceiver binds to each operator, one reserved for OTPs and one for bulk alerts, so a campaign never queues in front of a login code. Each bind keeps a window of ten requests and paces itself under the operator's per-bind cap; the rare ESME_RTHROTTLED goes back into a delay queue instead of failing the message. Every submit_sm carries the client's DLT entity and template IDs in the operator's TLVs. During launch the team found that 3% of receipts would not match: one operator returned message_id in hex in submit_sm_resp but in decimal inside the receipt text. Normalising both to one form fixed the gap, and the OTP-to-receipt match rate went to 100%.
Common wrong turns
Common mistakes with SMPP
Slow deliver_sm acknowledgements
If you process a receipt before acknowledging it, the SMSC waits, retries or throttles the whole session. Send
deliver_sm_respimmediately and do the database work from a queue.Reading submit_sm_resp as delivered
A successful
submit_sm_responly means the SMSC accepted the message. Delivery is known only when the receipt arrives withstat:DELIVRD— or never, if the phone stays off.Ignoring the window
Sending one PDU and waiting for its response caps you at a few messages per second; firing hundreds at once gets you throttled. Tune the window and pace to the operator's agreed rate per bind.
Wrong data_coding for Indian scripts
Hindi or Tamil sent as data_coding 0 arrives as question marks. Use 8 (UCS-2) with UTF-16 big-endian text, and remember that drops one SMS to 70 characters.
No reconnect or duplicate protection
Sessions drop. Rebind automatically with backoff, and resubmit only messages without a response, keyed by your own ID, so a reconnect never sends a customer the same OTP twice.
Good practice
Best practices for SMPP
- Keep OTPs on their own bind so bulk traffic can never delay them.
- Store your own ID beside the SMSC's message_id, and normalise hex and decimal forms before matching receipts.
- Alert when enquire_link responses slow down — it is the earliest sign of a struggling SMSC.
- Log command_status values by name (ESME_RINVDSTADR, ESME_RTHROTTLED) rather than raw numbers.