Files
smpp-js/interop-tests/research/operator-quirks.md
T

66 KiB
Raw Blame History

Real-world SMPP behaviours & quirks checklist

For @larvit/smpp (ESME client + SMSC server, SMPP 3.4 default) pre-1.0 testing. Every item: behaviour, who, source URL, one-line test. Unsourced items are marked unverified — folklore.

Legend for repeat sources: Kannel = Kannel User's Guide 1.4.5 (kannel.org); spec = SMPP v3.4 Issue 1.2 (smpp.org/SMPP_v3_4_Issue1_2.pdf, cross-read via sysop.fr HTML mirror where the PDF extractor cut off).


1. Bind

2. Keepalive

3. Windowing / throughput

4. Message ids

  • Kannel msg-id-type (03): 0x00 both decimal, 0x01 deliver_sm decimal/submit_sm_resp hex, 0x02 deliver_sm hex/submit_sm_resp decimal, 0x03 both hex — Kannel User's Guide 1.4.5. Test: set each value and assert submit_sm_resp id vs. DLR id: field parse in the matching base.
  • Jasmin dlr_msgid: 0 = identical id in both submit_sm_resp and deliver_sm, 1 = submit_sm_resp hex / deliver_sm decimal, 2 = reverse — https://docs.jasminsms.com/en/latest/management/jcli/modules.html. Test: mirrors Kannel's quirk under a different config name — same base-mismatch bug class, two vendors, two spellings.
  • Jasmin: internal message id is a UUID (gateway-side tracking) distinct from id_smsc (an integer — what the SMSC actually returned) — https://docs.jasminsms.com/en/latest/apis/http/index.html. Test: verify a test double returning a UUID as submit_sm_resp message_id is accepted (spec only requires a C-octet string, no fixed format).
  • Melrose Labs simulator: message_id is 8 chars for bound v3.3 sessions, 64 chars for v3.4/v5 — https://melroselabs.com/services/smsc-simulator/smsc-simulator-technical-details/. Test: bind at 3.3 vs 3.4 against the simulator, assert id length differs.
  • SMPPSim: randomised message ID start value with a configurable prefix (v2.6.0+) — not sequential/predictable — https://github.com/haifzhan/SMPPSim/blob/master/SMPPSim_OFFICIAL_README. Test: assert client doesn't assume sequential/incrementing ids from this simulator.
  • Sinch (legacy Cloud SMPP): message_id is hex on SMPP 3.3 binds, standard ASCII integer (decimal) on SMPP 3.4 binds — same backend, format keyed off negotiated interface_version — https://developers.sinch.com/docs/sms/other/sms-other-cloud-smpp. Test: bind at 0x33 vs 0x34, confirm message_id format in submit_sm_resp changes accordingly.
  • Vonage: by default, submit_sm_resp.message_id is hex while the corresponding deliver_sm DLR id: field is decimal — the two don't match without conversion unless the account is reconfigured; hex deprecated in 2023 in favor of UUID format for both — https://api.support.vonage.com/hc/en-us/articles/204015803 , https://api.support.vonage.com/hc/en-us/articles/204015663. Test: correlate submit_sm_resp message_id (hex) against DLR id: (decimal), confirm conversion is required to match; also test UUID-format ids on newer accounts.
  • Bird: receipted_message_id TLV (tag 0x001E) in the DLR carries the same value as submit_sm_resp.message_idhttps://developers.messagebird.com/api/sms-messaging/. Test: capture submit_sm_resp message_id, assert the DLR's receipted_message_id TLV matches byte-for-byte.
  • Clickatell: message identity is carried in the DLR's short_message text (id:xxx) as well as via TLV — https://archive.clickatell.com/developers/api-docs/pdu-details/. Test: parse the DLR text body's id: token and confirm it matches submit_sm_resp's message_id.
  • Telesign: message_id is a 32-digit hex value, random per request; for a concatenated message, submit_sm_resp returns the reference id only for the first part (a message_parts_count TLV covers the rest) — https://developer.telesign.com/enterprise/docs/smpp-protocol. Test: submit a message that auto-splits into N parts, assert only one message_id returns while message_parts_count=N.
  • Bandwidth: submit_sm_resp.message_id is a 36-char UUID (migrated from a legacy 10-char id), echoed back in the receipted_message_id TLV (0x1E) of the DLR — https://support.bandwidth.com/hc/en-us/articles/360046149833. Test: assert the library accepts a 36-char UUID-format message_id (not just numeric/short ids) and correlates via the TLV, not string-matching the DLR body.
  • Route Mobile: only documents ESME_RINVMSGID (0x0C); no explicit hex/decimal format published — routemobile.com PDF. Test: N/A — document as an unpublished detail.

