SMPP connection guide
For banks, aggregators and high-volume platforms that speak SMPP natively: bind directly and stream traffic without HTTP overhead.
Getting access
- In the portal, go to Settings and request SMPP access.
- Once enabled, your
system_idappears there. The bind password is yourmbs_API token: the same one you create under API Tokens in the portal sidebar (it needs the message-send scope). - Whitelist of your source IPs may be requested for production binds.
Long passwords are expected
mbs_ tokens are much longer and our server accepts them in full. Most client libraries pass long passwords through unchanged, if yours enforces the 9-character limit, patch or configure it; a truncated token will be rejected at bind.Connection parameters
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| Protocol | SMPP 3.4 | No | The industry-standard version. v5.0 features are not required. |
| Bind type | transceiver | No | One bind for both submit_sm (MT) and deliver_sm (DLRs). Separate TX/RX binds also accepted. |
| system_id | credential | No | Issued per account when SMPP access is enabled. Shown in portal Settings. |
| password | mbs_ token | No | Your mbs_ API token, passed in full as the bind password. Revoking the token instantly kills the bind credentials. |
| source_addr | alphanumeric | No | Your approved sender ID. source_addr_ton=5, source_addr_npi=0 for alphanumeric senders. |
| destination | E.164 | No | 2547XXXXXXXX, no plus sign. dest_addr_ton=1, dest_addr_npi=1. |
Message encoding
data_coding=0: GSM-7 default alphabet (160 chars/segment).data_coding=8: UCS-2 for Unicode (70 chars/segment).- Long messages: use UDH concatenation (
esm_class=0x40) ormessage_payload; both are supported.
Delivery receipts
Set registered_delivery=1 on submit_sm; DLRs arrive as deliver_sm with the standard receipt text format:
id:7c31a2f4 sub:001 dlvrd:001 submit date:2607111015 done date:2607111016 stat:DELIVRD err:000 text:…stat values
| Field | Type | Required | Description |
|---|---|---|---|
| DELIVRD | final | No | Delivered to the handset. |
| UNDELIV / EXPIRED | final | No | Could not be delivered within the validity period. |
| REJECTD | final | No | Refused by the carrier. |
| ACCEPTD / ENROUTE | interim | No | In transit. A final receipt follows. |
Bind and send: code examples
A transceiver bind that submits one message and listens for delivery receipts, in the most common SMPP client stacks. Swap in the host, port and credentials from your portal Settings page.
// go get github.com/linxGnu/gosmpp
package main
import (
"fmt"
"log"
"os"
"time"
"github.com/linxGnu/gosmpp"
"github.com/linxGnu/gosmpp/data"
"github.com/linxGnu/gosmpp/pdu"
)
func main() {
auth := gosmpp.Auth{
SMSC: "smpp.mobilesasa.com:2775",
SystemID: "your_system_id",
Password: os.Getenv("MOBILESASA_TOKEN"), // your mbs_ API token
SystemType: "",
}
trans, err := gosmpp.NewSession(
gosmpp.TRXConnector(gosmpp.NonTLSDialer, auth),
gosmpp.Settings{
EnquireLink: 30 * time.Second, // keepalive
OnPDU: func(p pdu.PDU, _ bool) {
if dsm, ok := p.(*pdu.DeliverSM); ok {
msg, _ := dsm.Message.GetMessage()
fmt.Println("DLR:", msg) // id:… stat:DELIVRD …
}
},
OnRebindingError: func(err error) { log.Println("rebind:", err) },
},
5*time.Second,
)
if err != nil {
log.Fatal(err)
}
defer trans.Close()
sm := pdu.NewSubmitSM().(*pdu.SubmitSM)
sm.SourceAddr = pdu.NewAddressWithAddr("DUKAPAY")
sm.SourceAddr.SetTon(5) // alphanumeric
sm.SourceAddr.SetNpi(0)
sm.DestAddr = pdu.NewAddressWithAddr("254712345678")
sm.DestAddr.SetTon(1) // international
sm.DestAddr.SetNpi(1)
sm.Message, _ = pdu.NewShortMessageWithEncoding(
"Your OTP is 482913", data.GSM7BIT)
sm.RegisteredDelivery = 1 // request DLRs
if err := trans.Transceiver().Submit(sm); err != nil {
log.Fatal(err)
}
select {} // keep the bind alive
}No curl, and notes on Rust and .NET
Response codes
Every submit_sm_resp carries a standard SMPP v3.4command_status. Zero means accepted and themessage_id is the tracking id you will see in delivery receipts. These are the ones we send and what each means here:
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| 0x0A ESME_RINVSRCADR | sender | No | The source_addr is not a sender ID your account holds (approved, active, and either yours, shared with you or the platform default), or it is not registered on the destination's network. If your account has the platform default sender enabled, messages to networks your sender does not cover go out as the default sender instead of failing. |
| 0x0B ESME_RINVDSTADR | recipient | No | The destination_addr is not a valid Kenyan number, its prefix belongs to no network, or your account is on trial access and the number is not one of your own. |
| 0x01 ESME_RINVMSGLEN | message | No | Empty message, or longer than the carrier accepts (over 918 GSM-7 or 402 UCS-2 characters). |
| 0x58 ESME_RTHROTTLED | balance | No | Your SMS balance cannot cover the message. Top up and resubmit; nothing was sent. |
| 0x04 ESME_RINVBNDSTS | session | No | submit_sm before a successful bind. Bind first. |
| 0x0E ESME_RINVPASWD / 0x0F ESME_RINVSYSID | bind | No | Wrong password or unknown system_id, or SMPP access is switched off for that user. Check Settings, SMPP in the portal. |
| 0x08 ESME_RSYSERR | ours | No | Something failed on our side. Retry with backoff; if it persists, contact support with the sequence_number and time. |
Before 15 October 2026 the codes were off by one
CommandStatusType(9)) or the wrong name. The codes now match the specification; if your code switches on the numeric value, update it.Keeping the bind healthy
- Send
enquire_linkevery 30 seconds; treat two missed responses as a dead bind and reconnect with backoff. - Respect the throughput agreed for your account; submit windows above it are throttled with
ESME_RTHROTTLED: back off, don't hammer. - Reconnect storms hurt everyone: cap reconnection attempts at one per 5 seconds with jitter.
Do you actually need SMPP?