Current providers
We add providers as markets demand. On the Scale plan, you can request a specific provider preference (e.g. direct SMPP bind to MTN) — contact support.
How routing works
1
Country detected
From the
to number’s E.164 dial code.2
Chain resolved
We look up
provider_routes for that country plus the * global defaults. Country-specific rows go first; * rows fill the tail.3
Walk the chain
The worker tries each provider in order. Success → persist and fire
sms.sent.4
Failover on transient errors
5xx responses, timeouts, network errors → try the next provider. Every attempt is recorded.
5
Short-circuit on permanent errors
phone_invalid / dnd_blocked — failover wouldn’t help, and re-hitting the chain could look like spam. We stop immediately and mark rejected.Attempt history
Every send carries aprovider_attempts array showing what we tried:
Permanent vs transient errors
We classify errors so failover is smart, not noisy:Provider-specific webhook endpoints
Each provider has its own DLR webhook endpoint we expose:
In production both require a shared HMAC secret (
BEEM_DLR_SECRET / TERMII_DLR_SECRET). Timestamps older than 5 minutes are rejected as replays.
You don’t interact with these directly — they’re for the upstream providers to POST to. Your webhook (configured via /v1/webhooks) receives the correlated sms.delivered / sms.failed events.