5. Delivery receipts

  • spec: sm_length/short_message max 254 octets; sm_length must be 0 when message_payload TLV is used to carry >254 octets of user data — spec §1.2.21/1.2.22 via sysop.fr mirror. Test: assert a client switches to message_payload with sm_length=0 once payload exceeds 254 octets rather than truncating.
  • spec esm_class bit meanings for MC→ESME (deliver_sm): 0x00 normal message, 0x04 "Short Message contains SMSC Delivery Receipt", 0x08 SME delivery acknowledgement, 0x10 SME manual/user acknowledgement, 0x20 "Intermediate Delivery Notification" — spec via sysop.fr mirror. Test: assert a DLR arrives with esm_class 0x04 (not 0x00) and an intermediate notification with 0x20 — a naive "esm_class != 0 means DLR" check wrongly classifies intermediates as final.
  • spec registered_delivery bits 1,0: 01 receipt on success or failure, 10 receipt on failure only, 00 none (default); intermediate notification requested via registered_delivery & 0x10 == 0x10 — corroborated by SMPPSim ("set to 0x11 for both intermediate and final") — spec via sysop.fr mirror; https://github.com/haifzhan/SMPPSim/blob/master/SMPPSim_OFFICIAL_README. Test: request 0x01 vs 0x02 vs 0x11, assert the right receipt combination arrives.
  • smpp.org receipt text format: id (variable), sub (3 octets, zero-padded), dlvrd (3 octets, zero-padded), submit date/done date (10 octets, YYMMDDhhmm), stat (7 octets), err (3 octets), text (20 octets, documented as "unused field, result will be blank" — not truncated message content, contrary to some vendor implementations) — https://smpp.org/smpp-delivery-receipt.html. Test: assert text: is blank by default and that a parser doesn't assume it always carries 20 chars of truncated body text.
  • smpp.org's own message_state-numbered stat list: 0 SCHEDULED (intermediate), 1 ENROUTE (intermediate), 2 DELIVERED, 3 EXPIRED, 4 DELETED, 5 UNDELIVERABLE — https://smpp.org/smpp-delivery-receipt.html — cross-validated against Telesign's TLV table (message_state: 2=DELIVRD, 3=EXPIRED, 4=DELETED, 5=UNDELIV, 6=ACCEPTD, 7=UNKNOWN, 8=REJECTD, 9=SKIPPED vendor-added) — https://developer.telesign.com/enterprise/docs/sms-smpp-tlvs. Test: assert the numeric message_state TLV and the 7-char stat: text field are kept in sync by any test double (they're two encodings of the same fact and can drift).
  • Melrose Labs simulator: DLR TLVs receipted_message_id, message_state (2=DELIVERED), network_error_code (0=no error); DLR "returned <1s after submit_sm_resp"; MO deliver_sm echoes the data_coding used on the original submit — https://melroselabs.com/services/smsc-simulator/smsc-simulator-technical-details/. Test: assert DLR arrives within ~1s and that MO deliver_sm's data_coding matches the original submit's.
  • Jasmin dlr_level: 1=SMS-C level, 2=Terminal level, 3=Both — https://docs.jasminsms.com/en/latest/apis/http/index.html. Test: request each level, confirm the number/timing of DLR callbacks differs (SMSC-level fires once at ack; terminal-level needs a simulated MO ack).
  • Sinch: DLR body follows spec Appendix B; deliver_sm inbound fields (service_type, esm_class, protocol_id, priority_flag, schedule_delivery_time, validity_period, registered_delivery, replace_if_present_flag, sm_default_msg_id) are "always 0"; optional vendor TLV 0x1403 carries destination MCC+MNC — https://developers.sinch.com/docs/sms/smpp/send-message , https://developers.sinch.com/docs/sms/smpp/receive-message. Test: parse deliver_sm and confirm those fields are always zero; check for optional TLV 0x1403.
  • Infobip: DLR format id: sub: dlvrd: submit date: done date: stat: err:; documented stat values include DELIVRD, EXPIRED, UNDELIV, ACCEPTD, UNKNOWN, ENROUTE, REJECTD — note ENROUTE appears as a possible DLR stat here, not just an intermediate flag — https://www.infobip.com/docs/essentials/api-essentials/smpp-specification. Test: assert the parser accepts stat:ENROUTE as a valid value, not just the plain 6-value spec set.
  • Vonage: DLR format id: sub:001 dlvrd:000 submit date: done date: stat: err: text:none; err: only populated (non-000) when state ≠ DELIVRD/ACCEPTD; documented stat values: ACCEPTD, DELIVRD, UNDELIV, EXPIRED, FAILED, REJECTD, DELETED, UNKNOWN (8 values, including FAILED/DELETED beyond the plain spec set); one DLR per segment for concatenated messages, not one per logical message; retries deliver_sm toward the ESME on temporary-failure command_status, up to 130 attempts ≥62s apart, discarding after 24h — https://api.support.vonage.com/hc/en-us/articles/204015663 , https://api.support.vonage.com/hc/en-us/articles/204015773 , https://developer.vonage.com/en/messaging/sms/guides/delivery-receipts. Test: (a) assert err:000 only on DELIVRD/ACCEPTD; (b) return a temporary-failure status on deliver_sm_resp, confirm Vonage retries rather than treating it as final; (c) confirm one DLR per segment on a multi-part SMS.
  • Bird: message_state TLV (0x0427) = final receipt state; network_error_code TLV (0x0423) = carrier-level error, distinct from the text err: value — both map into Bird's own numeric error table (0131) — https://developers.messagebird.com/api/sms-messaging/. Test: build DLR fixtures with both TLVs set and assert the parser surfaces them independently rather than conflating carrier code with SMPP err digits.
  • Clickatell: DLR body exactly id:xxx sub:001 dlvrd:NNN submit date:YYMMDDHHMMSS done date:YYMMDDHHMMSS stat:xxx err:000 text:; observed stat values DELIVRD/ACCEPTD/REJECTD/UNDELIV — https://archive.clickatell.com/developers/api-docs/pdu-details/. Test: parse against this exact field order, reject malformed variants (missing sub:, wrong date length).
  • Kaleyra: DLR only returned if registered_delivery=1 and bound as receiver/transceiver (TX-only gets none); stat values DELIVRD/FAILED/EXPIRED/UNDELIV with an extensive per-operator err code table (000 DELIVRD, 001 unknown subscriber, 003/010 absent subscriber, 004/006/007 handset error, 005 barred, 008/012 net error, 011 SMSC system failure, 013 mobile off, 016 handset busy, 023032 DLT/template failures, hex 10241911 for blacklisting/DND/etc) — https://messaging.kaleyra.com/support/solutions/articles/3000091798-delivery-reports , https://messaging.kaleyra.com/support/solutions/articles/3000091797-error-codes-and-descriptions. Test: (a) submit with registered_delivery=0, assert no DLR ever arrives; (b) submit with registered_delivery=1 on a TX-only bind, assert none arrives there; (c) table-driven test mapping numeric err code to stat value.
  • tyntec: sends a temporary "buffered" DLR shortly after submission, followed by a final DLR later — two receipts per message is expected, not a bug; large GSM error code table (0x0000 no error, 0x0001 unknown subscriber, 0x0006 absent subscriber, 0x000d call barred, 0x0021 message-waiting-list full, 0x0022 system failure, 0x6000 SIM memory full, 0xa001 no network response, 0xa002 message too long) — https://www.tyntec.com/helpcenter/docs/faqs/sms/sms-delivery/how-can-i-troubleshoot-sms-delivery/ , https://www.tyntec.com/helpcenter/docs/faqs/sms/sms-delivery/gsm-error-codes/. Test: assert two deliver_sm's for one submit_sm are accepted, not treated as a duplicate.
  • CM.com: DLR format id:(32 chars) submit date: done date: stat: err:; confirmed 7-char stat values DELIVRD/EXPIRED/DELETED/UNDELIV/ACCEPTD/UNKNOWN/REJECTD; sent via deliver_sm's short_message OR via data_sm's message_payload depending on account config; also carries a message_state TLV; proprietary TLVs operator (0x1401, MCC/MNC) and tariff (0x140A) — https://developers.cm.com/messaging/docs/smpp. Test: parse both text-body stat and message_state TLV, assert agreement; assert exactly 7-char stat.
  • LINK Mobility (SMSC-SMPP User Guide): stat values DELIVRD/EXPIRED/REJECTD/UNDELIV/DELETED; extended format sub:000 dlvrd:000 submit date: done date: stat: err: text: where sub/dlvrd are always literally "000" and text is always empty; only final delivery reports supported, no intermediate receiptshttps://www.linkmobility.com/resources/developer/SMSC-SMPP-User-Guide-1.5.pdf. Test: assert sub/dlvrd are literally "000" and text is empty on every DLR from this profile; assert requesting an intermediate notification has no effect.
  • LINK Mobility (Implementation Guide): esm_class always 0x04 for DLRs (unless configured for legacy "Logica style" text-only DRs); TLVs message_state (0x0427), network_error_code (0x0423), receipted_message_id (0x001E); vendor extension TLVs 0x17000x1706 (timestamp/operator/operator_timestamp/status_code/reason_code/status_text/message_id); non-standard registered_delivery=0x21 ("request server delivery report") beyond the spec's normal value set — https://a.storyblok.com/f/151608/x/bb282a50a7/link-mobility-implementation-guide-smpp.pdf. Test: bind with/without "Logica style DR" config, assert DR shape differs (TLV-based vs legacy text); test 0x21 as a vendor-extension registered_delivery value.
  • Telia: network_error_code TLV values SUBSCRIBER_UNKNOWN(201), SUBSCRIBER_TEMPORARILY_BARRED(202), REFUSED_BY_SERVICE_PROVIDER(207), SUBSCRIBER_ABSENT(208), ILLEGAL_EQUIPMENT(209) — https://cdn.messaging.teliacompany.com/documents/developer/index.html. Test: assert network_error_code values map to this reason set on simulated failure receipts.
  • Twilio: supports DLRs across short/long code, 10DLC, alphanumeric via its enterprise SMPP API but explicitly not Messaging-Service-specific REST features (link shortening, scheduling, advanced opt-out) via that path — https://help.twilio.com/articles/31537798746267. Test: assert SMPP-submitted messages don't trigger REST-only status-callback features.
  • Telesign: DLR presence signaled by esm_class bit 2; err is a 3-octet hex code with 40+ vendor values (e.g. 0x000004A6 blocked, 0x000004C2 sender-ID restriction); status given redundantly in both text body and TLVs; message_parts_count TLV reports segment count; DLRs route to a random connected data-center unless system_type targets one — https://developer.telesign.com/enterprise/docs/smpp-protocol , https://developer.telesign.com/enterprise/docs/sms-smpp-tlvs. Test: assert DLR text and TLV state never disagree; assert message_state=9 maps to a SKIPPED state most other vendors lack.
  • Route Mobile: DLR only if bound RX/TRX and registered_delivery=1; base stats DELIVRD/FAILED/EXPIRED/UNDELIV/REJECTD plus a numeric 000412 status-error table (e.g. 001 unidentified subscriber→UNDELIV, 027 absent subscriber→EXPIRED, 408 DND→REJECTD, 411 duplicate submission→REJECTD) — routemobile.com PDF. Test: fixture-test each numeric code against its documented stat bucket.
  • Bandwidth: ACCEPTD is "the most precise indicator of a successful message" (vs DELIVRD = actual handset confirmation); DLR listening window is 73 hours, after which error 902/9902 (not a failure) or 4794 ("expired by carrier") if a late DLR does arrive; international "Intermediate DLR" only confirms carrier hand-off, not handset delivery, and DLRs aren't guaranteed internationally — https://www.bandwidth.com/support/en/articles/12823143 , https://www.bandwidth.com/support/en/articles/12823161 , https://www.bandwidth.com/support/en/articles/12823152. Test: assert a DLR arriving after 73h is flagged distinctly (902/9902/4794), not as generic UNDELIV; assert an intermediate DLR isn't conflated with terminal DELIVRD.
  • Syniverse: explicit code table — DELIVRD=0, DELIVERED-TO-CARRIER=3/ACCEPTED-BUFFERED=4 (interim success), DELETED=5, UNDELIV=7/9/90/181/190, EXPIRED=8, REJECTD=23/35, INTERNAL ERROR=98, BAD ADDRESS=90, INVALID ROUTING=91, CARRIER GATEWAY ERROR=93, intermediate carrier states 184191 ("UNDELIV-INTERIM"), UNKNOWN=999 — https://sdcsupport.syniverse.com/hc/en-us/articles/360038257473. Test: fixture-test each numeric code against its documented interim-vs-terminal classification.

