better-smpp
Core

Message utilities

Splitting, reassembly, and delivery-receipt parsing as standalone tools.

The client wires these in automatically; they're exported for server-side use, proxies, and anything custom.

splitMessage

Splits text into transmission-ready segments — encoding-aware (GSM escape pairs and UTF-16 surrogate pairs are never cut), with concat UDHs or SAR TLV values:

import { splitMessage } from "@better-smpp/core";

const split = splitMessage("a very long message…".repeat(30));
split.encoding;   // "ASCII"
split.dataCoding; // 1
split.reference;  // shared concat reference
split.segments;   // [{ text, udh: <Buffer 05 00 03 ref total seq> }, …]

// SAR TLVs instead of UDH:
splitMessage(text, { method: "sar" });
// segments: [{ text, sar: { sarMsgRefNum, sarTotalSegments, sarSegmentSeqnum } }, …]

// 16-bit references, forced encoding, fixed reference:
splitMessage(text, { wideRef: true, encoding: "UCS2", reference: 0x1234 });

Short text returns a single bare segment — safe to call unconditionally.

MessageReassembler

The inverse: feed it every inbound deliver_sm (or submit_sm on the server) and it returns the complete message once all segments arrived. Handles 8-bit and 16-bit concat UDHs and SAR TLVs, out-of-order and interleaved segments:

import { MessageReassembler } from "@better-smpp/core";

const reassembler = new MessageReassembler({ timeoutMs: 60_000 });

connection.on("deliver_sm", (pdu) => {
  const message = reassembler.add(pdu);
  if (!message) return; // waiting for more segments

  message.message;   // full text (or Buffer for binary)
  message.parts;     // segment count
  message.reference; // concat reference (absent for singles)
  message.pdus;      // underlying PDUs in sequence order
});

Stalled partial messages are evicted after timeoutMs (default 60 s); reassembler.pendingCount reports how many are in flight.

parseDeliveryReceipt

Turns receipt deliver_sm PDUs into typed objects. Understands the appendix-B text format and the receiptedMessageId / messageState TLVs (TLVs win):

import { parseDeliveryReceipt, isDeliveryReceipt } from "@better-smpp/core";

const receipt = parseDeliveryReceipt(pdu);
// undefined when the PDU isn't a receipt at all
if (receipt) {
  receipt.messageId;    // "abc123"
  receipt.state;        // "DELIVRD"
  receipt.messageState; // 2 (MESSAGE_STATE.DELIVERED)
  receipt.submitted;    // 1
  receipt.delivered;    // 1
  receipt.submitDate;   // Date (UTC)
  receipt.doneDate;     // Date (UTC)
  receipt.error;        // "000"
  receipt.text;         // first chars of the original message
  receipt.raw;          // unparsed source text
}

A PDU counts as a receipt when its esmClass has the receipt bits (MC_DELIVERY_RECEIPT, DELIVERY_ACKNOWLEDGEMENT, INTERMEDIATE_DELIVERY) or when receipt TLVs are present — isDeliveryReceipt(pdu) exposes the check.

On this page