---
title: "SMPP v3.4 connectivity"
description: "When to use SMPP instead of REST, how binds are provisioned over TLS, submit_sm essentials, encoding, concatenation, delivery receipts and error codes."
canonical: "https://omnichannels.ai/developers/smpp/"
language: "en"
updated: "2026-10-10"
publisher: "BroadNet Technologies"
brand: "OmniChannels.ai"
section: "Connectivity"
---

# SMPP v3.4 connectivity

Canonical: https://omnichannels.ai/developers/smpp/
Language: en
Publisher: OmniChannels.ai by BroadNet Technologies

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](/demo/), 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

1. Open a TLS connection to the host and port you were given.
2. Send `bind_transceiver` with your `system_id` and password, and wait for `bind_transceiver_resp` with `command_status` 0.
3. Submit messages with `submit_sm`. Each `submit_sm_resp` returns the `message_id`.
4. Receive delivery receipts as `deliver_sm` and answer every one with `deliver_sm_resp`.
5. Send `enquire_link` about every 30 seconds while idle, and reconnect if responses stop.
6. Close with `unbind` and wait for `unbind_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_sm` whose `short_message` starts with a 6-octet User Data Header (reference number, total parts, part number), and set `esm_class` to 0x40. The header is why parts hold 153 or 67 characters.
- **Send it whole** in the `message_payload` TLV 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:

```text title="deliver_sm short_message"
id:2603121407537573731 sub:001 dlvrd:001 submit date:2610100914 done date:2610100915 stat:DELIVRD err:000 text:Your OmniDemo code
```

`id` 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.

```js title="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](/developers/errors-and-limits/): default limits for REST and SMPP.
- [SMS API](/developers/sms/): the REST alternative, with bulk jobs and lookup.
- [Webhooks](/developers/webhooks/): delivery statuses for REST traffic.
