Skip to main content

SMPP edge model

Orbit’s SMPP edge is a managed relay, not an open SMPP gateway you configure directly. The relay fronts Jasmin, an open-source SMS gateway, and everything you can configure on it — bind credentials, throughput caps, DLR delivery mode, upstream carriers — lives in your account configuration. A background reconciliation loop continuously applies that configuration to the relay, so the state your SMPP client observes is always a projection of what you set. The SMPP bind guide covers the client side: how to connect and send. This page covers the platform model underneath — who owns each piece of state, and why a status change you make takes up to ~30 seconds to take effect on the wire.

Why a relay sits in front of SMS

SMPP 3.4 is the interchange protocol aggregators and high-throughput senders already speak. Exposing it directly from a bespoke service would mean re-implementing a full SMPP server — session state, PDUs, delivery-receipt queues, multi-tenant isolation — before a single message moved. Jasmin already is a mature SMPP server, so Orbit runs Jasmin as the protocol endpoint and layers its multi-tenant model on top: each customer’s bind credentials become Jasmin users, and the boundary between tenants is enforcement Orbit applies on top of the credentials it issues. The bind endpoint at smpp.orbit.devotel.io speaks SMPP on exactly two ports (2775 plain TCP, 3550 TLS). Both terminate on the same Jasmin instance and take the same credentials. (The bind guide covers connectivity, including the connect-timeout behavior you need when the port is wrong.)

The credential-to-Jasmin-user model

Every SMPP credential you create — via POST /api/v1/messaging/smpp/credentials or the dashboard — becomes one row in smpp_credentials in your tenant schema (see Tenant isolation, Section 2, which lists smpp_credentials as a per-tenant table). The row carries the bind identity (systemId), the throughput cap (tpsLimit), an optional IP allow-list (allowedCidrs), the DLR delivery mode (dlrMode / dlrWebhookUrl), and a lifecycle status. Each such row maps to exactly one Jasmin user on the relay — the same id, so there is no mapping table to drift. The API never touches the relay directly; it writes the row, and the reconciliation loop (below) turns the row into the Jasmin user. Your system_id from the API response is the Jasmin username your client binds with. Because credentials live in your tenant schema and nowhere else, one tenant’s bind credentials are invisible to another tenant by construction — the same isolation rule the rest of your account data follows.

The reconciler contract

The relay’s configuration is owned by a reconciliation service that runs a pass every ~30 seconds. Each pass is one shot, with no long-lived in-memory state:
  1. Read the desired state — every tenant’s smpp_credentials rows (and any registered BYO-carrier rows).
  2. Diff against the relay — the pass lists what Jasmin currently holds and computes which users or connectors need to be added, updated, disabled, or removed.
  3. Apply the diff — provision new users for new credentials, update password/TPS/status on existing ones, and disable or remove users whose rows were suspended or revoked.
Three properties of that contract matter operationally:
  • status is reconciler-routed, not immediate. Flipping a credential to suspended in the dashboard or via PATCH writes the row; the next pass disables the Jasmin user. A status change takes effect on the wire within one reconciliation tick — budget a minute, not a millisecond, when you schedule a suspend/rotate sequence.
  • The pass is idempotent. The same diff applied twice is a no-op, and a pass interrupted mid-way is simply retried by the next one. Running two copies of the reconciler at the same time converges rather than conflicts.
  • The loop only manages what your configuration created. Binds and connectors the platform provisions for its own purposes — the inbound carrier trunks that receive your delivered-DLR and MO traffic, for example — are outside the reconciler’s diff, so no credential change you make can ever delete them.
The same contract governs the BYO-carrier side: registering an upstream carrier row (POST /api/v1/messaging/smpp/carriers) reconciles to a Jasmin smppc connector plus a routing (mtrouter) rule on the next pass, and soft-deleting it (DELETE) removes both. The DLR and MO gateway pipeline page covers what the loop stamps back onto your rows — bind counts and connector bind status — which is what the dashboard renders.

DLR delivery modes

Each credential decides where its delivery receipts leave Orbit. The dlrMode field is the whole contract: Two operational consequences:
  • bind mode can only deliver to a live bind. Receipts queued while no session is open wait for the next bind; if your integration needs receipts even when your SMPP client is down, use webhook or both.
  • Switching modes is a row update, reconciled like any other. A change to dlrMode or dlrWebhookUrl propagates on the next reconciliation pass, and receipts generated before the change still obey the mode in effect at generation time.
Per-credential tpsLimit and allowedCidrs ride the same path: the API writes the row; the reconciler applies the cap and the source-IP allow-list to the Jasmin user on the next pass.

BYO carrier model

Enterprise tenants with their own carrier agreements can register upstream SMSC connections (type: smpp or type: http) that Orbit routes outbound traffic through alongside its own carrier network. Routing per carrier row is declarative:
  • scopeall (the carrier can serve any outbound message) or by_country_mcc (only the MCC list in scopeFilter.mccs, up to 50).
  • priority — an integer where lower wins; two active carriers serving the same scope resolve by priority, not by registration order.
  • statusactive reconciles the connector and route in; inactive (the soft-deleted state) reconciles them out.
Because both the smppc connector and its routing rule are reconciler-managed, a POST /carriers followed one tick later by an outbound send is the normal flow — the send picks the connector up the moment the pass has run. Likewise, DELETE /carriers/{id} stops new sends from routing to the carrier after the next pass completes. In-flight submissions made before the pass are finished on the old route.

The status timeline a bind observes

From a connected SMPP client’s point of view, credential status changes show up as protocol behavior, always after one reconciliation tick at most: Revocation is therefore the strongest of the three: not only is bind access gone, the identity itself is removed from the relay, and POST /credentials/{id}/rotate on a revoked credential has nothing left to rotate against. Create a fresh credential instead.

Who owns which failure

When a bind or submit_sm fails, the actor that owns it follows the model: Anything not in the table — a credential that is active in the dashboard yet still rejected minutes after a status change, or a carrier whose bindStatus never flips — is signal to raise with support, since the reconciler itself converges within a minute under normal operation.
  • Connect via SMPP — the client-side guide: ports, creating a credential, DLR fields, and choosing SMPP vs REST.
  • SMPP API reference — the full /messaging/smpp endpoint surface (credentials + carriers).
  • Tenant isolation — where smpp_credentials lives and why that isolates your binds at the schema level.
  • DLR and MO gateway pipeline — how receipts and inbound messages fan out once they cross the gateway, and what the reconciler stamps back into your rows.