Before you send African traffic over an SMPP 3.4 bind, confirm eight things: the right bind type for receiving delivery reports, a throughput and window setting that matches what you were allocated, allow-listed source IPs, correct TON/NPI and E.164 addressing, the right data_coding for Unicode languages, consistent long-message handling, DLR mapping on message_id, and an enquire_link keep-alive with clean reconnects. Most bind and delivery problems we see come from one of these.

Last reviewed: 28 October 2026. Rules and network practice change; check the linked sources for anything you rely on.

This is a pre-go-live checklist, not a first-connection tutorial. For connection basics read connecting to eSMS Africa over SMPP and the SMPP integration guide; for when SMPP is worth it at all, see SMS API vs SMPP.

0. Before the first bind

  • Credentials: a system_id and password per bind, requested through the SMPP portal. Keep separate binds for production and testing.
  • Host and port: smpp.esmsafrica.io, port 2775. Confirm the transport settings for your bind with the team when it is provisioned.
  • Static source IPs: connections are accepted only from allow-listed addresses. If you run in the cloud, send through a fixed egress IP or NAT gateway; autoscaled nodes with changing IPs are a common cause of sudden bind failures. See whitelisting your IP for SMPP.
  • Allowed sender IDs: each bind carries a list of permitted senders. A source_addr outside that list is rejected.

1. Choose the bind type

BindPDUSendsReceives DLRs and inboundUse when
Transceiver (TRX)bind_transceiverYesYesMost integrations: one session does both
Transmitter (TX)bind_transmitterYesNoYou split sending and receiving across processes
Receiver (RX)bind_receiverNoYesPaired with TX binds to collect receipts

The classic mistake is a TX-only setup with registered_delivery set: receipts are generated but have nowhere to go. If you bind TX, keep an RX bind open for the same account.

2. Throughput and window

Throughput (messages per second) is agreed per bind. The window is how many submit_sm PDUs you send before waiting for their responses. A window that is too large for your allocation leads to throttling; one that is too small wastes the allocation on round-trip time.

  • Start with a modest window (around 10) and raise it while watching response times.
  • Treat ESME_RTHROTTLED (0x00000058) and ESME_RMSGQFUL (0x00000014) as "slow down": pause, back off, and retry the same message. Do not drop it or open extra binds to push past the limit.
  • Spread large campaigns over time, especially to a single network in one country.

3. Keep the session alive

Send enquire_link about every 30 seconds when idle and answer the server's enquire_link promptly. If a response does not come back within your timeout, close the socket and rebind with exponential back-off (for example 2, 4, 8 seconds up to a minute). Never rebind in a tight loop: repeated failed binds can get an IP temporarily blocked. On shutdown, send unbind and wait for unbind_resp.

4. Addressing: TON, NPI and number format

FieldAlphanumeric senderInternational numberShort code
source_addr_ton5 (alphanumeric)1 (international)3 (network specific)
source_addr_npi0 (unknown)1 (ISDN/E.164)0

