Skip to main content

smpp-connector — Application Logic

Status: populated | Last updated: 2026-04-18

1. Use Cases

UC-01: Establish SMPP Session

Trigger: Service startup or reconnect timer fires after an UNBOUND event. Input: operatorId (configured via environment or dynamic operator list)

Steps:

  1. Call operator-management-service internal API: GET /internal/operators/{operatorId}/smpp-credentials
  2. Receive { host, port, systemId, password, tpsLimit, bindMode }.
  3. Open TCP socket to host:port.
  4. Send bind_transceiver PDU with systemId and password.
  5. Wait for bind_transceiver_resp (timeout: 10 s).
  6. On success: transition state to BOUND; publish operator.health event BOUND.
  7. On failure / timeout: increment reconnectAttempts; schedule reconnect with backoff; publish operator.health event UNBOUND if first failure.
  8. If MNO rejects bind_transceiver with error code ESME_RINVCMDID (not supported): retry with bind_transmitter + bind_receiver pair.

UC-02: Transmit SMS Dispatch Command

Trigger: NATS message received on smpp.operator.{operatorId}. Input: DispatchCommand

Steps:

  1. Validate DispatchCommand fields (non-empty to, text, valid operatorId).
  2. Check session state; if not BOUND → NAK the NATS message (requeue).
  3. Check TPS: call TpsThrottleService.isAllowed(operatorId, tpsLimit).
    • If rate limit exceeded → NAK with 500 ms delay (NATS NakDelay).
  4. Determine encoding: if text contains non-GSM7 characters → use UCS2 (0x08); otherwise GSM7 (0x00).
  5. If message text requires segmentation (> 160 GSM7 chars or > 70 UCS2 chars):
    • Apply longMessageStrategy (CSMS or TLV):
      • CSMS: split into segments with SAR UDH headers; send multiple submit_sm PDUs.
      • TLV: send single submit_sm with message_payload optional parameter.
  6. Send submit_sm PDU(s).
  7. Wait for submit_sm_resp (timeout: 30 s).
    • On ESME_ROK: extract message_id; write MessageCorrelation record to PostgreSQL.
    • On transient error (e.g. ESME_RTHROTTLED): NAK and requeue.
    • On permanent error (e.g. ESME_RINVDSTADR): ACK (do not requeue); publish sms.dlr.inbound with state UNDELIVERABLE.
  8. ACK NATS message.

UC-03: Receive DLR (deliver_sm)

Trigger: MNO sends a deliver_sm PDU containing a delivery receipt. Input: DeliverSmPdu (from SMPP session TCP buffer)

Steps:

  1. Parse deliver_sm PDU; extract receipted_message_id and message_state from UDH or short_message text.
  2. Look up MessageCorrelation by operatorMessageId = receipted_message_id AND operatorId.
  3. If correlation not found → log warn dlr.correlation.not_found; send deliver_sm_resp ESME_RMSGQFUL; discard.
  4. Map SMPP message_state enum to internal DlrMessageState.
  5. Publish sms.dlr.inbound NATS event with { messageId, operatorMessageId, status, operatorId, deliveredAt }.
  6. Send deliver_sm_resp ESME_ROK to MNO.
  7. Delete MessageCorrelation record (or mark as completed if audit retention required).

Trigger: 30 s interval timer per active SMPP session. Input: Active SmppSession

Steps:

  1. Send enquire_link PDU.
  2. Wait for enquire_link_resp (timeout: 10 s).
  3. On success: reset heartbeat timer; no action.
  4. On timeout / no response: transition session to UNBOUND; publish operator.health UNBOUND event; initiate reconnect sequence (UC-01).

UC-05: Primary/Backup Failover

Trigger: Primary operator session transitions to UNBOUND.

Steps:

  1. Mark primary operator as UNBOUND in session map.
  2. If a backup operator is configured for this operator:
    • Initiate SMPP session with backup operator (UC-01).
  3. Re-route pending NATS messages to backup operator subject smpp.operator.{backupOperatorId} — this is done by sms-orchestrator acting on the operator.health UNBOUND event from this service.
  4. When primary operator recovers (successful bind): publish operator.health FAILBACK event; sms-orchestrator and routing-engine will re-evaluate routing.

UC-06: TPS Throttling (Redis Sliding Window)

Trigger: Every submit_sm attempt.

Steps:

  1. Compute windowStart = Math.floor(Date.now() / 1000) (1-second window).
  2. Redis pipeline:
    INCR tps:{operatorId}:{windowStart}
    EXPIRE tps:{operatorId}:{windowStart} 2
  3. Read returned count from INCR.
  4. If count > tpsLimit → deny (caller NAKs NATS message).
  5. If count <= tpsLimit → allow.