better-smpp
Client

Receiving

Reassembled messages, structured delivery receipts, and manual acknowledgement.

Bind as a receiver or transceiver and the SMSC pushes deliver_sm PDUs at you. The client routes them into two high-level events, acknowledging each PDU automatically.

The message event

Ordinary mobile-originated messages arrive as AssembledMessage objects. Multipart messages (concat UDH or SAR TLVs) are reassembled — segments may arrive out of order or interleaved with other messages — and delivered once, complete:

client.on("message", (message) => {
  message.sourceAddr;      // "46709771337"
  message.destinationAddr; // your address
  message.message;         // full decoded text (or Buffer for binary payloads)
  message.parts;           // segments it arrived in
  message.pdus;            // the underlying PDUs, in sequence order
});

Incomplete multipart messages whose remaining segments never arrive are evicted after 60 seconds.

The receipt event

deliver_sm PDUs flagged as delivery receipts (esm_class receipt bits, or the receiptedMessageId/messageState TLVs) are parsed into typed objects instead:

client.on("receipt", (receipt, pdu) => {
  receipt.messageId;    // TLV preferred over the text `id:` field
  receipt.state;        // "DELIVRD" | "UNDELIV" | "EXPIRED" | …
  receipt.messageState; // numeric MESSAGE_STATE, when known
  receipt.submitted;    // sub:001 → 1
  receipt.delivered;    // dlvrd:001 → 1
  receipt.submitDate;   // Date (UTC)
  receipt.doneDate;     // Date (UTC)
  receipt.error;        // err:000
  receipt.text;         // first characters of the original message
  receipt.raw;          // the unparsed short-message text
});

Both the appendix-B text format (id:… sub:… dlvrd:… stat:… err:… text:…) and the receipt TLVs are understood; TLVs win when both are present.

Acknowledgement

By default every deliver_sm is answered with a success deliver_sm_resp after your event handlers run. To control the response yourself — for example to reject deliveries you failed to persist — disable auto-ack and respond from the raw event:

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

const client = new SmppClient({ autoAckDeliverSm: false, /* … */ });

client.on("deliver_sm", async (pdu) => {
  try {
    await store(pdu);
    await client.respond(pdu, { messageId: "" });
  } catch {
    await client.respond(pdu, {
      commandStatus: CommandStatus.ESME_RX_T_APPN, // temporary failure — SMSC retries
    });
  }
});

The raw deliver_sm event always fires (before message/receipt), so it also works for logging alongside auto-ack.

Other SMSC-initiated PDUs

client.on("alert_notification", (pdu) => { /* subscriber became reachable */ });
client.on("outbind", (pdu) => { /* SMSC requests a bind on this connection */ });

enquire_link pings from the SMSC are answered automatically by the connection layer.

On this page