SMPP connection guide

For banks, aggregators and high-volume platforms that speak SMPP natively: bind directly and stream traffic without HTTP overhead.

Getting access

  1. In the portal, go to Settings and request SMPP access.
  2. Once enabled, your system_id appears there. The bind password is your mbs_ API token: the same one you create under API Tokens in the portal sidebar (it needs the message-send scope).
  3. Whitelist of your source IPs may be requested for production binds.

Long passwords are expected

SMPP 3.4 nominally caps passwords at 9 characters; 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

FieldTypeRequiredDescription
ProtocolSMPP 3.4NoThe industry-standard version. v5.0 features are not required.
Bind typetransceiverNoOne bind for both submit_sm (MT) and deliver_sm (DLRs). Separate TX/RX binds also accepted.
system_idcredentialNoIssued per account when SMPP access is enabled. Shown in portal Settings.
passwordmbs_ tokenNoYour mbs_ API token, passed in full as the bind password. Revoking the token instantly kills the bind credentials.
source_addralphanumericNoYour approved sender ID. source_addr_ton=5, source_addr_npi=0 for alphanumeric senders.
destinationE.164No2547XXXXXXXX, 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) or message_payload; both are supported.

Delivery receipts

Set registered_delivery=1 on submit_sm; DLRs arrive as deliver_sm with the standard receipt text format:

text
id:7c31a2f4 sub:001 dlvrd:001 submit date:2607111015 done date:2607111016 stat:DELIVRD err:000 text:…

stat values

FieldTypeRequiredDescription
DELIVRDfinalNoDelivered to the handset.
UNDELIV / EXPIREDfinalNoCould not be delivered within the validity period.
REJECTDfinalNoRefused by the carrier.
ACCEPTD / ENROUTEinterimNoIn 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

SMPP is a binary TCP protocol, so there's no curl equivalent, for a quick command-line smoke test of your credentials, send one message through the REST API instead; it exercises the same sender and routing. Rust's SMPP crate ecosystem is still young with no de-facto standard client. Rust shops usually integrate over the REST API (see the Rust tabs throughout these docs) or wrap an existing SMPP library via FFI. On .NET the widely deployed client is the commercial Inetlab.SMPP library. It works fine against our bind parameters above, but unless you already own a licence, the REST API (C# and VB.NET tabs throughout these docs) is the faster route.

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

FieldTypeRequiredDescription
0x0A ESME_RINVSRCADRsenderNoThe 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_RINVDSTADRrecipientNoThe 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_RINVMSGLENmessageNoEmpty message, or longer than the carrier accepts (over 918 GSM-7 or 402 UCS-2 characters).
0x58 ESME_RTHROTTLEDbalanceNoYour SMS balance cannot cover the message. Top up and resubmit; nothing was sent.
0x04 ESME_RINVBNDSTSsessionNosubmit_sm before a successful bind. Bind first.
0x0E ESME_RINVPASWD / 0x0F ESME_RINVSYSIDbindNoWrong password or unknown system_id, or SMPP access is switched off for that user. Check Settings, SMPP in the portal.
0x08 ESME_RSYSERRoursNoSomething 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

Our server used to answer 0x09 for a bad sender and 0x0A for a bad recipient. Libraries showed the raw number (for exampleCommandStatusType(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_link every 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?

The REST bulk endpoint happily moves hundreds of thousands of messages a day with far less operational burden. Choose SMPP when you already run an SMPP stack or need sustained multi-hundred-TPS throughput.