Integrations
Sending SMS over SMPP
Banks, fintechs and aggregators can send to Sendozi over SMPP 3.4 as well as the HTTP API. Sendozi creates the account; you bind from your allowed IPs with the system ID and password, send submit_sm with your approved Sender ID, and receive delivery receipts as deliver_sm on the same connection.
Who SMPP is for
Use SMPP if you already run an SMS gateway or an SMPP client and want to keep one open connection to Sendozi. Everything else can use the HTTP API, which needs no persistent connection.
A message sent over SMPP passes the same checks as one sent over the HTTP API: approved KYC, a Sender ID approved on the route, a funded wallet, opt-outs and Do Not Disturb, and for transactional traffic the approved templates. It is priced and charged the same way, and it appears in the Console under Messages like any other send. There is no sandbox over SMPP: every accepted message is live.
Getting an SMPP account
- 1
Get your workspace ready
KYC approved, the wallet funded, and a Sender ID approved on the route you will send on. For transactional traffic, the Sender ID also needs approved templates; see Transactional SMS.
- 2
Ask Sendozi for an account
SMPP accounts are created by Sendozi, not in the Console. Send your workspace name, the fixed public IP addresses your SMPP client connects from, and whether the account is for transactional or promotional traffic.
- 3
Receive the connection details
The workspace owner receives an email from support@sendozi.com with the host, port, system ID, password, route, bind limit, messages per second and your allowed IPs. Keep the password private; if it is ever exposed, reply to that email and Sendozi will reset it.
- 4
Connect and test
Bind from one of your allowed IPs, send a message to your own phone, and check that its delivery receipt arrives. Your allowed IPs are admitted within a minute of the account being created.
Connection settings
| Setting | Value |
|---|---|
| Host and port | As given in your connection details email, port 2775. TLS is available on request. |
| Protocol | SMPP 3.4 |
| system_id and password | From the email. The password is 8 characters, the most SMPP allows. |
| Bind | bind_transceiver, or a bind_transmitter plus a bind_receiver. No more binds at once than your account's bind limit. |
| Bind within | 30 seconds of connecting, or the connection is closed. |
| enquire_link | At least every 2 minutes; every 30 seconds is a good default. A bind that sends nothing for 2 minutes is closed. |
| Window | Up to 50 submit_sm awaiting a response on each bind. |
| Throughput | Your account's messages per second, counted across all your binds. |
After 10 failed binds in 15 minutes from one system_id or one address, further binds are refused without being checked. A wrong system_id and a wrong password get the same answer.
Sending a message: submit_sm
| Field | What to send |
|---|---|
| source_addr | A Sender ID approved for your workspace on your account's route, spelt as approved. |
| destination_addr | One Nigerian mobile number. The same spellings as the HTTP API are accepted; 2348012345678 is the safest. |
| source and destination TON/NPI | Not checked. Your client's usual values are fine (5/0 for an alphanumeric Sender ID, 1/1 for the number). |
| data_coding | 0 (GSM 03.38, unpacked: one character per byte), 1 (ASCII), 3 (Latin-1) or 8 (UCS-2). Any other value is read as GSM 03.38. Use 8 for text with characters outside GSM 03.38. |
| short_message or message_payload | The text. message_payload may carry a whole long message in one submit_sm. |
| esm_class | Set the UDH indicator (0x40) when short_message starts with a concatenation header. |
| registered_delivery | 1 for a delivery receipt. 2 (receipt on failure only) is treated as 1; 0 asks for none. |
A submit_sm_resp with command_status 0 means the message is queued, not delivered. Its message_id is 16 hexadecimal characters, and the delivery receipt carries the same id: store it as a string. Live promotional messages accepted outside 08:30 to 19:30 WAT are queued and sent when the window opens; transactional messages go at any hour.
Transactional accounts
- Every message must be one of the Sender ID's approved transactional templates, with its variables filled in. Sendozi matches the text to the template itself, so there is no template_id to send.
- The matched template's event type is used, and each message counts as its own event.
- Text that matches no approved template is refused with 0x00000402.
- The Sender ID's per-minute and daily transactional limits apply; past them, submit_sm is refused with ESME_RTHROTTLED (0x00000058).
Long messages
- Send a long message as parts with a concatenation header (8-bit reference, IEI 0x00, or 16-bit, IEI 0x08), as parts with the sar_msg_ref_num, sar_total_segments and sar_segment_seqnum parameters, or whole in message_payload.
- Each part is answered with its own message_id and gets its own delivery receipt. The message is sent, and priced and charged as one message, when its last part arrives.
- All parts must arrive within a minute. If they do not, nothing is sent or charged, and the parts already answered receive UNDELIV receipts with err 011 (when receipts were asked for).
- If a message is refused after its first parts were answered, those parts receive UNDELIV receipts with the reason in err.
- The whole message may be up to 1000 characters of GSM text, or 469 with Unicode characters. Longer messages are refused with ESME_RSUBMITFAIL (0x00000045).
Delivery receipts
A receipt is a deliver_sm with esm_class 0x04, sent on a transceiver or receiver bind. Its source_addr is the recipient's number and its destination_addr is your Sender ID. It carries the receipted_message_id and message_state parameters, and this text, with dates as YYMMDDhhmm in WAT:
id:3f9a1c0b7d2e4a68 sub:001 dlvrd:001 submit date:2609261432 done date:2609261432 stat:DELIVRD err:000 text:- Answer every receipt with deliver_sm_resp status 0. A receipt that is not acknowledged is sent again 30 seconds later.
- Receipts wait for you while no receiver bind is open, and are dropped if not collected within 72 hours.
- A message with no report from the network within 72 hours gets an EXPIRED receipt with err 010.
| stat | message_state | Meaning |
|---|---|---|
| DELIVRD | 2 | Delivered to the handset |
| UNDELIV | 5 | Not delivered; err says why |
| REJECTD | 8 | Rejected by the network |
| EXPIRED | 3 | No final report within 72 hours |
| err | Meaning |
|---|---|
| 000 | Delivered |
| 001 | Not delivered, no more specific reason |
| 002 | Do Not Disturb |
| 003 | Invalid number |
| 004 | Sender ID refused |
| 005 | Wallet balance |
| 006 | Unconfirmed: it may have been delivered; not charged, not resent |
| 007 | Cancelled |
| 008 | Recipient opted out |
| 009 | Sending paused by Sendozi |
| 010 | No report from the network within 72 hours |
| 011 | A long message never arrived whole |
| 012 | Template not approved |
Status codes
| command_status | Meaning | What to do |
|---|---|---|
| 0x00000000 | Queued; the message_id is the one its receipt will carry | Wait for the receipt |
| 0x00000058 ESME_RTHROTTLED | Over your messages per second, or a transactional limit | Slow down and retry |
| 0x00000014 ESME_RMSGQFUL | Too many submit_sm awaiting a response on this bind | Wait for responses, then retry |
| 0x00000008 ESME_RSYSERR | Temporary, on Sendozi's side (sending paused, an outage) | Retry later |
| 0x0000000B ESME_RINVDSTADR | Not a valid Nigerian mobile number | Fix the number |
| 0x0000000A ESME_RINVSRCADR | Sender ID not approved for your workspace on this route | Use an approved Sender ID |
| 0x00000001 ESME_RINVMSGLEN | The message has no text | Send the text |
| 0x00000004 ESME_RINVBNDSTS | Not bound, or submitted on a receiver bind | Submit on a transmitter or transceiver bind |
| 0x00000401 | Wallet balance too low, or wallet frozen | Fund the wallet |
| 0x00000402 | Text matches no approved transactional template | Use the approved wording |
| 0x00000403 | Account, workspace or its key may not send (suspended, KYC, no live access) | Contact Sendozi |
| 0x00000404 | Recipient opted out, or on Do Not Disturb for this kind of message | Do not retry |
| 0x00000405 | Content failed Sendozi's policy checks | Change the text |
| 0x00000045 ESME_RSUBMITFAIL | Anything else wrong with the request, such as a message that is too long | Check the message |
| command_status | Meaning |
|---|---|
| 0x0000000E ESME_RINVPASWD | Wrong system_id or password |
| 0x0000000D ESME_RBINDFAIL | Account or workspace suspended, bind limit reached, address not allowed, or too many failed binds |
| 0x00000005 ESME_RALYBND | This connection is already bound |
| 0x00000008 ESME_RSYSERR | Temporary; bind again shortly |
| 0x00000003 ESME_RINVCMDID | Command not offered |
Examples
Most SMS gateways and platforms only need the settings above entered as a new SMSC connection. If you are writing your own client, these show a bind, a submit_sm and receipt handling. Use the host, system ID and password from your email.
const smpp = require("smpp");
const session = smpp.connect({ url: "smpp://smpp.sendozi.com: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 refused:", bind.command_status);
session.submit_sm({
source_addr: "AcmeBank", source_addr_ton: 5, source_addr_npi: 0,
destination_addr: "2348012345678", dest_addr_ton: 1, dest_addr_npi: 1,
registered_delivery: 1,
// Text longer than one SMS: send it in message_payload, or split it with a UDH.
short_message: "Your code is 482913. It expires in 10 minutes.",
}, (resp) => {
if (resp.command_status === 0) console.log("Queued:", resp.message_id);
else console.error("Refused: 0x" + resp.command_status.toString(16));
});
});
// Receipts arrive on the same bind. Acknowledge every one.
session.on("deliver_sm", (pdu) => {
console.log("Receipt for", pdu.receipted_message_id, pdu.short_message.message);
session.send(pdu.response());
});import os
import smpplib.client
import smpplib.consts
import smpplib.gsm
client = smpplib.client.Client("smpp.sendozi.com", 2775)
client.set_message_sent_handler(lambda pdu: print("Queued:", pdu.message_id))
client.set_message_received_handler(lambda pdu: print("Receipt for", pdu.receipted_message_id))
client.connect()
client.bind_transceiver(system_id=os.environ["SMPP_SYSTEM_ID"], password=os.environ["SMPP_PASSWORD"])
# make_parts splits long text and adds the concatenation header.
parts, encoding_flag, msg_type_flag = smpplib.gsm.make_parts("Your code is 482913. It expires in 10 minutes.")
for part in parts:
client.send_message(
source_addr_ton=smpplib.consts.SMPP_TON_ALNUM,
source_addr_npi=smpplib.consts.SMPP_NPI_UNK,
source_addr="AcmeBank",
dest_addr_ton=smpplib.consts.SMPP_TON_INTL,
dest_addr_npi=smpplib.consts.SMPP_NPI_ISDN,
destination_addr="2348012345678",
short_message=part,
data_coding=encoding_flag,
esm_class=msg_type_flag,
registered_delivery=True,
)
client.listen()Not offered over SMPP
- query_sm, cancel_sm, replace_sm, submit_multi and data_sm are answered with ESME_RINVCMDID. Look a message up, or cancel a queued send, in the Console or over the HTTP API.
- Replies from handsets are not delivered.
- There is no sandbox: test with a real message to your own phone.
Frequently asked questions
- Can I create an SMPP account from the Sendozi Console?
- No. Sendozi creates SMPP accounts. Contact Sendozi with your workspace name, the fixed IP addresses you connect from and the route you need, and the connection details are emailed to the workspace owner.
- Do messages sent over SMPP cost the same as the HTTP API?
- Yes. They are priced and charged the same way, from the same workspace wallet, and a long message is charged as one message.
- Why does my SMPP connection close as soon as it opens?
- Your client is connecting from an IP address that is not on your SMPP account. Only the allowed IPs listed in your connection details email are admitted; ask Sendozi to add the address you connect from.
Related reading
Sending
Transactional SMS: OTPs, alerts and receipts
What a transactional send needs: a transactional Sender ID, an approved template per event, event_type, event_reference, one recipient and an Idempotency-Key. With a working OTP example.
Reference
Account, balance, usage and Sender IDs
The read-only account endpoints: who a key belongs to, the wallet balance, lifetime usage, the workspace's Sender IDs and their approval on each route, and the service health check.
Sending
Messages, batches and delivery status
Every send creates a batch of recipients, each becoming a message. How to look them up, what each status means, why a message failed, how to page through lists, and how to cancel a send.
Reliability
Errors and the response envelope
The Sendozi response envelope, every error code the API returns with its HTTP status and fix, which errors are safe to retry, Retry-After, and how request_id traces a request.