better-smpp
Client

Reliability

Keepalive, send windows, throttling, congestion, and automatic reconnect.

Everything on this page is what separates a demo from an SMPP integration that survives production: link supervision, backpressure, and recovery.

TCP won't tell you an idle SMPP link died. Enable enquire_link keepalives:

new SmppClient({
  enquireLink: 30_000, // shorthand for { intervalMs: 30_000 }
  // or:
  enquireLink: { intervalMs: 30_000, timeoutMs: 10_000, maxMissed: 2 },
});

Unlike the original node-smpp — which sent pings but never noticed missing answers — unanswered keepalives count as misses; after maxMissed (default 1) the connection is declared dead, destroyed, and (if configured) reconnected.

Send window

SMSCs assume a bounded number of outstanding requests. Cap yours:

new SmppClient({
  windowSize: 10,        // max un-acked requests in flight
  windowQueueLimit: 500, // optional: fail fast beyond this backlog
});

Requests beyond the window queue FIFO and are released as responses arrive. enquire_link bypasses the window so keepalives always flow, even under full load. When windowQueueLimit is hit, requests reject immediately with SmppWindowFullError — your signal to shed load upstream.

Throttling

When the SMSC answers ESME_RTHROTTLED or ESME_RMSGQFUL, submits retry with exponential backoff (on by default):

new SmppClient({
  throttle: { maxRetries: 3, baseDelayMs: 1_000, maxDelayMs: 60_000 },
  // throttle: false  → propagate throttling errors immediately
});

client.on("throttled", (status, delayMs) => {
  metrics.increment("smpp.throttled");
});

After the retries are exhausted the original SmppCommandError propagates.

Congestion

SMPP v5.0 peers report load via the congestionState TLV (0–100). The client tracks it from every response:

client.congestionState;                 // last reported value
client.on("congestion", (value) => {    // fires on change
  if (value > 80) pauseCampaign();
});

Automatic reconnect + rebind

Opt in and the client owns the whole recovery story:

const client = new SmppClient({
  url: "smpp://user:pass@smsc.example.com",
  reconnect: true,
  // or: { maxRetries: Infinity, baseDelayMs: 1_000, maxDelayMs: 30_000, rebind: true }
});

client.on("reconnecting", (attempt, delayMs) => log.warn({ attempt, delayMs }));
client.on("reconnected", () => log.info("link restored"));

On unexpected connection loss the client:

  1. emits close, then schedules reconnect attempts with exponential backoff,
  2. re-establishes the transport (TCP or TLS),
  3. re-binds with the last successful bind credentials (rebind: true, default),
  4. emits reconnected (plus the usual bound).

Requests issued while the link is down wait for the reconnect instead of failing — in-flight requests at the moment of disconnect are rejected (they may have reached the peer; replaying them could double-send), but queued work resumes safely.

close() and destroy() never trigger reconnection — recovery only applies to failures.

Observability

Wire the hooks to your logging/metrics stack; both default to no-ops:

new SmppClient({
  logger: {
    warn: (msg, meta) => log.warn(meta, msg),
    error: (msg, meta) => log.error(meta, msg),
  },
  metrics: {
    increment: (name, value = 1) => statsd.increment(`smpp.${name}`, value),
    gauge: (name, value) => statsd.gauge(`smpp.${name}`, value),
  },
});

Counters include pdu.sent, pdu.received, request.timeout, request.throttled, reconnect, enquire_link.missed; gauges include window.in_flight, window.queued and congestion_state.

On this page