For destination_addr use TON 1, NPI 1 and the E.164 digits without the plus: 254712345678, 2348031234567, 2250707123456 (Cote d'Ivoire keeps the 07 after its 2021 move to 10 digits). Strip national trunk zeros before submitting. Alphanumeric senders are limited to 11 characters.

5. data_coding for African languages

data_codingMeaningSingle / per partUse for
0SMSC default alphabet (GSM-7)160 / 153 charactersEnglish, Swahili, Kinyarwanda, Somali, Zulu, French without ê, â, ô, ç
8UCS-2 (UTF-16 big-endian)70 / 67 charactersAmharic, Tigrinya, Arabic, Portuguese and French with accents outside GSM-7, Yoruba and Igbo diacritics
3Latin-1140 bytesAvoid unless agreed; handset support varies

Two bugs come up again and again: sending UTF-8 bytes with data_coding=0 (the handset shows garbage), and sending UCS-2 without setting data_coding=8. Encode the text yourself, set the flag to match, and test with a real Amharic or Arabic string before go-live. Our language-by-language encoding guide lists which languages fit GSM-7.

6. Long messages

SMPP 3.4 gives you three ways to send a message longer than one segment. Pick one and use it everywhere:

  • UDH in the payload (most common): set esm_class 0x40 and start each part with a 6-byte header 05 00 03 [ref] [total] [seq]. This leaves 153 GSM-7 or 67 UCS-2 characters per part.
  • SAR TLVs: sar_msg_ref_num, sar_total_segments, sar_segment_seqnum, without a UDH.
  • message_payload TLV: send the whole text in one PDU and let the platform split it.

Use a new reference number per message, keep it the same across its parts, and never split inside a UCS-2 character or a GSM-7 escape sequence (characters such as € and [ take two septets). Each part is a billable segment.

7. Delivery report mapping

  • Set registered_delivery=1 on each submit_sm you want a receipt for.
  • Store the message_id from submit_sm_resp against your own message ID. For multi-part messages each part gets its own message_id.
  • Receipts arrive as deliver_sm with esm_class 0x04. Read the receipted_message_id and message_state TLVs where present, and otherwise parse the text: id:... sub:001 dlvrd:001 submit date:... done date:... stat:DELIVRD err:000.
  • Normalise IDs before matching: some systems report the same ID in hexadecimal in one place and decimal in another.
  • Handle a receipt arriving before you have stored the response (rare, but it happens under load): park unmatched receipts for a short time and retry the match.
  • Always reply deliver_sm_resp quickly, even if your database write fails; otherwise receipts are resent and back up the session.

Statuses to map: DELIVRD, UNDELIV, EXPIRED, REJECTD, DELETED, ACCEPTD and UNKNOWN. See how delivery receipts work over SMPP.

8. Errors worth handling explicitly

command_statusNameAction
0x0000000E / 0x0000000FESME_RINVPASWD / ESME_RINVSYSIDCheck credentials; do not retry in a loop
0x0000000DESME_RBINDFAILCheck the source IP allow-list and bind limits
0x00000005ESME_RALYBNDYour client sent a second bind on the same session
0x0000000AESME_RINVSRCADRSender not allowed on this bind, or wrong TON/NPI
0x0000000BESME_RINVDSTADRDestination not in E.164 or not routable
0x00000058ESME_RTHROTTLEDBack off and retry the same message

More on bind errors: why is my SMPP bind failing.

A reference client

This Python sketch uses the open-source smpplib library to bind as a transceiver, send a Unicode message split into parts with UDH, and print receipts:

# pip install smpplib
import smpplib.client, smpplib.consts, smpplib.gsm

client = smpplib.client.Client("smpp.esmsafrica.io", 2775)

def on_deliver(pdu):
    # Delivery receipts and inbound messages arrive here on RX/TRX binds
    print("deliver_sm:", pdu.receipted_message_id, pdu.message_state, pdu.short_message)

client.set_message_received_handler(on_deliver)
client.set_message_sent_handler(lambda pdu: print("submit_sm_resp id:", pdu.message_id))

client.connect()
client.bind_transceiver(system_id="YOUR_SYSTEM_ID", password="YOUR_PASSWORD")

text = "ሰላም! Your order 1042 has shipped and will arrive on Friday."  # Amharic greeting forces UCS-2
parts, coding, esm_class = smpplib.gsm.make_parts(text)   # splits with UDH, picks data_coding

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="BRAND",
        dest_addr_ton=smpplib.consts.SMPP_TON_INTL,
        dest_addr_npi=smpplib.consts.SMPP_NPI_ISDN,
        destination_addr="251911234567",            # E.164 digits, no plus sign
        short_message=part,
        data_coding=coding,
        esm_class=esm_class,
        registered_delivery=True,                   # ask for a DLR
    )

client.listen()   # keeps the session open, answers enquire_link

Go-live test plan

  1. Bind, stay idle for five minutes, and confirm enquire_link keeps the session up.
  2. Send one GSM-7 message and receive DELIVRD.
  3. Send an Amharic or Arabic message and check it on a real handset.
  4. Send a three-part GSM-7 message and confirm it reassembles in order.
  5. Send to a deliberately invalid number and confirm your system records the failure.
  6. Push above your allocation briefly and confirm you back off on throttling errors.
  7. Kill the connection and confirm your client rebinds with back-off and loses no queued messages.

Frequently asked questions

Which bind type should I use for African traffic?

A transceiver bind suits most senders because DLRs come back on the same session. Use separate transmitter and receiver binds only if your architecture needs it, and never run transmitter-only if you request receipts.

Why does my Arabic or Amharic text show as question marks?

The text was sent in the wrong encoding, usually UTF-8 bytes with data_coding 0. Encode as UCS-2 (UTF-16 big-endian) and set data_coding to 8.

How often should I send enquire_link?

About every 30 seconds of inactivity is a safe default. If a response does not arrive within your timeout, reconnect with back-off.

Is SMPP priced differently from the API?

Messages are billed per segment by destination country in both cases. See is SMPP priced differently and the pricing page.

Sources

Ready to connect? See SMPP connectivity and the country routes on pages such as SMPP to Nigeria and SMPP to Kenya, then create an account and request your first bind.