6. Long messages

  • spec: message_payload TLV holds up to 64K octets — spec via smpp.org PDF fetch. Test: cap concatenation/payload tests at 64K.
  • spec segmentation TLV tags: sar_msg_ref_num=0x020C, sar_total_segments=0x020E, sar_segment_seqnum=0x020F — spec via sysop.fr mirror. Test: submit a 3-segment message via sar_* TLVs (not UDH), confirm the receiver reassembles using the TLVs.
  • UDH 8-bit vs 16-bit reference number, MO reassembly rules, max-segment limits, and a hard ">160 char rejection" rule: not documented in the SMPP spec itself (GSM 03.40 territory) — spec review. unverified — folklore beyond vendor-specific figures below.
  • Sinch: auto-splits oversized single submissions server-side; client is not required to pre-segment — https://developers.sinch.com/docs/sms/smpp/send-message. Test: submit a single long body exceeding one segment, confirm Sinch splits it rather than rejecting.
  • Infobip: long SMS not supported for Flash notifications — a concatenated + flash (data_coding 0x10/0x18) combination is unsupported — https://www.infobip.com/docs/essentials/api-essentials/smpp-specification. Test: submit a >1-segment flash SMS, confirm rejection/silent truncation.
  • Vonage: UDH concatenation via esm_class=0x40 (UDHI bit); char budgets 160/306/459/612 for 14 part GSM-7 (≈153/part after UDH), 70 for single UCS-2 segment; accepts up to 3200 chars but "not all carriers do" — recommends max 6 parts; all parts must go through the same SMPP server/gateway or the handset never reassembles; 20-minute window to submit all parts of one multipart SMS; MO concat params (concat/concat-ref/concat-total/concat-part) absent for US Sprint/Verizon — recommends grouping by same-sender + close timestamp as fallback — https://developer.vonage.com/en/messaging/sms/guides/concatenation-and-encoding , https://api.support.vonage.com/hc/en-us/articles/360046874352 , https://api.support.vonage.com/hc/en-us/articles/11615117187484 , https://api.support.vonage.com/hc/en-us/articles/205704158. Test: (a) submit segments with the same UDH ref to two different binds, confirm no reassembly; (b) delay a segment past 20 min, confirm the multipart message fails; (c) simulate MO concat without concat-ref params, confirm the client falls back to a heuristic.
  • Bird: no public max-segment/UDH-vs-sar specifics beyond what's in topic 5/7 — skip.
  • Clickatell: message_payload (up to 64K) as an alternative to short_message; optional sar_msg_ref_num/sar_segment_seqnum/sar_total_segments TLVs supported on submit_sm — https://archive.clickatell.com/developers/api-docs/using-the-smpp-api/ , https://archive.clickatell.com/developers/api-docs/pdu-details/. Test: submit >160 chars via message_payload only (short_message len 0), confirm accepted; submit the same via sar_* TLVs, confirm equivalent behaviour.
  • Kaleyra: no submit_multi; concatenation via esm_class=0x43 ("Store and Forward with UDHI") with UDH inside short_message; message_payload (≤64K) only available at interface_version 0x34 — requesting it under 3.3 silently has no effect — https://messaging.kaleyra.com/support/solutions/articles/3000091796-submitting-messages-through-smpp. Test: bind at 0x33, attempt message_payload, confirm rejected/ignored; works after rebinding at 0x34.
  • tyntec: without UDH-based concatenation, MO/MT segment order is not guaranteed; fallback is appending a page marker (1/3, 2/3, 3/3) to each part's text — https://www.tyntec.com/helpcenter/docs/faqs/sms/sms-features/will-my-sms-messages-arrive-in-order-in-case-of-concatenated-sms/. Test: send 3 segments out of order from a mock SMSC, confirm the client reassembles by sar/UDH sequence number rather than arrival order.
  • CM.com: no long-message specifics beyond DLR/payload routing already in topic 5 — skip.
  • LINK Mobility (Implementation Guide): UDH only, signaled purely via esm_class bit 6 (0x40 present / 0x00 absent) — no sar_ TLVs, no message_payload* supported; non-GSM chars force a 70-char UCS2 segment, not 160-char GSM — https://a.storyblok.com/f/151608/x/bb282a50a7/link-mobility-implementation-guide-smpp.pdf. Test: assert non-GSM chars force UCS2 70-char splitting, not GSM 160.
  • LINK Mobility (SMSC-SMPP User Guide): message_payload TLV not supported — "only one SMS may be delivered per call" — https://www.linkmobility.com/resources/developer/SMSC-SMPP-User-Guide-1.5.pdf. Test: submit with message_payload against this profile, expect rejection/ignored.
  • Telia: message_payload explicitly supported to avoid splitting into separate submit_sm; 153 GSM chars/segment when concatenating; REST API (separate from SMPP) caps at 16 segments — https://cdn.messaging.teliacompany.com/documents/developer/index.html. Test: assert 153-char GSM segment boundary for concatenated (not single) SMS via SMPP.
  • Telesign: base SMS 140 bytes; single-message limits GSM7=160/ASCII=140/Latin-1=140/UTF-16BE=70; per-segment limits when concatenated GSM7=153/ASCII=134/Latin-1=134/UTF-16BE=67 (6-byte UDH overhead); max 10 segments; "smart splitting" avoids breaking words/URLs across segments; auto-splits an oversized single message and adds UDH per part, billed per part — https://developer.telesign.com/enterprise/docs/understand-sms-encoding-character-limits-and-splitting , https://developer.telesign.com/enterprise/docs/smpp-protocol. Test: assert segmentation breakpoints match 160/153 (GSM7) and 70/67 (UCS2); refuse an 11th segment.
  • Route Mobile: no submit_multi or sar_* TLVs at all; concatenation only via UDH with esm_class=0x43; message_payload up to 64K but only at interface_version=0x34; strict UDH length validation — ESME_RINVUDHLEN (0x406) unless UDH length is 05/06 (any data_coding) or 0b (only data_coding=245) — routemobile.com PDF. Test: submit with sar_msg_ref_num TLV set, assert rejection (ESME_ROPTPARNOTALLWD); submit UDH length 07, assert ESME_RINVUDHLEN.
  • Syniverse: GSM7 single=160/fragment=152 chars; UCS-2 single=70/fragment=67 chars; max 10 fragments; extended GSM chars (€, ^, |, etc.) count as 2 septets each — https://sdcsupport.syniverse.com/hc/en-us/articles/360010947674. Test: assert a message containing consumes 2 GSM7 code units for segmentation-count purposes.

