HomeBlog › SMPP Protocol

SMPP Protocol Guide for Beginners: How SMS Messaging Works

SMSApril 5, 202615 min read
TL;DRSMPP (Short Message Peer-to-Peer) is the TCP-based protocol used by SMS Centers (SMSCs) and external applications to exchange SMS messages. Every SMS gateway, CPaaS, and aggregator on Earth speaks SMPP. This guide covers SMPP v3.4 (the dominant version), session types (transmitter/receiver/transceiver), the most important PDUs (submit_sm, deliver_sm, bind_transceiver), data coding, long SMS concatenation, delivery receipts, and the error codes you'll actually see.

What is SMPP?

SMPP was originally developed by Aldiscon (later acquired by Logica, now part of IMI Mobile) in the 1990s as an open protocol for exchanging SMS traffic between Short Message Service Centers (SMSCs) and external clients. The spec was handed over to the SMS Forum, which standardized SMPP v3.4 in 1999 — still the dominant version in 2026. A later v5.0 exists but has minimal adoption.

SMPP runs over TCP on port 2775 (or 3550 for TLS). It's a binary, session-oriented protocol: a client (ESME) opens a TCP connection to an SMSC, authenticates with a bind, then exchanges PDUs (Protocol Data Units) until one side unbinds or the connection drops.

Key Terminology

  • SMSC — Short Message Service Center. The operator's SMS server. Stores, routes, and delivers SMS.
  • ESME — External Short Message Entity. Any SMPP client connecting to an SMSC (your application, an aggregator, another operator).
  • MO — Mobile Originated. SMS sent from a mobile subscriber to your application.
  • MT — Mobile Terminated. SMS sent from your application to a mobile subscriber.
  • PDU — Protocol Data Unit. A single SMPP message (bind, submit, deliver, etc.).
  • TLV — Tag-Length-Value. Optional parameters added in v3.4+.
  • DLR — Delivery Receipt. A status report confirming whether an MT was delivered.

Session Types (Bind Modes)

An SMPP session has one of three "roles", determined at bind time:

Bind TypePDUPurpose
Transmitterbind_transmitterSend MT only (you submit, SMSC delivers to handsets). No inbound traffic.
Receiverbind_receiverReceive MO + DLRs only. No outbound traffic.
Transceiverbind_transceiverBidirectional — both send and receive on one session.

Transceiver is the most common in modern deployments because it's simpler and uses one TCP connection. Transmitter+Receiver pair is legacy and rarely used today.

PDU Structure

Every SMPP message has the same basic shape: a 16-byte header followed by a variable body.

┌─────────────────────────────────────────────┐
│  command_length  (4 bytes, big-endian)      │  Total PDU size
├─────────────────────────────────────────────┤
│  command_id      (4 bytes)                  │  What PDU this is
├─────────────────────────────────────────────┤
│  command_status  (4 bytes)                  │  0 = OK on responses
├─────────────────────────────────────────────┤
│  sequence_number (4 bytes)                  │  Matches req ↔ resp
├─────────────────────────────────────────────┤
│  body (variable)                            │  Depends on command_id
└─────────────────────────────────────────────┘

The sequence_number is used to match requests to their responses asynchronously — ESMEs can pipeline many submits before any response arrives.

The PDUs You'll Use 99% of the Time

CommandCommand IDPurpose
bind_transceiver0x00000009Open a session (auth with system_id/password)
bind_transceiver_resp0x80000009SMSC's response
submit_sm0x00000004Send an MT message
submit_sm_resp0x80000004SMSC's ack with message_id
deliver_sm0x00000005Inbound MO or DLR from SMSC
deliver_sm_resp0x80000005Your ack to the SMSC
enquire_link0x00000015Keepalive ping (send every 30–60s)
enquire_link_resp0x80000015Keepalive pong
unbind0x00000006Close session gracefully
unbind_resp0x80000006Ack

Less common but useful: query_sm, cancel_sm, replace_sm, data_sm, submit_multi.

Sending a Message: submit_sm

The main body of a submit_sm PDU has these key fields:

  • source_addr_ton / source_addr_npi — type of number / numbering plan for sender
  • source_addr — sender (up to 21 chars for alphanumeric senders)
  • dest_addr_ton / dest_addr_npi — type/NPI for recipient
  • destination_addr — recipient MSISDN (international format, + stripped)
  • esm_class — message type (normal, delivery ack, etc.)
  • protocol_id — 0 for normal SMS
  • priority_flag — 0 (normal) to 3 (highest)
  • schedule_delivery_time — "" for immediate
  • validity_period — how long to retry if handset unreachable
  • registered_delivery — 1 = request DLR
  • data_coding — charset (see below)
  • short_message — the actual message bytes, up to 254 octets

Data Coding: GSM-7 vs UCS-2

The data_coding field tells the SMSC how to interpret the message bytes. The three you care about:

data_codingCharsetMax chars (single SMS)Max chars (concatenated)
0GSM 7-bit default alphabet160153 per part
3ISO-8859-1 (Latin-1)140134 per part
8UCS-2 (UTF-16 BE, for Unicode)7067 per part

