SMPP (Short Message Peer-to-Peer) is the telecom protocol for exchanging SMS over a persistent TCP connection. We accept SMPP v3.4 binds over TLS for high-volume SMS, with the same routes as the REST API.
SMPP or REST?#
| REST API | SMPP v3.4 | |
|---|---|---|
| Channels | SMS plus WhatsApp, RCS, Messenger, Instagram, Telegram, Email and Push | SMS only |
| Connection | One HTTPS request per call | One long-lived TLS session (a bind) |
| Throughput | Suits most application traffic | Built for sustained high volume, with many messages in flight |
| Delivery receipts | Webhook to your HTTPS endpoint | deliver_sm on the same session |
| Effort | Any HTTP library | An SMPP client library and connection management |
| Best for | Applications, OTP, multichannel use cases | Aggregators, resellers and enterprises that already run SMPP clients |
You can use both on one account: REST for multichannel and application traffic, SMPP for high-volume SMS.
Provisioning a bind#
SMPP access is set up by our team rather than self-served. When you request access, tell us your destination countries, sender IDs, expected peak throughput and the public IP addresses you will connect from. You receive:
| Item | Detail |
|---|---|
| Host and TLS port | Provided with your credentials. Use the TLS port for production traffic. |
system_id and password |
Your bind credentials. Store them like any other secret. |
system_type |
Leave empty unless we give you a value. |
| Throughput | Messages per second per bind, agreed for your account. |
| Number of binds | How many concurrent sessions you may open. |
| IP allowlist | Only the source IPs you register can connect. |
Resellers and wholesale partners can have dedicated binds.
Bind types#
| Bind | PDU | Direction |
|---|---|---|
| Transceiver (TRX) | bind_transceiver |
Submit messages and receive receipts on one session. Recommended. |
| Transmitter (TX) | bind_transmitter |
Submit only. Receipts need a separate receiver bind. |
| Receiver (RX) | bind_receiver |
Receive receipts and inbound messages only. |
Use a single transceiver bind per connection unless your SMPP client requires separate transmitter and receiver sessions.
Session lifecycle#
- Open a TLS connection to the host and port you were given.
- Send
bind_transceiverwith yoursystem_idand password, and wait forbind_transceiver_respwithcommand_status0. - Submit messages with
submit_sm. Eachsubmit_sm_respreturns themessage_id. - Receive delivery receipts as
deliver_smand answer every one withdeliver_sm_resp. - Send
enquire_linkabout every 30 seconds while idle, and reconnect if responses stop. - Close with
unbindand wait forunbind_resp.
submit_sm_resp only means the message was accepted for delivery. The final outcome arrives later in the delivery receipt.
submit_sm essentials#
| Field | Value | Notes |
|---|---|---|
source_addr_ton / source_addr_npi |
5 / 0 for an alphanumeric sender ID; 1 / 1 for an international number | Must match the sender type. |
source_addr |
Your approved sender ID, up to 11 characters if alphanumeric | Unregistered sender IDs may be rejected or replaced in some countries. |
dest_addr_ton / dest_addr_npi |
1 / 1 | International format. |
destination_addr |
Country code plus number, no +, for example 447700900123 |
Same format as the REST API. |
registered_delivery |
1 | Request a delivery receipt. |
data_coding |
0 (GSM-7) or 8 (UCS-2) | See encoding below. |
short_message |
The text, encoded per data_coding |
Up to 254 octets. |
message_payload (TLV 0x0424) |
Alternative to short_message for long content |
Use one or the other, never both; set sm_length to 0 when using it. |
esm_class |
0x40 when short_message starts with a UDH |
Needed when you segment messages yourself. |
validity_period |
Optional | How long delivery attempts continue. |
Encoding#
data_coding |
Encoding | Single message | Per part when concatenated |
|---|---|---|---|
| 0 | GSM-7 default alphabet | 160 characters | 153 characters |
| 8 | UCS-2 (Unicode: Arabic, emoji, other scripts) | 70 characters | 67 characters |
One character outside GSM-7 means the whole message must be sent as UCS-2.
Concatenation (long messages)#
Messages longer than one segment are split into parts that the handset reassembles. You can do this in one of two ways:
- Segment it yourself. Send each part as its own
submit_smwhoseshort_messagestarts with a 6-octet User Data Header (reference number, total parts, part number), and setesm_classto 0x40. The header is why parts hold 153 or 67 characters. - Send it whole in the
message_payloadTLV and let the SMSC segment it. Confirm with our team that this is enabled on your bind before relying on it.
Use the same reference number for all parts of a message and a new one for each new message.
Delivery receipts#
When you set registered_delivery to 1, the receipt arrives as a deliver_sm with the receipt bit (0x04) set in esm_class. The text follows the common SMPP receipt format:
deliver_sm short_message
id:2603121407537573731 sub:001 dlvrd:001 submit date:2610100914 done date:2610100915 stat:DELIVRD err:000 text:Your OmniDemo codeid matches the message_id from submit_sm_resp. Where present, the receipted_message_id TLV (0x001E) carries the same ID and is easier to parse than the text.
stat |
Meaning |
|---|---|
DELIVRD |
Delivered to the handset. |
UNDELIV |
Undeliverable. |
EXPIRED |
The validity period ended before delivery. |
REJECTD |
Rejected by the network, for example a blocked sender. |
DELETED |
Deleted before delivery. |
ACCEPTD |
Accepted; an intermediate state. |
UNKNOWN |
No state information available. |
Always answer deliver_sm with deliver_sm_resp. Unacknowledged receipts are re-sent and can lead to throttling of the bind.
Throughput and windowing#
SMPP is asynchronous: you can send several submit_sm PDUs before the responses arrive. The number of unacknowledged PDUs in flight is your window.
- A window of 1 (waiting for each response) is the most common cause of low throughput.
- The documented default is 1,000 messages per second per bind with a window of 100 outstanding
submit_sm. Your account's limits are confirmed with your credentials. - If you receive
ESME_RTHROTTLED, slow down and retry with exponential backoff. - On disconnect, reconnect with backoff and jitter, not in a tight loop, and track outstanding sequence numbers so you can reconcile receipts.
- Higher aggregate throughput comes from more binds, up to the number agreed for your account.
Common command_status codes#
| Code | Name | Meaning | What to do |
|---|---|---|---|
| 0x00000000 | ESME_ROK |
Success. | Nothing. |
| 0x00000001 | ESME_RINVMSGLEN |
Invalid message length. | Check sm_length, or that you did not send both short_message and message_payload. |
| 0x00000004 | ESME_RINVBNDSTS |
Command not allowed in the current bind state. | For example, submitting on a receiver bind. |
| 0x00000005 | ESME_RALYBND |
Already bound. | Do not bind twice on one session. |
| 0x00000008 | ESME_RSYSERR |
System error. | Retry with backoff. |
| 0x0000000A | ESME_RINVSRCADR |
Invalid source address. | Check the sender ID and its TON/NPI. |
| 0x0000000B | ESME_RINVDSTADR |
Invalid destination address. | Check the number format. |
| 0x0000000D | ESME_RBINDFAIL |
Bind failed. | Check system_id, password and your source IP. |
| 0x0000000E | ESME_RINVPASWD |
Invalid password. | Check credentials. |
| 0x00000014 | ESME_RMSGQFUL |
Message queue full. | Back off and retry. |
| 0x00000045 | ESME_RSUBMITFAIL |
Submit failed. | Check the message, then retry later. |
| 0x00000058 | ESME_RTHROTTLED |
Throughput limit exceeded. | Slow down; retry with backoff. |
Example session#
This sketch uses the open-source smpp package for Node.js. Any SMPP v3.4 client library (for example jSMPP for Java or python-smpplib) follows the same steps.
Node.js
import smpp from "smpp";
// Host, TLS port and credentials come with your bind.
const session = smpp.connect({
url: `ssmpp://${process.env.SMPP_HOST}:${process.env.SMPP_TLS_PORT}`,
auto_enquire_link_period: 30000,
});
session.bind_transceiver(
{ system_id: process.env.SMPP_SYSTEM_ID, password: process.env.SMPP_PASSWORD },
(bindResp) => {
if (bindResp.command_status !== 0) throw new Error(`bind failed: ${bindResp.command_status}`);
session.submit_sm(
{
source_addr_ton: 5,
source_addr_npi: 0,
source_addr: "OmniDemo",
dest_addr_ton: 1,
dest_addr_npi: 1,
destination_addr: "447700900123",
registered_delivery: 1,
short_message: "Your OmniDemo code is 482913",
},
(resp) => console.log("accepted", resp.message_id),
);
},
);
session.on("deliver_sm", (pdu) => {
if (pdu.esm_class & 0x04) console.log("receipt", pdu.short_message.message);
session.send(pdu.response()); // always acknowledge
});Check your library's documentation for its TLS URL scheme and encoding options; option names differ between libraries.
Next steps#
- Errors and limits: default limits for REST and SMPP.
- SMS API: the REST alternative, with bulk jobs and lookup.
- Webhooks: delivery statuses for REST traffic.
Ready to send your first message?
Request test credentials. An engineer sets up your account, and test sends go to your own verified numbers.
Request test access