7. Encodings

  • spec data_coding table: 0x00 SMSC Default Alphabet, 0x01 IA5/ASCII, 0x02 Octet unspecified (8-bit binary), 0x03 Latin-1, 0x04 Octet unspecified (8-bit binary) — spec itself lists 0x02 and 0x04 as duplicate "binary" entries; 0x05 JIS, 0x06 Cyrillic, 0x07 Latin/Hebrew, 0x08 UCS2, 0x09 Pictogram, 0x0A ISO-2022-JP, 0x0D Extended Kanji JIS, 0x0E KS C 5601, 0xCC0xDF GSM MWI control, 0xF00xFF GSM message class control (where flash/class-0 lives — the common "0x10/0x18" shorthand is a non-standard vendor simplification of this range) — spec via sysop.fr mirror. Test: verify the library picks one binary code (most vendors use 4, not 2) and documents the choice; verify flash handling covers the 0xF0-0xFF range, not just the two literal bytes 0x10/0x18.
  • spec esm_class UDHI bit 0x40 ("UDHI Indicator") — spec via sysop.fr mirror. Test: an 8-bit binary message (data_coding=4) carrying UDH must set esm_class 0x40; a decoder should flag UDH-shaped payload bytes arriving without this bit set.
  • Jasmin's coding parameter mirrors the spec's DCS table 1:1 (0=SMSC default…8=UCS2…13=Extended Kanji JIS, 14=KS C 5601) — https://docs.jasminsms.com/en/latest/management/jcli/modules.html. Test: round-trip all Jasmin-listed DCS values through the parser.
  • National language shift tables: not found documented in any researched source — unverified — folklore.
  • Sinch: data_coding 0x00 GSM-7 default, 0x01 US-ASCII, 0x02/0x04 binary, 0x03 Latin-1, 0x08 UCS2/UTF-16BE — but "only characters within the GSM-7 table can be parsed to handset" for DCS 3, i.e. Latin-1 bytes outside GSM-7 mis-render on many handsets — https://developers.sinch.com/docs/sms/smpp/message-encoding. Test: send data_coding=3 with non-GSM-7 chars, confirm mis-rendering is documented/expected, not a bug to "fix".
  • Infobip: DCS 0/1 = GSM7/IA5 default, 3 = Latin-1, 8 = Unicode/UCS-2; flash uses DCS 16 or 24 (both valid) — https://www.infobip.com/docs/essentials/api-essentials/smpp-specification. Test: submit flash with both 0x10 and 0x18, confirm both accepted.
  • Vonage: default encoding for new accounts is Latin-1 (ISO-8859-1), not GSM-7 — a real provisioning-default quirk; example DCS: Latin-1=3, Cyrillic=6, Hebrew=7, Unicode=8 — https://api.support.vonage.com/hc/en-us/articles/204015683 , https://api.support.vonage.com/hc/en-us/articles/204015813. Test: send GSM-7-only text on a fresh Vonage account without specifying data_coding, confirm it defaults to 0x03, not 0x00.
  • Bird: supported data_coding {0,1,2,8}; any other value received is remapped to the "most appropriate" of 0/2/8 before forwarding to the operator — https://developers.messagebird.com/api/sms-messaging/. Test: submit with data_coding=3, assert the outbound MT is remapped to one of {0,2,8}, not passed through verbatim.
  • Kaleyra: data_coding=0 GSM 03.38, =3 Latin-1, =8 expects UTF-16 Big Endian; data_coding=1 (ASCII) explicitly "NOT RECOMMENDED, known to cause problems" — https://messaging.kaleyra.com/support/solutions/articles/3000091796-submitting-messages-through-smpp. Test: submit UCS-2 as little-endian vs big-endian, confirm only big-endian round-trips; lint against data_coding=1.
  • LINK Mobility (Implementation Guide): data_coding table 0x00 default/GSM, 0x01 IA5→GSM, 0x02/0x04 binary, 0x03 Latin-1, 0x050x07 not supported, 0x08 UCS2; regardless of requested DCS, everything is transcoded into GSM or Unicode only before reaching the handset (arbitrary DCS is not preserved on the wire) — https://a.storyblok.com/f/151608/x/bb282a50a7/link-mobility-implementation-guide-smpp.pdf. Test: submit data_coding=3 (Latin-1), assert the network delivers transcoded GSM/Unicode, not raw Latin-1.
  • Telia: configurable default among GSM8/GSM7/Latin-1/Latin-9; UCS-2 for non-GSM content — https://cdn.messaging.teliacompany.com/documents/developer/index.html. Test: assert UCS-2 required/accepted for chars outside the GSM 7-bit default alphabet.
  • Telesign: data_coding 0/1 = GSM 03.38 default, 3 = Latin-1 (SMPP-only), 8 = Unicode/UTF-16BE; 0/1/3 "may result in ASCII encoding if the input stream can't be decoded" as GSM/Latin-1 — https://developer.telesign.com/enterprise/docs/smpp-protocol. Test: send non-GSM7-valid bytes with data_coding=0, confirm documented ASCII fallback rather than a hard failure.
  • Route Mobile: data_coding=0 default GSM 03.38; =1 also GSM 03.38 but explicitly flagged "NOT RECOMMENDED... known to cause problems"; =3 Latin-1 "if and only if told so explicitly"; =8 Unicode, body expected UTF-16 Big Endian — routemobile.com PDF. Test: assert the library warns against data_coding=1 and defaults new integrations to 0; assert UCS-2 bytes are big-endian.
  • Syniverse: customers submit UTF-8; the system auto-detects and, if any character isn't GSM-7, converts the entire message to UCS-2 (no partial/mixed encoding) — https://sdcsupport.syniverse.com/hc/en-us/articles/360010947674. Test: send one emoji mixed into otherwise-GSM7 text, confirm the whole message is segmented under UCS-2 limits, not GSM7's.

