Connection layer
SmppConnection — the shared transport session under client and server.
SmppConnection wraps a connected socket (net.Socket or tls.TLSSocket) with
everything SMPP needs above TCP. SmppClient and SmppServerSession are built on it;
use it directly for proxies and custom roles.
import net from "node:net";
import { SmppConnection } from "@better-smpp/core";
const socket = net.connect(2775, "smsc.example.com");
await new Promise((resolve) => socket.once("connect", resolve));
const connection = new SmppConnection(socket, {
requestTimeoutMs: 30_000,
windowSize: 10,
enquireLink: { intervalMs: 30_000 },
});What it does
- Framing — accumulates socket data, extracts complete PDUs (partial frames across reads and multiple frames per read both handled), decodes them through the codec.
- Sequence numbers — auto-assigned, wrapping at
0x7FFFFFFF; caller-provided sequence numbers are respected (proxy use). - Correlation —
request()returns a Promise resolved by the matching response's sequence number, regardless of response order. - Keepalive — optional
enquire_linktimer with dead-link detection; inbound pings are answered automatically. - Hygiene — unknown commands get
generic_nack (ESME_RINVCMDID); malformed bodies are nacked with the right status without killing the stream; an invalid frame header (the stream itself is corrupt) destroys the connection.
Requests and responses
// Request/response with timeout + cancellation:
const resp = await connection.request(
{ command: "submit_sm", destinationAddr: "…", shortMessage: "hi" },
{ timeoutMs: 5_000, signal: controller.signal },
);
// Answer an inbound request:
connection.on("deliver_sm", (pdu) => {
void connection.sendResponse(pdu, { messageId: "" });
});
// Fire-and-forget (responses, outbind, alert_notification):
await connection.send({ command: "alert_notification", /* … */ });request() rejects with SmppCommandError (non-zero status or generic_nack),
SmppTimeoutError, SmppAbortError, or SmppClosedError — see
Errors.
Events
Typed per-command events (deliver_sm, submit_sm, …) plus pdu (everything),
unknownPdu, send, stateChange, error, close.
Flow control
windowSize/windowQueueLimitbound outstanding requests (enquire_linkbypasses the window).pause()/resume()hold inbound dispatch — this is what the server uses while the bind hook authenticates.pendingCountreports awaiting responses.
State
The connection tracks sessionState (OPEN → BOUND_* → UNBOUND/CLOSED) but does
not enforce it — the client/server layers own transitions via setState(). Helpers
canTransmit, canReceive and isBound are exported for your own checks.