Work through it, tick as you go.
bind_transceiverOpen and authenticate the sessionbind_transceiver_respSession acceptedsubmit_smSend a messagesubmit_sm_respAccepted, message ID returneddeliver_smDelivery receipt or inbound messagedeliver_sm_respAcknowledgedenquire_linkKeepalive while idle
Session & bind setup
Pick the right bind mode for your traffic direction and confirm the negotiated protocol version before you send a single message.
- Confirm SMPP version — v3.4 for most routes, v5.0 where the provider supports congestion-state and enhanced TLVs.v5.0 is backward compatible; don't assume features are available until the bind_resp confirms it.
- Choose the correct bind type per session.transmitter (MT only), receiver (DLR/MO only), or transceiver (both on one session). Prefer transceiver to halve connection count where the provider allows it.
- Set system_id, password, system_type, and address_range exactly as provisioned.A mismatched address_range silently blocks MO/DLR routing on receiver binds.
- Agree the number of concurrent binds and per-bind TPS with the provider in writing.Opening more binds than provisioned is the most common cause of a throttled or dropped account.
- Handle bind_resp status codes explicitly — retry ESME_RALYBND and back off on ESME_RBINDFAIL.Never hot-loop a failed bind; use exponential backoff with jitter.
Submitting messages (submit_sm)
- Set source_addr_ton / source_addr_npi and dest_addr_ton / dest_addr_npi correctly for alphanumeric vs numeric sender IDs.Alphanumeric sender → TON 5 (alphanumeric); international MSISDN → TON 1, NPI 1.
- Choose the correct data_coding: GSM 7-bit (0), 8-bit (4), or UCS-2 (8) for Unicode.Mixing encodings without setting data_coding causes mojibake and mis-segmentation.
- Implement long-message concatenation via UDH or the SAR optional TLVs (sar_msg_ref_num, sar_total_segments, sar_segment_seqnum).Pick one method and be consistent; some routes reject UDH, others reject SAR TLVs.
- Request delivery receipts with registered_delivery = 1 only where you consume them.Requesting DLRs you don't parse wastes throughput and can trigger provider-side warnings.
- Respect validity_period and schedule_delivery_time semantics for your use case (OTP = short validity).Short-lived OTPs should carry a tight validity_period so stale messages aren't delivered late.
Optional parameters (TLVs) worth setting
- message_payload — for payloads that exceed the short_message field limit.Don't populate both short_message and message_payload; providers reject that.
- sar_* TLVs — standards-based concatenation without UDH bytes in the body.
- dest_addr_subunit / source_addr_subunit — where the route requires it for handset routing.
- Carry client-supplied metadata only in provider-approved TLVs.Custom TLVs outside the agreed range are commonly stripped or cause parse errors downstream.
Delivery receipts (DLR) handling
Delivery receipts arrive as deliver_sm PDUs and are the backbone of your reconciliation and quality reporting.
- Parse the receipt from the deliver_sm short_message field: id, sub, dlvrd, submit date, done date, stat, err.The `stat` field (DELIVRD, UNDELIV, EXPIRED, REJECTD, etc.) is your terminal status.
- Correlate DLRs to your submit using the provider's message_id format, not the receipt text alone.message_id may be returned in hex on submit_resp but decimal in the DLR — normalise both.
- Treat intermediate (non-final) receipts separately from final states.esm_class bit 0x04 marks a delivery receipt; only final `stat` values should close a message.
- Always return a deliver_sm_resp — an unacknowledged DLR is re-sent and can stall the window.Respond promptly even if downstream processing is async.
- Map operator error codes to a normalised taxonomy (absent subscriber, handset busy, spam-blocked, invalid number).Raw error codes vary by route; a normalised taxonomy makes cross-route quality comparable.
Keepalive & flow control
- Send enquire_link on an idle timer (commonly 30–60s) and treat a missed enquire_link_resp as a dead session.This is the only reliable way to detect a half-open TCP connection.
- Implement the SMPP window: cap outstanding un-acked PDUs and pause submits when the window is full.Blindly firing submit_sm past the window is what triggers ESME_RTHROTTLED.
- Back off on ESME_RTHROTTLED (0x58) rather than retrying immediately.Add jitter so reconnect storms don't synchronise across workers.
- Honour unbind / unbind_resp for graceful shutdown; reconnect with backoff on unexpected disconnects.
Transport security (TLS)
- Use SMPP over TLS where the provider offers it; disable plaintext binds in production.SMPP has no native encryption — TLS is the transport-layer control.
- Pin or validate the server certificate chain and enforce TLS 1.2+.Reject expired or self-signed certs; don't disable verification 'to make it work'.
- Restrict binds to allow-listed source IPs on both ends.IP allow-listing is a standard operator control and a fast way to contain credential leaks.
- Rotate SMPP credentials on a schedule and store them in a secret manager, never in code.
Pre-production test plan
- Send to a live test MSISDN on each target route and confirm handset receipt end to end.
- Verify DLR round-trip for delivered, undelivered, expired, and rejected outcomes.
- Test long (multi-segment) messages and Unicode (UCS-2) content on each route.
- Load-test at your provisioned TPS and confirm graceful behaviour at the window/throttle ceiling.
- Simulate a dropped connection mid-window and confirm clean reconnect + no duplicate submits.Idempotency on retry prevents double-billing and duplicate handset delivery.
- Confirm monitoring: bind state, window occupancy, submit/DLR latency, and error-code rates all alert.
Standards and regulator references cited in this guide. BroadNet is a GSMA Associate Member and MEF member operating carrier-grade messaging since 2003.