Rule of thumb: if your message is pure Latin with no emojis or accents, use GSM-7 (0). If it contains any non-GSM-7 character (Arabic, Chinese, emoji, em-dash, curly quotes), you must use UCS-2 (8) and you lose 90 characters per SMS. Users are often surprised their "170-character" message suddenly splits into 3 parts because they pasted a curly quote.

Long SMS: Concatenation via UDH

SMS was designed for 160 characters max. To send longer messages, you split into segments and use the User Data Header (UDH) to tell the handset they're parts of the same message so it can reassemble. The UDH occupies 6 bytes at the start of the payload, which is why you drop from 160 to 153 chars per segment.

The UDH for concatenation (IEI 0x00, 8-bit reference):

05 00 03 <ref> <total_parts> <part_number>
↑   ↑  ↑    ↑       ↑             ↑
│   │  │    │       │             └ 1..N (which part)
│   │  │    │       └ total parts
│   │  │    └ reference (same across all parts of this msg)
│   │  └ length of IE data
│   └ IE identifier (0 = 8-bit concat)
└ UDH length

Many SMPP libraries handle this automatically — you pass a long string and it generates and submits the segments. Otherwise you do it manually via the message_payload TLV or manual UDH in short_message with esm_class = 0x40.

TLVs (Optional Parameters)

SMPP v3.4 added TLVs for carrying optional parameters that didn't fit in the fixed body. Useful ones:

  • message_payload (0x0424) — alternative to short_message, supports up to 64 KB payload (useful for WAP push, concatenated messages)
  • receipted_message_id (0x001E) — in a deliver_sm DLR, identifies which submit_sm it refers to
  • message_state (0x0427) — state of a receipted message (ENROUTE, DELIVERED, EXPIRED, etc.)
  • user_message_reference (0x0204) — your own reference ID
  • callback_num (0x0381) — call-back number TLV

Delivery Receipts (DLRs)

If you set registered_delivery = 1 on a submit_sm, the SMSC will eventually send you a deliver_sm PDU containing a DLR. The DLR's short_message field contains a text string in a standard format:

id:2b3c4d5e sub:001 dlvrd:001 submit date:2604121234
done date:2604121234 stat:DELIVRD err:000 text:Hello

Key fields:

  • id — the message_id from submit_sm_resp (how you correlate)
  • stat — final state: DELIVRD, EXPIRED, DELETED, UNDELIV, ACCEPTD, UNKNOWN, REJECTD
  • err — GSM network error code if failed

Common Error Codes

CodeNameMeaning
0ESME_ROKSuccess
1ESME_RINVMSGLENMessage length invalid
8ESME_RSYSERRSystem error (generic)
10ESME_RINVSRCADRInvalid source address
11ESME_RINVDSTADRInvalid dest address
13ESME_RBINDFAILBind failed (auth)
14ESME_RINVPASWDInvalid password
15ESME_RINVSYSIDInvalid system_id
20ESME_RMSGQFULMessage queue full
58ESME_RX_T_APPNESME receiver temporary app error
69ESME_RSUBMITFAILSubmit failed (generic)
88ESME_RTHROTTLEDRate limit exceeded — back off
97ESME_RINVDLNAMEInvalid distribution list
192ESME_RNOTSUPPRSPNo response from operator

For the full list see our SMPP Error Codes reference.

Security and Rate Limiting

  • TLS — use port 3550 with TLS whenever the aggregator supports it. Plain TCP SMPP should be avoided over the public internet.
  • IP whitelisting — most aggregators require you to register source IPs before they'll accept connections.
  • Rate limits — SMSCs enforce per-second submit limits (typically 30–200 TPS per bind). Respect ESME_RTHROTTLED and back off.
  • Multiple binds — for higher throughput, open multiple transceiver sessions in parallel rather than trying to push one faster.
  • enquire_link — send a ping every 30–60 seconds. If you miss 3 in a row, assume the connection is dead and reconnect.

Best Practices

  • Always request DLRs on A2P traffic (registered_delivery=1) and actually process them — don't fire-and-forget.
  • Store the message_id from submit_sm_resp so you can correlate DLRs.
  • Implement retry with exponential backoff on ESME_RTHROTTLED.
  • Reconnect automatically on TCP drops; use sequence_number tracking to avoid duplicates.
  • Detect charset — if the message contains any non-GSM-7 char, automatically switch to UCS-2.
  • Use TLVs (message_payload) for long messages; don't try to hand-roll UDH unless you must.
  • Monitor DLR success rate by destination country and route — sudden drops usually mean a carrier issue.

Key Takeaways

  1. SMPP is the universal SMS exchange protocol — v3.4 dominates in 2026.
  2. Open a bind_transceiver session, submit via submit_sm, receive via deliver_sm, keep alive with enquire_link.
  3. Watch the data_coding field — any Unicode forces UCS-2 and cuts character limit from 160 → 70.
  4. Always request DLRs and correlate by message_id to measure real delivery rates.
  5. Respect rate limits (ESME_RTHROTTLED), open multiple binds for throughput, always use TLS over the public internet.
← Back to Blog