SMS over SMPP 3.4
Connect your platform to the Hablame SMPP 3.4 server to send SMS and receive, for each message, the final status we know for it.
12 min readReviewed on
Goal
Send SMS from your own platform with the standard SMPP 3.4 protocol, and know what happened to each one. It is the right path if you already have an SMPP client or need to send sustained volume.
Over SMPP you send and receive delivery receipts for what you sent. SMS that people send to your short code do not arrive here: they arrive by webhook.
Requirements
- An SMPP account created by the Hablame team. How to request it.
- Fixed public IP addresses: only the ones you authorize are enabled for your account.
- A cost center that usage is attributed to.
- An SMPP 3.4 client (
interface_version0x34). If you want receipts, areceiverortransceiverconnection.
Credentials
Support creates your account and gives you the server, the port, your system_id (up to 15 characters) and your password (up to 8 characters, the most SMPP 3.4 allows). They are not published on this portal.
The same port takes encrypted and unencrypted connections: if your client opens with TLS (version 1.2 or later), the server detects it on its own. We recommend TLS.
The bind’s system_type, addr_ton, addr_npi and address_range are accepted but not validated.
Steps
- 01Open the connection and bind
Send the first byte within 5 s; if you use TLS, finish the handshake within 10 s. The bind must arrive within 10 s and, overall, before 30 s have passed since you opened the connection.
- 02Send with submit_sm
Over a
transmitterortransceiverconnection. Setregistered_deliveryto 0x01 if you want a receipt for each message. - 03Store the message_id
It arrives in the
submit_sm_resp. It is the same one the receipt carries, character for character. - 04Receive and confirm receipts
They arrive as
deliver_smover yourreceiverortransceiverconnections of the same account. Answer each one withdeliver_sm_respand status 0 within 60 s.
| Command | Purpose |
|---|---|
bind_transmitter | Opens a connection to send. |
bind_receiver | Opens a connection to receive delivery receipts. |
bind_transceiver | Opens a connection for both. |
submit_sm | Sends a message (or one part of a long message). |
enquire_link | Checks that the connection is alive. |
deliver_sm_resp | Confirms a receipt we sent you. |
unbind | Closes the connection cleanly. |
unbind_resp | Answers an unbind we sent you. |
query_sm, submit_multi, data_sm, replace_sm, cancel_sm are not supported: they get generic_nack with 0x00000003 and the connection stays open.
Keeping the connection alive
- Hablame sends you an
enquire_linkevery 30 s, with or without traffic. Always answer it: after 2 in a row without an answer, the connection is closed. - If your client stops reading for 30 s, the connection is closed.
- You can send your own
enquire_link, but only after binding: before that they get 0x00000004. - To close, send
unbindand wait for its response.
Limits
| What | Limit | When exceeded |
|---|---|---|
| Simultaneous connections per IP and account | 20 | The oldest one is closed to open the new one. If the new one is a transmitter and the one to close is the last one receiving receipts, the new one is rejected with 0x00000005. |
| Simultaneous connections from one IP | 64 | The new connection is closed right away. |
submit_sm per second on each connection | 3,000 | Response 0x00000058. Each part of a long message counts as one submit_sm. |
| PDU size | 64 KiB | generic_nack with 0x00000002 and the connection is closed. |
Failed binds per minute, per IP and system_id | 5 | For the rest of that minute, every bind gets 0x0000000D. |
Examples
submit_sm fields
source_addr- The sender, up to 20 characters.
destination_addr- The destination number, up to 20 characters. Required: without it, 0x0000000B.
esm_class- 0x40 if the text starts with a UDH header.
registered_delivery- 0x01: a receipt with the final status, whatever it is. 0x02: a receipt only if the message fails. 0x00: no receipt. There are no intermediate notifications.
data_coding- The text encoding (table below).
short_message- The text, up to 254 octets. For longer text, use
message_payload; if both arrive,short_messageis used. schedule_delivery_time- Not applied today: the message goes out right away. Do not use it to schedule sends.
validity_period- Accepted and stored as it arrives.
Encoding
In SMPP, GSM-7 text goes unpacked: one character per octet. If you pack it, it arrives garbled.
data_coding | Encoding | Single-part message | Each part of a long message |
|---|---|---|---|
0x00 | GSM-7, unpacked (one character per octet) | 160 characters | 153 characters |
0x01 | ASCII | 160 characters | 153 characters |
0x03 | Latin-1 (ISO-8859-1) | 160 characters | 153 characters |
0x04 | Binary | 140 octets | 134 octets |
0x08 | UCS-2 (big endian) | 70 characters | 67 characters |
- GSM-7 includes
ñ,Ñ,é,É,ü,Ü,¿,¡. The accented vowelsá,í,ó,ú,Á,Í,Ó,Úare not included: text with them goes in UCS-2 (0x08). - In GSM-7,
€,[,],{,},~,\,^,|take two characters each. - In UCS-2, an emoji outside the basic plane takes two characters.
Long messages
There are two ways to send text that does not fit in one part:
- Split it yourself, with a concatenation UDH header (IEI 0x00 with an 8-bit reference or 0x08 with a 16-bit reference) and
esm_class0x40, or with thesar_msg_ref_num,sar_total_segmentsandsar_segment_seqnumTLVs if you do not use UDH. If both arrive, the UDH wins. - Send it whole in the
message_payloadTLV, without UDH, within the 64 KiB PDU limit.
If you split it, the server joins the parts before sending: it waits up to 60 minutes from the first one and accepts up to 255 parts. Parts are matched by organization, reference, total and destination, so you can send them over different connections of the same organization. Each part gets its own message_id and its own receipt.
An example receipt
id:0192f0c4-7b1e-7a3d-9c55-3e2a1b4c5d6e sub:001 dlvrd:001 submit date:2610241004 done date:2610241004 stat:DELIVRD err:000 Text:Hola Ana, tu pedido Expected responses
submit_sm_resp
With status 0 it carries the message_id: a version 7 UUID of 36 characters, one per PDU, including each part of a long message. With any other status, the message was not accepted: see the command_status table.
Delivery receipts
A receipt is the final status we know for your message. It does not guarantee that the person read it.
- It arrives as a
deliver_smwithesm_class0x04 and the addresses swapped:source_addris the phone you sent to anddestination_addris your sender. - There is one receipt per part.
subanddlvrdare always001. submit dateis when we accepted the message anddone dateis when the outcome became known, both in Colombia time (UTC−5).Text:carries the first 20 printable ASCII characters of the message, so in UCS-2 it may be almost empty.- Read the status from the TLVs rather than the text: the text is informational and the TLVs are the standard form.
user_message_referenceis not returned.
id:<message_id> sub:001 dlvrd:001 submit date:YYMMDDhhmm done date:YYMMDDhhmm stat:<status> err:<error> Text:<text>stat: | message_state | What it means | Sent with 0x02? |
|---|---|---|---|
DELIVRD | 2 | Delivered. | No |
EXPIRED | 3 | Expired without being delivered. | Yes |
DELETED | 4 | Deleted. | Yes |
UNDELIV | 5 | Could not be delivered. | Yes |
ACCEPTD | 6 | Accepted. | No |
UNKNOWN | 7 | Unknown status. | Yes |
REJECTD | 8 | Rejected. | Yes |
| TLV | Tag | Use |
|---|---|---|
message_payload | 0x0424 | Read in submit_sm: the text, when it does not fit in short_message. If both arrive, short_message is used. |
sar_msg_ref_num | 0x020C | Read in submit_sm: long-message reference, if you do not use UDH. |
sar_total_segments | 0x020E | Read in submit_sm: total number of parts. |
sar_segment_seqnum | 0x020F | Read in submit_sm: this part’s number, starting at 1. |
receipted_message_id | 0x001E | Always in the receipt: the message’s message_id. |
message_state | 0x0427 | Always in the receipt: the status, as a number (status table). |
How receipts reach you
- Each receiving connection starts with 5 unconfirmed receipts at a time and can go up to 50 if you answer quickly.
- If you do not answer a
deliver_smwithin 60 s, or answer with a non-zero status, the receipt is sent again. It may reach you more than once: drop duplicates byreceipted_message_id. - If you have no receiving connection, receipts wait and keep being retried for up to 72 hours.
Common errors
The most frequent responses and what to do. The full list is in command_status codes.
| Code | When | What to do |
|---|---|---|
0x00000001 ESME_RINVMSGLEN | The submit_sm is malformed, or carries no text in either short_message or message_payload. | Check how your client builds the PDU. Do not retry it unchanged. |
0x00000004 ESME_RINVBNDSTS | The command does not fit the connection state: it arrived before a successful bind (enquire_link included), or it is a submit_sm over a receiver connection. | Bind first, and send over a transmitter or transceiver connection. An unbind without a bind gets this code and also closes the connection. |
0x0000000A ESME_RINVSRCADR | You connected from an IP address that is not authorized for your account. | Ask support to authorize that IP address. |
0x0000000D ESME_RBINDFAIL | The password is wrong, the bind is malformed (and the connection is closed), or there were too many failed attempts: after 5 in one minute from the same IP and system_id, the rest of that minute gets this code even with the right password. | Check the password and wait for the next minute before retrying. |
0x00000058 ESME_RTHROTTLED | You went over 3,000 submit_sm per second on that connection, or Hablame is briefly slowing intake to protect itself. | Slow down and retry that message with a backoff. The connection stays open. |
Security
- SMPP 3.4 limits the password to 8 characters. That is why your account only accepts connections from the IP addresses you authorized, and a bind from any other gets 0x0000000A.
- After 5 failed binds in one minute from the same IP and
system_id, the rest of that minute is rejected. - Use TLS 1.2 or later so your password and messages do not travel in clear text.
- Keep the password in your secrets manager and never send it by email. If you think it leaked, email developers@hablame.co.
Related endpoints
SMPP does not use the REST API: there are no related endpoints. Inbound SMS arrive by webhook.