8. Addresses

  • spec TON table: 0 Unknown, 1 International, 2 National, 3 Network Specific, 4 Subscriber Number, 5 Alphanumeric, 6 Abbreviated. NPI table: 0 Unknown, 1 ISDN(E163/E164), 3 Data(X.121), 4 Telex(F.69), 6 Land Mobile(E.212), 8 National, 9 Private, 10 ERMES, 14 Internet(IP), 18 WAP Client Id — spec via sysop.fr mirror. Test: alphanumeric sender should be TON=5; the spec treats address as a C-octet digit string and doesn't mandate '+' handling — test both '+' and bare-digit forms are accepted.
  • Melrose Labs: destination_address <8 chars → ESME_RINVDSTADR — see topic 1. Test: useful edge case specifically for short-code-length destination addresses.
  • Kannel: default source-addr TON=2/NPI=1 — Kannel User's Guide 1.4.5 (see topic 1).
  • Sinch: alphanumeric sender TON=5/NPI=0, max 11 chars; MSISDN sender TON=1/NPI=1, max 18 chars; destination must be E.164 — https://developers.sinch.com/docs/sms/smpp/send-message. Test: submit a 12-char alphanumeric sender or non-E.164 destination, confirm rejection/truncation.
  • Infobip: shortcode→TON 3/NPI 0; alphanumeric→TON 5/NPI 0; "+"-prefixed→TON 1/NPI 1; else→TON 0/NPI 1; recommends TON/NPI=1 + E.164 as most reliable; address_range optional unless the account requires it — https://www.infobip.com/docs/essentials/api-essentials/smpp-specification. Test: submit each TON/NPI combo against its matching address shape, confirm acceptance; submit mismatched combos, check rejection vs silent tolerance.
  • Telnyx: bind requires addr_ton=1/addr_npi=1, fixed — https://support.telnyx.com/en/articles/1667062-short-message-peer-to-peer-set-up-guide. Test: bind with different values, confirm only 1/1 accepted.
  • Vonage: documented alphanumeric-sender example uses TON=5, NPI=1 — notably NPI=1, not the NPI=0 convention used by Infobip/Sinch for alphanumeric senders — https://api.support.vonage.com/hc/en-us/articles/204015853. Test: submit alphanumeric sender with NPI=0 vs NPI=1 against Vonage, confirm which the documented example actually expects (cross-vendor inconsistency worth its own regression test).
  • Kaleyra: TON 1=International MSISDN, 3=National/Network Short Code, 5=Alphabetic — https://messaging.kaleyra.com/support/solutions/articles/3000091796-submitting-messages-through-smpp. Test: submit ton=5 with an alphanumeric source_addr, confirm accepted.
  • Clickatell: TON/NPI "auto detected" rather than strictly validated; alphanumeric sender "not available on all networks" — downgraded/rejected per destination operator rather than at the SMPP layer — https://archive.clickatell.com/developers/api-docs/using-the-smpp-api/. Test: submit an alphanumeric sender to an operator that doesn't support it, confirm the failure surfaces as a per-operator DLR err, not an SMPP submit_sm_resp rejection.
  • LINK Mobility (Implementation Guide): source addr_ton restricted to 1 (MSISDN Int'l)/2 (national short code)/5 (alphanumeric); addr_npi ignored entirely — https://a.storyblok.com/f/151608/x/bb282a50a7/link-mobility-implementation-guide-smpp.pdf. Test: submit with an unlisted TON (e.g. 3), check rejection/fallback.
  • LINK Mobility (SMSC-SMPP User Guide): source TON options Alphanumeric(5)/International(1)/National(2)/Network-specific(3)/Subscriber(4)/Abbreviated(6), each NPI Unknown(0) or ISDN(1); destination restricted to International(1) TON onlyhttps://www.linkmobility.com/resources/developer/SMSC-SMPP-User-Guide-1.5.pdf. Test: submit a national-format (non-E.164) destination TON, expect rejection.
  • Telia: alphanumeric sender TON=5/NPI=0, max 11 chars; International TON=1/NPI=1; Short number TON=3/NPI=0 — https://cdn.messaging.teliacompany.com/documents/developer/index.html. Test: assert alphanumeric sender rejected/truncated beyond 11 chars.
  • Telesign: destination requires dest_addr_ton=1/npi=1; source addresses require prior whitelisting/approval before use — https://developer.telesign.com/enterprise/docs/smpp-protocol. Test: submit with dest_addr_ton=0, confirm rejection per this vendor's stated model.
  • Route Mobile: source_addr_ton Alphanumeric=5, National/Network Short Code=3, International MSISDN=1 (default); destination TON/NPI not enforced at all — "always interpreted as 1," destination must be international format without leading "00" — routemobile.com PDF. Test: submit dest_addr_ton=9 (garbage), confirm still accepted and treated as TON=1; submit a destination with leading "00", confirm rejection/mis-routing.
  • Syniverse: alphanumeric sender requires per-operator registration/approval (no TON/NPI published); short codes 56 digits — https://sdcsupport.syniverse.com/hc/en-us/articles/360019714593 , https://sdcsupport.syniverse.com/hc/en-us/articles/229476208. Test: validate short-code source addresses are 56 digits.

9. Time fields

  • Absolute format YYMMDDhhmmsstnnp: YY year, MM month, DD day, hh hour, mm minute, ss second, t tenths of a second, nn UTC time-differential in quarter-hour units, p = +/- — Kannel devel mailing list quoting the spec, https://www.kannel.org/pipermail/devel/2009-April/002384.html. Test: build an absolute validity_period string per this layout and confirm it parses.
  • Relative format: replace p with R; example 000000000500000R = "5 minutes validity from message receipt" — same source. Test: submit a relative validity_period, confirm the SMSC computes expiry from its own receipt time, not the client's clock.
  • Kannel real-world quirk: "will send validity in utc therefore 00+ is hardcoded" — Kannel never emits a non-zero UTC offset even though the format supports one — same Kannel devel-list source. Test: assert a test against Kannel doesn't expect timezone-aware absolute validity_period.
  • Sinch: schedule_delivery_time supports both absolute and relative; max 168 hours (1 week) ahead — https://developers.sinch.com/docs/sms/other/sms-other-cloud-smpp. Test: schedule >168h out, confirm rejection.
  • Infobip: relative format worked example "070605040302100R" = 7y 6mo 5d 4h 3m 2s 1 tenth-sec, relative — https://www.infobip.com/docs/essentials/api-essentials/smpp-specification. Test: submit that exact string in schedule_delivery_time/validity_period, confirm Infobip parses it as relative.
  • Telesign: validity_period is a 17-octet C-octet string; absolute form uses +/- UTC offset in quarter-hours as the last char; relative form uses R there instead with unused fields zeroed ("relative to current MC time") — https://developer.telesign.com/enterprise/docs/smpp-protocol. Test: build both absolute (+/-) and relative (R) strings per this layout, assert both parse.
  • Route Mobile / Syniverse: only document the error codes (ESME_RINVEXPIRY 0x62 for Route Mobile; 1045 "Invalid Validity Period"/1040 "Invalid scheduled delivery time" for Syniverse) — no accepted-format detail published — routemobile.com PDF; https://sdcsupport.syniverse.com/hc/en-us/articles/360038257473. Test: assert malformed validity_period strings trigger these vendor-specific error codes.
  • Clickatell: documents validity_period as a mechanism to stagger bulk-send delivery when the gateway has queueing delays — a throttling/scheduling tool, not just a TTL — https://archive.clickatell.com/developers/api-docs/validity-period-advanced-message-send/. Test: set a short validity_period on a message stuck behind a large queued batch, confirm it expires (stat:EXPIRED) rather than delivering late.
  • LINK Mobility (SMSC-SMPP User Guide): recommends validity_period ≥15 minutes; schedule_delivery_time is unsupported on submit_sm for this profile — https://www.linkmobility.com/resources/developer/SMSC-SMPP-User-Guide-1.5.pdf. Test: submit with schedule_delivery_time set, expect rejection/ignoring on this profile.
  • Absolute-vs-relative rejection rules for the remaining vendors (Bird, Clickatell beyond above, Kaleyra, tyntec, CM.com, Bandwidth): not publicly documented — unverified — folklore.

10. Error handling

  • spec generic_nack triggers: invalid command_length ("assume the data is corrupt... a generic_nack PDU must be returned") and unknown command_id ("a generic_nack PDU must also be returned") — spec via smpp.org PDF fetch. Test: send a PDU with a bogus command_id, assert generic_nack, not a silent drop or close.
  • spec session states OPEN/BOUND_TX/BOUND_RX/BOUND_TRX/CLOSED gate which PDUs are valid when; out-of-state PDUs aren't given an explicit error rule in the spec text itself, but ESME_RINVBNDSTS (0x00000004, "Incorrect BIND Status for given command") is the closest documented code — spec via smpp.org PDF fetch. Test: send deliver_sm on an unbound connection, expect ESME_RINVBNDSTS or a connection close, not silent processing.
  • spec outbind: lets the SMSC signal an ESME to originate a bind_receiver, typically because the SMSC has outstanding messages for it (§2.2.1) — spec via smpp.org PDF fetch. Test: simulate an SMSC-initiated outbind, assert the ESME responds by binding as receiver.
  • spec alert_notification: SMSC-issued in BOUND_RX/BOUND_TRX, and is the one PDU with no response — spec via smpp.org PDF fetch. Test: after receiving alert_notification, assert the ESME sends back no response PDU (unlike every other request).
  • spec data_sm: uniquely supports Transaction Message Mode; submit_sm does not support transaction mode (§2.10.2/2.10.3/§4.4) — spec via smpp.org PDF fetch. Test: assert the library rejects/flags an attempt to request transaction mode via submit_sm's esm_class instead of data_sm.
  • Whether submit_sm_resp with non-zero status still carries a body: not confirmed either way in the reachable spec text — unverified — folklore.
  • Vonage: command_status=0x00000005 (ESME_RALYBND) beyond the 2-connection default; 0x0d/13 decimal (ESME_RBINDFAIL) for wrong host/zone/creds; on deliver_sm, only status codes the spec's table marks "temporary failure" trigger a Vonage retry — everything else is final, no retry — https://api.support.vonage.com/hc/en-us/articles/204015713 , https://api.support.vonage.com/hc/en-us/articles/204015783 , https://api.support.vonage.com/hc/en-us/articles/204015773. Test: return each documented temporary vs non-temporary status in deliver_sm_resp, confirm retry only for the temporary set.
  • Bird: DLR text err: and network_error_code TLV both index into Bird's own proprietary 0131 error table, distinct from the underlying carrier's raw code — https://developers.messagebird.com/api/sms-messaging/. Test: table-driven test asserting err code → Bird meaning, kept separate from any carrier-code test.
  • Kaleyra: successful submit_sm_resp returns error code 0 + non-null message reference; failure returns "a Kaleyra vendor specific error code" — not necessarily a standard SMPP ESME_* value — https://messaging.kaleyra.com/support/solutions/articles/3000091796-submitting-messages-through-smpp. Test: assert error handling tolerates/logs unrecognized vendor codes rather than assuming the standard ESME_* enum is exhaustive.
  • LINK Mobility (Implementation Guide): generic_nack supported bidirectionally; client must handle SMSC-initiated unbind and respond before disconnecting, then wait ≥30s before reconnecting; outbind not mentioned (implicitly unsupported); ESME_RSYSERR(8)/ESME_RTHROTTLED(88) responses lack the vendor extension TLV even when the extension feature is enabled — https://a.storyblok.com/f/151608/x/bb282a50a7/link-mobility-implementation-guide-smpp.pdf. Test: assert reconnect within 30s of an unbind is refused; assert RSYSERR/RTHROTTLED responses lack the vendor extension even with it turned on.
  • Telenor: OUTBIND/SUBMIT_MULTI/ALERT_NOTIFICATION unsupported (see topic 1); GENERIC_NACK is in the supported-operations list — https://developer.telenor.no/images/97-322719.pdf.
  • Telia: ESME must send unbind before closing the TCP connection; SMSC can also unilaterally unbind for maintenance — https://cdn.messaging.teliacompany.com/documents/developer/index.html. Test: close TCP without unbind vs proper unbind, compare vendor-side session-cleanup expectations.
  • Route Mobile: rich vendor error-code extension beyond standard SMPP: ESME_CREDIT_ERROR 0x401, ESME_SPAM_MESSAGE 0x404, ESME_RINVSMLEN 0x405 (>160 char text message), ESME_RINVUDHLEN 0x406, ESME_RINSMSEMPTY 0x407, ESME_RINDSTDND 0x408 (destination in DND), 0x4090x412 (invalid source/template, long-message-template error, duplicate submission, destination/source barred) — routemobile.com PDF. Test: assert each vendor-extension code round-trips through generic error parsing without falling back to "unknown error."
  • Bandwidth: error ranges by origin — Bandwidth client 301-476, Bandwidth server 100-231, carrier client 700-795, carrier server 600-650, carrier ambiguous 902/999; 306 = registered_delivery out of range; 304-305 = invalid UDH; 720 = destination not in numbering plan — https://www.bandwidth.com/support/en/articles/12823160. Test: assert a registered_delivery value outside {0,1} on submission is flagged in the same class as Bandwidth's 306.
  • Syniverse: 1032 invalid registered_delivery, 1033 system error, 1034 invalid service_type, 1035 invalid command length, 1036 wrong state for command, 1037 invalid message id, 1038 outgoing queue full, 1039 throttling, 1041 submit failed, 1042/1043 invalid source/dest TON, 1048 invalid data_coding — https://sdcsupport.syniverse.com/hc/en-us/articles/360038257473. Test: checklist that the library independently validates/reports each of command_length, session state, message id, service_type, registered_delivery, TON, data_coding.

11. Framing / transport

  • spec §2.4: the underlying network connection is assumed to provide "reliable data transfer... including packet encoding, windowing, flow control and error handling," and segmentation/reassembly of PDUs across packets happens below the SMPP layer — spec via smpp.org PDF fetch. Test: feed a test harness PDUs deliberately split across multiple TCP writes and multiple PDUs coalesced into one read, assert the parser reassembles by command_length rather than assuming one PDU per read.
  • spec §3.1 C-octet String: "a series of ASCII characters terminated with the NULL character... empty strings encode as a single NULL octet (0x00)" — spec via smpp.org PDF fetch. Test: a field that's non-null-terminated or contains embedded NULs should be rejected/flagged by a strict parser.
  • spec PDU header: four 4-octet big-endian fields (command_length, command_id, command_status, sequence_number); command_id 0x000000000x000001FF requests / 0x800000000x800001FF responses; sequence_number 0x000000010x7FFFFFFF — spec via smpp.org PDF fetch. Test: fuzz a header with command_length smaller than actual PDU bytes present, confirm the generic_nack "invalid command_length" path (topic 10) fires.
  • sm_length=0 + message_payload combination is the standard way to carry >254-octet user data — spec via sysop.fr mirror (also topic 5/6). Test: assert a parser accepts short_message len 0 alongside a populated message_payload TLV.
  • Telia: requires SNI on all SMPP TLS connections, lists 14 supported TLS cipher suites, min TLS1.2, port 3550 — https://cdn.messaging.teliacompany.com/documents/developer/index.html. Test: attempt a TLS handshake without SNI, expect rejection.
  • LINK Mobility: TLS 1.2/1.3 required, ports 3600 (plain)/3601 (TLS), TLS1.0/1.1 discontinued since 2020-11-15 — https://www.linkmobility.com/resources/developer/SMSC-SMPP-User-Guide-1.5.pdf. Test: attempt TLS1.1, expect rejection.
  • Kaleyra: message_payload explicitly capped at 64K octets, gated behind interface_version=0x34 — https://messaging.kaleyra.com/support/solutions/articles/3000091796-submitting-messages-through-smpp. Test: send message_payload right at and just over 64K, confirm boundary behaviour.
  • Bird: plaintext SMPP on TCP 2775, TLS on TCP 2776 — https://docs.bird.com/connectivity-platform/other-integrations/how-can-i-use-the-short-message-peer-to-peer-smpp-network-protocol. Test: configure a mock SMSC on 2775/2776 to match a Bird-shaped client config.
  • No vendor among the ~20 researched publicly documents multi-PDU-per-TCP-segment behaviour, command_length-lies-about-actual-size handling, or enquire_link burst tolerance by name — this whole topic is essentially spec-only + implementation-defined across all vendors; treat the spec §2.4/§3.1 items above as the ground truth and the rest as unverified — folklore.

Top 10 quirks most likely to break a fresh implementation (ranked)

  1. Message ID base/field mismatch between submit_sm_resp and DLR id:. Kannel msg-id-type, Jasmin dlr_msgid, and Vonage's default hex-vs-decimal split all independently produce a submit_sm_resp id that doesn't string-match the DLR's id: field unless converted. A correlation layer that assumes "same string" will silently lose DLR-to-message mapping. (Kannel User's Guide; Jasmin docs; Vonage API Support.)
  2. esm_class DLR bit confusion: 0x04 (final receipt) vs 0x20 (intermediate) vs 0x00 (plain MO). A naive "esm_class != 0 means DLR" check misclassifies intermediate notifications and even non-DLR SME acks (0x08/0x10) as final delivery receipts. (SMPP 3.4 spec.)
  3. Long-message mechanism varies by vendor and none is universal. UDH-only (LINK Mobility, Vonage, Route Mobile via esm_class 0x43), sar_* TLVs (Clickatell, spec-native), and message_payload (Telia, Kaleyra — gated behind interface_version 0x34 — Route Mobile) are all real, mutually exclusive requirements. A library hardcoded to one mechanism won't interop with vendors requiring another. (Multiple vendor docs above.)
  4. UCS-2 byte order. Kaleyra and Route Mobile explicitly require UTF-16 big-endian; a JS/TS implementation defaulting to native (often little-endian) UTF-16 encoding silently corrupts every non-GSM message. (Kaleyra, Route Mobile docs.)
  5. Throttling semantics: ESME_RTHROTTLED (0x58) vs ESME_RMSGQFUL (0x14) need different backoff, and window/TPS limits vary 10x across vendors (2/s free SMPPSim vs 100+/s Infobip) with some vendors closing the socket instead of responding. A fixed retry policy will misbehave against most real SMSCs. (smpp.org error codes; LINK Mobility, Sinch, Vonage, Telia docs.)
  6. DLR text-body field variability is much wider than the "standard" format suggests. sub/dlvrd are hardcoded "000" and text always blank at LINK Mobility; stat vocabularies extend beyond the core 7 values (Vonage adds FAILED/DELETED, Infobip allows ENROUTE as a stat, Telesign adds SKIPPED). A strict fixed-format parser breaks on the first non-conforming vendor. (LINK Mobility, Vonage, Infobip, Telesign, smpp.org.)
  7. Concatenated SMS must stay on one session/server, and MO concat params can be absent. Vonage: all parts must go through the same SMPP server or the handset never reassembles, within a 20-minute submission window; MO concat TLVs are missing entirely for some US carriers, requiring a same-sender+timestamp heuristic. tyntec: without UDH, segment order isn't guaranteed at all. (Vonage, tyntec docs.)
  8. PDU framing over TCP is not 1:1 with read()/write() calls. The spec explicitly pushes segmentation/reassembly below the SMPP layer, but no vendor publishes concrete "multiple PDUs per segment" or "PDU split across segments" test guidance — a stream parser that assumes one PDU per socket read will break under real-world TCP behavior (Nagle, MTU, coalescing) the first time it hits production traffic. (SMPP 3.4 spec §2.4/§3.1.)
  9. Interface-version-gated features fail silently, not loudly. Binding at 0x33 instead of 0x34 silently disables message_payload/certain TLVs (Kaleyra, Route Mobile) or changes message_id format (Sinch legacy) rather than rejecting the bind — a library that doesn't track negotiated version per-session will send PDUs the far end simply ignores or misinterprets. (Kaleyra, Route Mobile, Sinch docs.)
  10. TON/NPI conventions for alphanumeric sender disagree across vendors. NPI=0 (Infobip, Sinch, Telia) vs NPI=1 (Vonage's own documented example) for the same TON=5 alphanumeric-sender case — a library validating "correct" TON/NPI combinations against one vendor's convention will reject valid submissions to another. (Infobip, Sinch, Telia, Vonage docs.)

Coverage notes

  • Kannel's dlr-mask (named in the brief) was searched for directly in the Kannel 1.4.5 User's Guide smsc smpp section and does not appear there — confirmed absent via targeted fetch, not merely unfound. It may exist under a different name/group in newer Kannel, or the brief may have conflated it with another gateway's option. Marked unverified — folklore rather than guessed.
  • Twilio confirmed to offer SMPP, but enterprise-gated only (no public/self-service target) — see topic 1.
  • Tele2 and Hi3G/Tre (3, Sweden) have no public SMPP interconnect technical spec discoverable in English or Swedish — explicitly no findings, not omitted by oversight.
  • Esendex's public docs describe only capability-level facts (multi-bind, separate in/out binds); no protocol-level parameters (TON/NPI, data_coding, DLR format) could be recovered — its SMPP developer sub-pages are JS-rendered and returned empty content to fetching.
  • Syniverse's raw SMPP bind-level technical manual is not publicly reachable; everything sourced instead comes from its SCG platform docs, which wrap SMPP in a REST layer but still publish SMPP-level status-code tables.
  • Telenor's two public PDFs are business/operational overviews confirming supported-vs-unsupported PDU types but not publishing message_id format, DLR text format, or TON/NPI tables.