SMPP wire-level recipes
The Connect via SMPP guide covers the bind lifecycle and the runnable client snippets; the SMPP API reference covers the REST surface. This page is the layer under both: the literal bytes. Each PDU below appears as a complete hex listing — the 16-octet header plus the payload — annotated field by field, so you can benchmark your client library, debug a byte offset, or translate the listing straight into a hex test fixture. The PDUs shown are the full session any sender runs:bind, submit_sm, and the keepalive loop. Field values match the worked parameters on the Connect via SMPP page: alphanumeric sender OrbitBrand, destination 447700900123, payload hello world, plain-ASCII with data_coding=0x00.
Reading a hex listing
Every SMPP PDU is a 16-octet big-endian header followed by a payload:
Two wire-level rules matter on every PDU:
- C-strings.
system_id,password, source/destination addresses, andmessage_idare NUL-terminated octet strings — the value’s bytes plus one0x00octet. A lettered span in a listing below such as4f 72 ...reads as ASCII. - Digits only on destinations.
dest_addr_tonvalues above 1 are legacy detail; setton=1(international) with an E.164 digit string,ton=5(alphanumeric) with digits anyway, and the bound is upheld — number normalization is edge-side, so don’t depend on it.
The PDU enumeration
The complete set your integration can legitimately send or expect on a session, in call order:
In responses the high bit is set: a request goes out with
0x0000xxxx, its response comes back with 0x8000xxxx. Every _resp PDU below carries the same sequence_number as its request, even on error — reuse that to line up through your own operation table.
1. Open the session: bind_transceiver
Pick a transceiver bind unless you operate a send-only/discipline split deliberately — receipts and (with the credential’s dlrMode on bind or both) inbound deliver_sm traffic come down the same session.
Request (sequence 0x00000001):
bind_transceiver_resp, 0x80000009, same sequence):
command_status=0x00000000 means the session is open and submit_sm is accepted from now on. A nonzero status in the response — 0x0000000E (ESME_RINVPASWD, bad password), 0x0000000F (ESME_RINVSYSID, unknown system_id), 0x00000015 (ESME_RINVBNDFMT, source IP outside the credential’s allowedCidrs), or 0x0000000D (ESME_RALYBND — see Errors) — rejects in the same envelope; read the status from the response before assuming the network ate it.
2. Send and poll the response: submit_sm
Request (sequence 0x00000002, length 0x42 = 66):
submit_sm_resp, 0x80000004, message_id for DLR reconciliation):
message_id against your sequence_number closing out the send. An inbound deliver_sm receipt on the same bind embeds that same string in its receipted_message_id field, which is the join between your operation table and the delivery world.
Send body notes:
data_coding=0x08is UCS-2 (Unicode);0x00is the SMSC default alphabet (GSM-7). Set the right one or you’re sending mojibake.esm_class=0x40flags UDHI for concatenated segments when you breach a single segment’s 140-octet limit; short segments link with a UDH at the payload head.- The
submit_smPDU has no batch mode: pipeline many in flight on the bind and let the remote window drain them. sm_lengthis a single octet of payload length right ahead ofshort_message; when you’re debugging capture, a mismatched length there is the frame’s framing fault.
3. Stay alive: enquire_link
Send one every 30–60 seconds. Response expected within your client’s timeout (10 s is plenty):
4. Close cleanly: unbind
submit_sm before the unbind — receipts sent in the window between the TCP close and the session teardown don’t re-queue.
sequence_number, a map from sequence → pending operation (each submit_sm goes in until the submit_sm_resp arrives), and the returned message_id keyed for DLR joins. Rebinds reset nothing for the edge — it correlates on the number alone, and re-use of sequence values across sessions is fine.
Errors
Work these top to bottom — the first row that matches is the fix.
Compliance controls (opt-out suppression, tenant quiet hours, consent) apply to every
submit_sm the same way they do on the REST path, per your tenant’s own configuration — the controls are tenant-owned and apply the same on the SMPP wire. A rejection there arrives as a nonzero command_status in submit_sm_resp, not as a silent drop, and the Developer → SMPP Compliance tab shows you how they apply. The Devotel softswitch carries the MT leg; your bind is the wire into it.
See also
- Connect via SMPP — bind credentials, ports, throughput, and the diagnostic ladder
- SMPP: the no-delivery receipt window — per-rule timing for the UNDELIV receipt on absorbed traffic
- SMPP edge model — the relay underneath your bind
- Send-side DLR model — the receipt vocabulary that
message_idreconciliation feeds - SMPP API reference — the REST surface for credentials and carriers