Designing a Global SMS Gateway: Multi-Carrier Routing Across the World
A complete, interview-ready system design walkthrough for building an SMS gateway that reliably delivers text messages to every country, routing intelligently through different telecom carriers and aggregators, at a scale of millions of messages per minute.
Introduction & History — SMS Never Died
Short Message Service, or SMS, was born in 1992 when the very first text message, “Merry Christmas,” was sent over the Vodafone GSM network in the United Kingdom. At that time nobody imagined that this simple 160-character protocol would still be carrying billions of messages a day more than three decades later. SMS survived the rise of smartphones, the explosion of messaging apps like WhatsApp and iMessage, and the arrival of rich communication services, because it has one property nothing else can match: it works on every phone, on every network, in every country, without needing an internet connection or an app installed.
An SMS gateway is the piece of infrastructure that sits between a business application and the telecom world. When a bank wants to send you a one-time password, when an airline wants to send a boarding gate change, or when a delivery company wants to tell you your package is arriving in ten minutes, that message does not travel directly from their server to your phone. It passes through an SMS gateway, which figures out which telecom carrier owns your phone number, which route offers the best price and reliability for that carrier at that moment, formats the message according to that carrier’s rules, submits it, and then tracks whether it was actually delivered.
Building this system for a single country is a moderately hard problem. Building it globally is a genuinely hard distributed systems problem, because every country has its own carriers, its own regulations, its own character encoding quirks, its own pricing, and its own failure patterns. A message to a number in Nigeria behaves completely differently from a message to a number in Japan or Germany. The gateway has to hide all of that complexity behind one simple API: send this message to this phone number.
To appreciate why this problem stays hard even for engineers who have already built large-scale web systems, it helps to compare SMS delivery to something more familiar, like delivering an email or a push notification. An email server can, in principle, deliver directly to any recipient’s mail server using a single open standard protocol, and a push notification travels through exactly one of a small handful of platform providers such as Apple or Google. SMS has neither of those luxuries. There is no single global SMS protocol that every operator agrees to speak with every other operator directly, and there is no small number of dominant gatekeepers. Instead, the industry is a patchwork of thousands of mobile network operators, each with their own commercial relationships, technical capabilities, and local regulatory obligations, and an SMS gateway’s entire value proposition is absorbing that patchwork so that a client application never has to think about it.
“Why can’t we just call one telecom API and be done with it?” The answer is that no single telecom operator has direct interconnection agreements with every carrier on earth. Real-world SMS delivery happens over a global web of bilateral and multilateral agreements between mobile network operators, aggregators, and hubbing providers. A gateway’s core job is to intelligently choose the best path through this web for every single message.
Requirements Gathering — Functional, Non-functional, Scale
2.1 Functional Requirements
Accept submissions
Accept a message submission from a client application through an API, containing the destination phone number, sender ID, message body, and optional metadata.
Country & carrier detection
Determine the destination country and carrier from the phone number, accounting for portability where the number may no longer belong to its original prefix owner.
Best-route selection
Select the best available route (carrier or aggregator) for that destination based on cost, quality, and current health, then translate the message into the exact protocol and encoding format that route expects.
Track and report
Submit the message, track its status through the carrier’s delivery pipeline, and report delivery receipts back to the client — queued, sent, delivered, failed, expired.
Automatic failover
Retry failed messages through alternate routes automatically, following business rules and bounded retry limits so that time-sensitive traffic does not expire under the wrong route.
Long messages & 2-way
Support long messages that must be split into multiple SMS segments and reassembled correctly on the handset, plus two-way messaging where inbound replies must be routed back to the correct client application.
2.2 Non-Functional Requirements
- Handle a global scale of tens of millions of messages per minute during peak events such as OTP surges, festival greetings, or election result notifications.
- Deliver time-sensitive messages such as one-time passwords within a couple of seconds end to end wherever technically possible.
- Guarantee at-least-once delivery attempts, meaning a message should never silently disappear without an attempt or a clear failure reason.
- Be highly available, with no single point of failure, and continue operating even if an entire carrier route or an entire data center becomes unavailable.
- Be horizontally scalable so capacity can grow by adding machines rather than redesigning the system.
- Comply with regional regulations such as sender ID registration rules, opt-out laws, and data residency requirements.
- Be cost-aware, since different routes to the same destination can vary in price by an order of magnitude.
2.3 Scale Estimation
Imagine this gateway serves a large fintech, an e-commerce marketplace, and several airlines as clients, spread across two hundred countries. If average daily volume is two billion messages, that averages to about twenty-three thousand messages per second, but real traffic is bursty. Diwali greetings, Black Friday order confirmations, or a national election can push instantaneous load to ten or twenty times the average, meaning the system must be designed for millions of messages per minute at peak, not just at average. Each message is small, typically under a kilobyte, but the metadata, retries, delivery receipts, and audit logs multiply the actual data volume that must be stored and indexed many times over.
High-Level Architecture & Components
At the highest level, the SMS gateway is a pipeline. A message enters through an API layer, gets validated and normalized, gets classified by destination, gets a route chosen for it, gets dispatched to that route’s carrier connector, and then its final status is tracked and reported back. Every one of these stages is its own independently scalable service, connected by durable message queues so that a slowdown in one stage does not immediately cascade into data loss upstream.
3.1 API Gateway
This is the front door for every client application. It handles authentication using API keys or OAuth tokens, enforces per-client rate limits so that one noisy client cannot starve others, performs basic schema validation, and forwards accepted requests into the internal pipeline. It also exposes a status query endpoint and a webhook registration mechanism so clients can be notified asynchronously when a delivery receipt arrives.
3.2 Validation Service
This service rejects malformed requests early, before they consume any expensive downstream resources. It checks that the phone number is structurally plausible, that the message body does not exceed allowed length after considering encoding, that the sender ID is registered for the destination country if required, and that the client has not exceeded contractual sending limits.
3.3 Normalization Service
Phone numbers arrive from clients in wildly inconsistent formats: with dashes, spaces, leading zeros, or missing country codes. The normalization service converts every number into the E.164 international format, a single unambiguous representation such as plus, country code, then subscriber number. This normalized form is what every downstream service relies on.
3.4 Routing Engine
This is the brain of the system, covered in depth in its own section below. It looks up which country and carrier a number belongs to, consults a routing table of available carrier connections for that destination, and picks the best one based on cost, current success rate, and latency.
3.5 Durable Message Queue
Once a route is chosen, the message is written to a durable, partitioned queue rather than being dispatched synchronously. This decouples the rate at which messages are accepted from the rate at which a particular carrier can actually process them, and it means a message is never lost even if a dispatch worker crashes mid-flight.
3.6 Dispatch Workers and Carrier Connectors
Dispatch workers pull messages off the queue and hand them to the appropriate carrier connector. Because different carriers speak different protocols, connectors are built per protocol family: SMPP for traditional bulk SMS binding, HTTP REST APIs for modern aggregators, and in some legacy or direct-interconnect cases, SS7 or Diameter signaling gateways for direct operator connections.
3.7 Delivery Receipt Handler
Carriers asynchronously report back whether a message was delivered, failed, or is still pending. This component listens for those receipts, correlates them back to the original message using a unique message identifier, updates the stored status, and notifies the client through a webhook or lets the client poll for it.
Internal Working, Step by Step
Walking through the life of a single message end to end makes the architecture concrete.
- Submission. A client application calls the send-message API with a destination number, a message body, and a sender identifier. The API gateway authenticates the request and checks the client’s rate limit bucket.
- Validation and normalization. The number is checked for structural validity and converted to E.164 format. The message body is checked for length and for whether it contains characters outside the standard GSM-7 alphabet, which affects how many segments it will need.
- Country and carrier identification. The normalized number’s country code and, where possible, its mobile network code are extracted, usually with the help of a Home Location Register lookup service or a maintained numbering plan database, since numbers can be ported between carriers.
- Route selection. The routing engine consults its routing table for that country and carrier combination, filters out any route currently marked unhealthy, and selects the best candidate using a scoring function that blends cost, recent delivery success rate, and latency.
- Persistence and enqueue. The message, along with its chosen route and a unique message ID, is written to the database for tracking and pushed onto the durable queue partition associated with that route or carrier.
- Dispatch. A worker consuming that partition picks up the message, encodes it in the exact binary or textual format the connector requires, and submits it to the carrier or aggregator, typically over a persistent SMPP session or an HTTPS call.
- Carrier acknowledgment. The carrier immediately acknowledges that it accepted the message for processing. This is not the same as delivery. It only means the carrier has taken responsibility for attempting delivery to the handset.
- Delivery attempt inside the telecom network. The carrier’s own network then attempts to deliver the message to the handset, which may involve locating the subscriber’s current cell tower, checking whether the handset is reachable, and queuing the message in a store-and-forward center if the phone is temporarily offline.
- Delivery receipt. Once the carrier’s network confirms delivery, or gives up after its own retry policy, it sends a delivery receipt back to the gateway’s delivery receipt handler, which updates the message status and notifies the client.
- Retry or failover. If the chosen route fails or times out, the routing engine’s failover logic selects an alternate route and repeats dispatch, up to a configured retry limit, before finally marking the message failed.
“What’s the difference between the carrier accepting a message and the message being delivered?” This distinction trips up many candidates. Acceptance means the telecom operator has taken custody of the message and is responsible for it now. Delivery means the message actually reached the handset’s inbox. A gateway must track both states separately, because a message can be “accepted” for hours while the recipient’s phone is switched off, and only later either deliver or expire.
Carrier Routing — The Heart of the System
Routing is what makes this system fundamentally different from a normal request-response web service. For any given destination number, there are usually multiple possible paths the message could take: a direct connection to the local carrier, several third-party aggregators who resell access to that carrier, and sometimes multiple aggregators who each have different pricing and different reliability at different times of day.
5.1 Building the Routing Table
The gateway maintains a routing table keyed by country code and, when available, mobile network code, mapping to an ordered or scored list of possible routes. This table is not static. It is continuously updated from several sources: commercial agreements with new aggregators, periodic test messages sent to measure real-world delivery success and latency per route, and live health signals from the dispatch layer itself.
5.2 The Least Cost Routing Algorithm
Historically, the dominant algorithm in this industry is called Least Cost Routing, where the system simply always picks the cheapest available route for a destination. This is attractive financially but dangerous if applied blindly, because the cheapest route is very often the least reliable one, especially in countries with aggressive gray-route filtering by local operators. A mature gateway instead uses a weighted scoring function that considers cost alongside recent success rate and latency, something closer to Least Cost Routing with a quality floor: never route through a path whose recent delivery success rate has fallen below an acceptable threshold, regardless of how cheap it is.
5.3 Route Scoring Formula
A practical scoring approach computes, for every candidate route to a destination, a composite score built from three normalized signals: the recent delivery success rate over a sliding window such as the last thousand messages or the last fifteen minutes, the average end-to-end latency for that route, and the per-message cost. Weights are tuned by business priority; for OTP traffic, success rate and latency dominate the formula, while for bulk marketing traffic, cost may dominate as long as a minimum quality bar is met. Routes are re-scored continuously as new delivery receipts arrive, so the system adapts within minutes if a carrier’s quality degrades.
5.4 Number Portability and HLR Lookups
In many countries, subscribers can switch carriers while keeping their original number, a feature called mobile number portability. This means the country code alone is not enough to know the actual current carrier; the original numbering block prefix might suggest one carrier while the number has actually been ported to a competitor. Gateways solve this using a Home Location Register lookup, a real-time query to a signaling network that returns the subscriber’s actual current serving carrier. Because HLR lookups have a cost and add latency, gateways typically cache results for a period of hours to days and only re-query when a delivery failure suggests the cached carrier information might be stale.
5.5 Failover and Retry Strategy
When a dispatch attempt fails or times out, the system does not simply retry the same route. It marks that specific attempt as failed, decrements that route’s live health score, and re-runs the route selection logic excluding the failed route, so the retry goes out over a genuinely different path. Retries are bounded, both in count and in total elapsed time, especially for time-sensitive traffic like OTPs, where a message delivered ten minutes late is often as useless as one never delivered at all.
“How would you prevent retry storms from overwhelming an already struggling carrier connection?” The answer combines exponential backoff between retry attempts, a circuit breaker per route that stops sending new traffic to a route once its failure rate crosses a threshold, and a bulkhead pattern that isolates queue partitions per route so that one carrier’s outage cannot consume the shared worker pool needed by healthy routes.
5.6 Choosing Between SMPP, HTTP APIs, and Signaling Gateways
The routing engine’s choice of route is not only about which carrier to use but also about which protocol connects to that carrier, and this choice has real operational consequences. SMPP, short for Short Message Peer-to-Peer protocol, is a binary protocol originally designed in the 1990s for bulk SMS interconnection, and it remains the dominant protocol for high-volume aggregator and carrier connections today because of its efficiency and its native support for delivery receipts and long-lived sessions. Modern aggregators increasingly also expose simple HTTPS REST APIs, which are easier to integrate and debug but typically carry more per-message overhead and lower maximum throughput per connection than a well-tuned SMPP session. Direct interconnection using SS7 or its modern successor Diameter is reserved for the very largest volumes and the most latency-sensitive traffic, such as national telecom operators connecting to each other directly, because it requires deep integration with the carrier’s own signaling infrastructure and heavy regulatory oversight. A mature routing table therefore does not just record which carrier serves a destination, it records which protocol variant of that carrier connection is healthiest right now, since the same underlying carrier might be reachable through more than one of these protocol paths simultaneously.
5.7 Handling Long Messages and Encoding
A standard SMS holds 160 characters when using the GSM-7 alphabet, but only 70 characters if the message contains any character requiring Unicode, such as emoji or many non-Latin scripts. Longer messages are automatically split into multiple linked segments using a concatenation header, and the routing engine must ensure all segments of one logical message travel through the same route so the carrier’s own reassembly on the handset works correctly. Choosing the correct encoding per destination is itself a routing-adjacent decision, because some legacy carrier connections silently corrupt certain Unicode ranges, so the routing table also tracks encoding compatibility per route.
Algorithms, Data Structures & Concurrency
Underneath the service boundaries described above, several concrete algorithmic and concurrency choices determine whether this system can actually sustain millions of messages per minute without falling over.
Partition assignment
When the message queue is partitioned by destination carrier, the mapping from carrier to partition should not be a simple modulo operation over a fixed partition count, because adding or removing partitions as traffic grows would then reshuffle almost every carrier’s assignment at once, causing a wave of cache misses and connection churn. Consistent hashing places both partitions and carriers on a hash ring, so that adding a new partition only moves a small fraction of carriers to it, leaving the rest undisturbed.
Transactional lanes
Not all messages are equal. A one-time password needs to jump ahead of a bulk marketing blast queued moments earlier. Rather than a single FIFO queue, the dispatch layer uses multiple priority lanes with weighted consumption, so worker pools pull disproportionately more often from the high-priority transactional lane while still making steady progress on the bulk lane, avoiding starvation.
Health scoring
A route’s live success rate is computed using a sliding window counter data structure, typically a ring buffer of fixed-size time buckets where each bucket independently tracks successes and failures and old buckets are discarded as time moves forward. This gives an accurate, memory-bounded, constantly refreshing view of recent route quality without scanning growing history on every score.
Rate limiting
Both per-client fairness and per-carrier throughput caps are enforced using the token bucket algorithm, where tokens are added at a steady rate representing allowed sustained throughput, and each message consumes one token, while a bucket’s maximum capacity allows short bursts above the steady rate without immediately rejecting traffic — a match for the naturally bursty nature of real-world messaging campaigns.
Retry pacing
When a dispatch attempt fails and a retry is scheduled, the delay grows exponentially with each successive failure on the same logical message, and a small random jitter is added so that many messages failing around the same moment — for example during a brief carrier blip — do not all retry in the exact same instant and create a synchronized retry storm against a carrier that is only just recovering.
Session concurrency
An SMPP session supports a sliding window of in-flight requests, meaning multiple messages can be submitted before earlier acknowledgments arrive, up to a carrier-negotiated window size. Dispatch workers manage this using a bounded semaphore per session, acquiring a permit before submission and releasing it upon acknowledgment, which maximizes throughput per session while respecting the carrier’s own concurrency limits.
Data Flow & Message Lifecycle
Every message in the system moves through a well-defined set of states, and the gateway persists that state transition history both for operational visibility and for client-facing status queries.
| State | Meaning |
|---|---|
| Received | The API accepted the request and it passed validation. |
| Queued | A route has been chosen and the message is waiting for a dispatch worker. |
| Submitted | The message was handed to the carrier connector and sent over the wire. |
| Accepted by Carrier | The carrier acknowledged receipt and responsibility for the message. |
| Delivered | The carrier confirmed the handset received the message. |
| Failed | The carrier reported a permanent failure, such as an invalid or blocked number. |
| Expired | The message exceeded its validity window, often because the handset stayed unreachable too long. |
| Retrying | An attempt failed and the routing engine has selected an alternate route. |
This asynchronous, event-driven flow is essential at scale. If the API gateway waited synchronously for a real delivery confirmation before responding to the client, a single slow carrier could stall thousands of client-facing requests. Instead, the client gets a fast acknowledgment that the message was accepted into the pipeline, and the actual delivery outcome arrives later through a webhook or a status poll.
Databases, Caching & Storage Design
8.1 Message Store
The message store needs to handle an extremely high write throughput, since every message generates at least two writes, one at submission and one at status update, plus additional writes for every retry attempt. A wide-column or document-oriented database, sharded by a combination of client identifier and time bucket, works well here because it scales writes horizontally and supports the range queries clients need when checking recent message history. A relational database can still work for smaller deployments but becomes a bottleneck at true global scale without heavy partitioning.
8.2 Sharding Strategy
Sharding by destination country would concentrate all traffic to a huge market like India or the United States onto a small number of shards, creating hot spots. A more balanced approach shards by message ID hash or by client plus time bucket, spreading load evenly, while a secondary index, often maintained in a search-optimized store, supports the country and carrier-based analytical queries the operations team needs for monitoring route health.
8.3 Routing Table Cache
The routing table itself must be read on the hot path for every single message, so it cannot live only in a database that requires a network round trip per lookup. It is kept in a distributed in-memory cache, replicated close to every routing engine instance, refreshed incrementally as health scores change, with the full authoritative table persisted in a database for durability and audit purposes.
8.4 Delivery Receipt Correlation Store
Delivery receipts often arrive minutes or, in some countries, hours after submission, and they must be matched back to the original message using a carrier-assigned reference ID. This correlation mapping is stored in a fast key-value store with a time-to-live matching the maximum validity period of a message, so the system does not accumulate stale correlation entries forever.
8.5 Rate Limiting Store
Per-client and per-route rate limits are enforced using a fast in-memory counter store supporting atomic increment operations, typically implemented with a sliding window or token bucket algorithm, since this needs to answer allow-or-deny decisions in well under a millisecond on the hot path.
8.6 Analytics and Reporting Store
Beyond the operational message store, business and operations teams need aggregated views: delivery success rate trends by country over the last month, cost breakdowns by client, or a comparison of route performance across aggregators. Rather than running these heavy analytical queries against the same live database handling real-time traffic, delivery events are streamed into a separate analytical data store built for large aggregations, keeping the operational path fast while still giving stakeholders the reporting depth they need without risking contention on the hot path.
| Store | Access pattern | Technology fit | Why |
|---|---|---|---|
| Message store | High-write, range queries by client/time | Wide-column / document, sharded | Horizontal writes; time-based reads |
| Routing table cache | Sub-ms lookups on every message | Distributed in-memory + pub-sub refresh | Hot path can’t tolerate DB round trip |
| Delivery receipt correlation | Key-value w/ TTL matching validity | KV store (Redis-like) | Ephemeral correlation, auto-expiry |
| Rate limiting | Atomic increments, sub-ms | In-memory counter (token bucket) | Allow/deny in <1 ms on hot path |
| Analytics | Heavy aggregations by country/time | Columnar analytical store | Isolated from real-time contention |
8.7 Replication and Consistency Considerations
The message database is typically configured with synchronous replication to at least one nearby replica for durability, so that a single node failure does not lose recently written message state, combined with asynchronous replication to a geographically distant replica for disaster recovery. This mirrors a common real-world pattern of trading a small amount of write latency for strong durability guarantees on the primary write path, while accepting slightly stale reads on the distant replica since that replica is only consulted during a regional failover, not during normal operation.
“Why not just use one database for everything?” Because the access patterns are wildly different: routing table lookups need sub-millisecond in-memory reads on every message, message history needs durable, queryable, high-write-throughput storage, and rate limiting needs atomic counters with automatic expiry. Forcing all three onto one storage engine either sacrifices latency, durability, or operational simplicity.
APIs & Microservices Design
The system is decomposed into independently deployable services communicating through well-defined APIs and asynchronous events, rather than one large monolith, because different components have very different scaling and reliability characteristics.
9.1 Client-Facing API
The public API exposes operations to send a message, check its current status, register a webhook callback URL, and manage sender identities. It is versioned so that changes in message formatting rules or new features do not break existing client integrations.
9.2 Internal Service Boundaries
Validation Service
Stateless, purely functional checks, easy to scale horizontally by simply adding instances. Rejects bad input long before it consumes routing or dispatch capacity.
Routing Service
Reads the routing cache, computes scores, and returns a chosen route; must be extremely low latency since it sits on every message’s critical path.
Dispatch Service
Owns the carrier connectors, manages persistent protocol sessions, and is scaled per carrier or per region since connection limits are often carrier-imposed.
Delivery Receipt Service
Ingests inbound callbacks from carriers, which can arrive as webhooks, SMPP delivery receipt PDUs, or polling responses depending on the carrier’s own capabilities.
Number Intelligence Service
Owns HLR lookups, portability data, and country and carrier classification, exposed as an internal lookup API consumed by the routing service.
9.3 Communication Patterns
Synchronous calls, typically over gRPC for internal service-to-service communication, are used only where a fast answer genuinely blocks the next step, such as the routing decision. Everything else flows through asynchronous, durable messaging, which is what allows the dispatch layer to absorb bursts without dropping the load onto the client-facing API.
Performance & Scalability
Handling millions of messages per minute requires scaling at every layer independently, because bottlenecks in this kind of system rarely show up where you expect them.
10.1 Horizontal Scaling of Stateless Services
The API gateway, validation service, and routing service are all stateless and can scale horizontally behind a load balancer simply by adding more instances, with auto-scaling triggered by request queue depth or CPU utilization rather than raw request count, since routing computations are more CPU-intensive than simple validation.
10.2 Partitioned Queues per Route
The durable message queue is partitioned, often by destination carrier or country, so that dispatch workers for one carrier can be scaled independently of workers for another. This also naturally implements the bulkhead pattern: if one carrier’s connection is slow or degraded, only its dedicated partition backs up, while messages destined for healthy carriers continue flowing normally.
10.3 Connection Pooling for Carrier Protocols
Many carriers use the SMPP protocol, which relies on a small number of long-lived, authenticated binding sessions per account rather than opening a new connection per message. Dispatch workers must pool and share these sessions efficiently, implementing windowing so multiple messages can be in flight on one session simultaneously up to the carrier’s allowed concurrency, rather than waiting for each message’s acknowledgment before sending the next.
10.4 Caching Hot Routing Data
Since the routing table is read on every single message, it is cached in memory on every routing service instance and updated through an efficient pub-sub invalidation mechanism whenever a route’s health score changes, avoiding a database round trip on the hot path entirely.
10.5 Batching Where the Protocol Allows
Some aggregator APIs support submitting a batch of messages in a single HTTP call. Where this is available, dispatch workers batch messages destined for the same route within a small time window, trading a few milliseconds of added latency for a meaningful reduction in network overhead and API call costs at very high volume.
A large messaging platform like Twilio operates its own Super Network, an abstraction layer over hundreds of direct carrier connections and aggregator relationships worldwide, continuously monitoring route quality and automatically shifting traffic away from underperforming paths, which is conceptually the same routing engine and health-scoring approach described above, just operated at an enormous scale across many countries simultaneously.
High Availability & Reliability
11.1 No Single Point of Failure
Every stateful component is replicated. The message database uses multi-node replication with automatic failover. The routing cache is distributed across multiple nodes so the loss of one cache instance does not blank out routing decisions. Dispatch workers for any given carrier run as a pool across multiple availability zones so a zone outage does not sever that carrier connection entirely.
11.2 CAP Theorem Trade-off
This system leans toward availability and partition tolerance over strict consistency for most of its state. If two routing service instances momentarily disagree about a route’s exact health score because of replication lag, that is an acceptable, self-correcting inconsistency, since the score updates continuously anyway. What must remain strongly consistent is the record of whether a specific message has already been dispatched, to avoid accidentally sending duplicate messages, which is why message state transitions are guarded by idempotency keys and atomic conditional writes rather than relying on eventual consistency.
11.3 Idempotency
Every client request carries an idempotency key, and every dispatch attempt is tagged with a unique attempt ID. If a client’s network call times out and it retries the same send request, the gateway recognizes the duplicate key and returns the original result rather than sending the message twice. Internally, if a dispatch worker crashes after submitting to the carrier but before recording that submission, an idempotent recovery process reconciles by checking the carrier’s own record before resubmitting, avoiding a costly double-send to the end user.
11.4 Circuit Breakers per Carrier Route
Each carrier connection is wrapped in a circuit breaker. If the failure rate on a route crosses a threshold within a short window, the breaker opens, and the routing engine’s health score for that route drops sharply, causing new messages to be routed elsewhere automatically. The breaker periodically allows a small trickle of test traffic through to detect recovery, and closes again once the route proves healthy.
11.5 Disaster Recovery
The system runs across multiple geographic regions. Configuration, routing tables, and client account data are replicated across regions, and DNS-based or anycast traffic steering can redirect client traffic to a healthy region if an entire region becomes unavailable. Regular backups of the message store, combined with tested restore procedures, protect against data loss from catastrophic failures rather than relying on replication alone.
“How do you prevent sending the same OTP twice to a user if a retry happens after the carrier already delivered it?” This is exactly the idempotency and reconciliation problem above: the system must distinguish between a delivery confirmation that has not yet arrived and an actual failure, and should query the carrier’s own status before blindly resubmitting on ambiguous timeouts.
Security
12.1 Client Authentication and Authorization
Every client integration uses scoped API credentials, ideally short-lived tokens rather than long-lived static keys, with per-client permissions controlling which sender IDs, countries, and message types they are allowed to use, since a compromised credential should have a limited blast radius.
12.2 Protecting Against Abuse and Fraud
SMS gateways are a frequent target of a fraud pattern called artificially inflated traffic, where bad actors exploit an application’s own SMS verification flow to generate traffic to premium-rate numbers they control, profiting from the termination fees. Defenses include velocity checks on how quickly a single account or IP can trigger sends to new numbers, anomaly detection on destination country distribution shifts, and mandatory CAPTCHA or device attestation upstream in the client application before an OTP is even requested.
AIT fraud can burn seven-figure carrier bills in a single weekend if left unchecked. Velocity limits, destination-country anomaly detection, and upstream CAPTCHA or device attestation must be layered together — no single control catches every variant of this attack pattern.
12.3 Content and Sender ID Validation
Many countries legally require sender IDs to be pre-registered, and message content is sometimes filtered for restricted categories such as gambling or unregistered marketing content, which the validation service enforces before a message ever reaches the routing engine, since violating local regulations can result in that carrier connection being suspended entirely.
12.4 Encryption in Transit and at Rest
All client-facing API traffic uses TLS. Message bodies at rest are encrypted, particularly important since SMS content frequently includes sensitive information like one-time passwords, banking alerts, or medical appointment reminders. Carrier connections using SMPP, an older protocol not designed with modern security in mind, are tunneled over TLS or run over private, dedicated network links rather than the public internet wherever possible.
12.5 PII Handling and Data Residency
Phone numbers and message content are personally identifiable information, and several jurisdictions require that data about their citizens be stored within that country’s borders. The system’s data layer must support per-region storage partitioning to satisfy these data residency rules rather than assuming a single global database is acceptable everywhere.
12.6 Opt-Out and Consent Management
Most countries have legal frameworks requiring recipients to be able to stop receiving messages by replying with a standard keyword, and the gateway must treat these opt-out replies as a first-class signal, immediately suppressing future sends to that number for that sender until consent is re-established, since ignoring an opt-out request is both a regulatory violation and a fast path to a carrier blacklisting the sender ID entirely.
12.7 Protecting Carrier Credentials
The credentials used to authenticate with each carrier connection, whether SMPP bind credentials or aggregator API keys, are sensitive secrets whose leakage could allow an attacker to send fraudulent messages that bill directly against the gateway operator’s account. These are stored in a dedicated secrets management system with strict access controls and automatic rotation, never embedded directly in application configuration files or source code.
Monitoring, Logging & Observability
13.1 Key Metrics
Submission-to-delivery latency
Broken down per country and per route, since a global average hides serious regional problems. p99 by country is more useful than an overall p99.
Delivery success rate per route
The single most important signal feeding the routing engine’s live health score — every score change flows out to routing caches on every instance.
Queue depth per partition
An early warning sign that a specific carrier connection is falling behind demand, long before any single message actually expires.
Connector error & session counts
Carrier connector error rates and connection session counts, to catch protocol-level session drops before they cause a backlog.
Cost per message per route
Tracked continuously since routing decisions directly affect operating expenses — a subtle model change can move millions of messages onto a more expensive path silently.
Trace-ID propagation
Every message tagged with a trace ID at submission that propagates through every hop — validation, routing, dispatch, receipt handling — so delayed messages can be reconstructed hop by hop.
13.2 Distributed Tracing
Because a single message passes through many independent services and asynchronous queues, each message is tagged with a trace ID at submission that propagates through every hop, from validation through routing, dispatch, and receipt handling, so an engineer investigating a delayed message can reconstruct its exact path and see precisely which stage introduced the delay.
13.3 Synthetic Monitoring
Beyond passively observing real customer traffic, the system continuously sends synthetic test messages to a controlled set of test numbers across major countries and carriers, measuring real delivery latency and success independent of actual client volume, which is often how route quality degradation is caught before it shows up in real customer complaints.
13.4 Alerting
Alerts are tiered: a route’s health score dropping below a warning threshold triggers an automated routing adjustment with a low-severity notification to the operations team, while a sustained drop across all routes to an entire country, or a queue depth growing without bound, triggers a page to an on-call engineer, since that pattern usually indicates a systemic issue rather than a single carrier’s temporary blip.
Deployment & Cloud Architecture
14.1 Multi-Region Deployment
The gateway is deployed across multiple cloud regions chosen to minimize latency to major carrier interconnection points, since some carriers require connections to originate from specific geographic locations or specific whitelisted IP ranges for security reasons. Regions operate largely independently, with cross-region replication for shared configuration and global client account data.
14.2 Containerization and Orchestration
Stateless services such as the API gateway, validation service, and routing service run as containerized workloads managed by an orchestration platform, which handles automated scaling, rolling deployments, and self-healing by restarting failed instances, allowing capacity to grow and shrink with observed traffic patterns.
14.3 Dedicated Connectivity for Carrier Links
For high-volume direct carrier connections, especially SS7 or Diameter-based interconnects, dedicated private network links or virtual private connections into the carrier’s infrastructure are common, since these signaling protocols were never designed to traverse the open internet safely or reliably.
14.4 Blue-Green and Canary Releases
Because a bug in the dispatch layer could silently drop or duplicate real customer messages, changes to carrier connectors and the routing engine are rolled out using canary deployments, shifting a small percentage of traffic to the new version first and comparing its delivery success rate against the stable version before a full rollout, rather than deploying to all traffic at once.
14.5 Cost Optimization
At the volumes this system operates at, infrastructure and carrier termination fees are both significant line items, and the two interact with each other. Auto-scaling policies for stateless services are tuned to scale down aggressively during predictable low-traffic hours, since over-provisioning idle capacity around the clock is wasteful at this scale. On the carrier side, the routing engine’s cost-awareness described earlier directly controls termination fee spend, and operations teams regularly review route performance reports to renegotiate aggregator contracts or shift volume toward direct carrier connections once a destination’s traffic justifies the fixed setup cost of a direct interconnect. Reserved capacity commitments with cloud providers for the steady baseline load, supplemented by on-demand scaling for burst traffic, is a common pattern that balances predictable cost against the system’s inherently spiky real-world demand.
Design Patterns & Anti-patterns
Patterns Used
- Circuit Breaker: isolates failing carrier routes automatically, described in detail in the reliability section above.
- Bulkhead: partitioned queues and dedicated worker pools per carrier prevent one carrier’s problems from starving others.
- Strategy Pattern: the routing engine treats route selection as a pluggable strategy, allowing least-cost, quality-weighted, or client-specific routing policies to be swapped without touching the rest of the pipeline.
- Saga-like Compensation: when a dispatch fails after partial progress, such as a message marked submitted but never acknowledged, a compensating action re-queues it through an alternate route rather than leaving it stuck.
- Event Sourcing for Message State: the full sequence of state transitions per message is stored as an append-only event log, making the current state derivable and auditable rather than relying on a single mutable status field that could be overwritten inconsistently under concurrent updates.
Anti-patterns to Avoid
- Blind least-cost-only routing: chasing the cheapest route without a quality floor silently degrades delivery rates for all clients, often invisibly until complaints arrive.
- Synchronous end-to-end delivery confirmation: making the client-facing API block until real handset delivery is confirmed couples client latency to the slowest, least reliable part of the entire global telecom system.
- A single shared queue for all destinations: without partitioning by carrier or country, one troubled carrier can back up the entire queue and delay unrelated healthy traffic.
- Ignoring number portability: routing purely by the original numbering prefix without HLR lookups or portability data leads to messages being sent through the wrong, often more expensive or less reliable, carrier.
- Treating “accepted by carrier” as “delivered”: reporting success to the client too early creates false confidence and hides real delivery failures from both the client and the business.
Best Practices & Common Mistakes
16.1 Best Practices
Continuously test routes
Continuously test routes with synthetic traffic rather than only reacting to real customer failures after the fact — degradation is usually visible in synthetic probes minutes before real complaints arrive.
Separate transactional from bulk
Separate transactional traffic, like OTPs, from bulk marketing traffic at the queueing level, since they have very different latency and cost priorities.
Explainable routing decisions
Keep routing decisions explainable and logged, so when a client asks why their message was delayed or cost more than expected, there is a clear audit trail rather than an opaque model output.
Graceful degradation
Design for graceful degradation: if the sophisticated scoring-based routing engine itself becomes unavailable, fall back to a simple, pre-configured default route per country rather than rejecting all traffic.
Idempotency from day one
Build strong idempotency guarantees from day one; retrofitting them after duplicate-message incidents is far more painful than getting them right from the start.
Tested DR runbooks
Practice regional failovers on a fixed schedule; a runbook that has never been executed is not a runbook, it is a document, and it will fail exactly when you most need it.
16.2 Common Mistakes
- Underestimating how much regulatory complexity varies by country, leading to messages being silently filtered by local operators for non-compliant sender IDs or content.
- Not accounting for character encoding when estimating message segments and billing, leading to unexpected costs when messages contain emoji or non-Latin scripts.
- Assuming delivery receipts always arrive promptly; in some countries they can be delayed by hours or never arrive at all, and the system must have a sensible timeout and fallback status rather than waiting indefinitely.
- Coupling the routing table update process too tightly to a slow database, causing route health signals to lag behind reality during a fast-moving carrier outage.
Real-World Industry Examples
Twilio — Super Network
Operates a global “Super Network” that abstracts hundreds of direct carrier and aggregator connections behind a single API, continuously scoring and shifting traffic between routes based on live delivery performance, which mirrors the routing engine and health-scoring design described throughout this tutorial.
WhatsApp Business & RCS gateways
Illustrate how modern messaging gateways now route across multiple channels, not just SMS, falling back to SMS automatically when a richer channel is unavailable for a given recipient, which extends the same routing-engine concept to a multi-channel decision rather than a single-protocol one.
AWS SNS SMS & Sinch
Both expose destination-country-based pricing and delivery reporting, reflecting the same country and carrier segmented routing table structure described in this design, where cost and reliability both vary sharply by destination.
Banking OTP infrastructure
In many countries prioritizes transactional SMS traffic on separate, premium-priced routes specifically to guarantee faster delivery and higher success rates than bulk marketing SMS, a direct real-world application of separating transactional and bulk traffic at the queueing and routing level.
Advantages, Disadvantages & Trade-offs
| Aspect | Advantage | Trade-off / Disadvantage |
|---|---|---|
| Multi-carrier routing | Higher reliability, better pricing, resilience to single-carrier outages | Significant operational complexity, more integration surface area to maintain |
| Asynchronous pipeline | High throughput, resilience to slow carriers, decoupled scaling | Higher perceived latency for the final delivery confirmation compared to a synchronous design |
| Aggressive least-cost routing | Lower cost per message | Risk of lower delivery quality if not paired with a strict health floor |
| Direct carrier interconnects | Best price and quality for high-volume destinations | High setup cost, ongoing compliance burden, only worthwhile at large scale |
| Aggregator-only strategy | Faster to launch, lower operational overhead | Higher per-message cost and an extra layer that can itself become a bottleneck or point of failure |
Frequently Asked Questions
Why does SMS delivery time vary so much between countries?
Because the underlying telecom infrastructure, interconnection quality, and regulatory filtering differ enormously by country. Some markets have modern, high-throughput operator connections while others rely on older signaling infrastructure or apply strict spam filtering that adds processing delay.
What happens if every available route to a country is down at the same time?
The routing engine has no healthy route to select, so the message is held in the queue up to a configured maximum wait time while the system retries periodically, and if no route recovers in time, the message is marked failed with a clear reason code so the client application can decide whether to try an alternate channel.
How is duplicate message sending prevented during retries?
Through idempotency keys on client requests and unique attempt identifiers on every internal dispatch attempt, combined with a reconciliation step that checks the carrier’s own record before resubmitting after an ambiguous timeout, as described in the reliability section.
Why can’t the gateway just always use the fastest carrier?
Because the fastest route for one destination country is not necessarily fast, or even available, for every other country, and the fastest route often carries a cost or reliability trade-off. Route selection is destination-specific and continuously recalculated, not a single global choice.
How does the system handle a recipient’s phone being switched off?
The carrier’s own network typically queues the message in a store-and-forward system for a validity period, often up to 72 hours by default, and attempts redelivery once the handset reconnects; the gateway simply waits for the eventual delivery receipt or the expiry notification within that window.
How would you design this system to add a new country in a weekend?
Onboarding a new country mainly means adding new routing table entries: negotiating and configuring at least two independent carrier or aggregator connections for redundancy, registering required sender IDs, running synthetic test traffic to seed initial health scores, and setting a conservative default route until enough real traffic has built confidence in the scoring data. Because the routing engine, dispatch workers, and queue partitioning are all generic and destination-agnostic by design, no core code changes are needed, only configuration and new connector credentials.
Why use both a message queue and a database instead of just one durable store?
The queue and the database solve different problems. The queue provides ordered, at-least-once, backpressure-aware handoff between the routing stage and the dispatch stage, and is optimized for high-throughput sequential consumption. The database provides durable, queryable, long-term storage of message history and status that clients and support teams need to search by arbitrary criteria such as time range or phone number. Using the queue as a permanent store would make historical queries slow and expensive, while using only a database for real-time dispatch coordination would add unnecessary latency and lose natural backpressure handling.
Summary & Key Takeaways
The core mental model
A global SMS gateway is fundamentally a routing and reliability problem, not just a message-sending problem, because no single carrier connection can reach every phone number on earth. Every hard design choice — the health-scored routing engine, the partitioned durable queue, the strict separation between “accepted by carrier” and “delivered to handset,” the multi-protocol dispatch layer — exists to absorb the messy, uneven, multi-country reality of the telecom world behind a single clean API.
If you remember nothing else from this tutorial, remember this: the gateway’s job is not to send a message. It is to choose, out of many possible paths through the global telecom web, the one path that will most reliably deliver this message to this number, right now, at an acceptable cost — and to do that decision millions of times per minute without ever letting the client application feel the underlying complexity.
Routing is the differentiator
The routing engine is the core differentiator, blending cost, live delivery success rate, and latency into a continuously updated scoring system rather than relying on a single static routing table.
Async, partitioned pipeline
An asynchronous, queue-based pipeline with partitioning per carrier or country is essential to prevent one carrier’s problems from affecting unrelated traffic, applying the bulkhead pattern at the infrastructure level.
Accepted vs delivered
Idempotency, circuit breakers, and clear separation between “accepted by carrier” and “delivered to handset” states are what make the system trustworthy for sensitive use cases like banking OTPs.
Compliance is design
Security, regulatory compliance, and fraud prevention are not optional add-ons but core design constraints, since violating a country’s rules can mean losing an entire carrier connection.
Industry-proven at scale
Real production systems like Twilio’s Super Network validate that this architecture — health-scored multi-carrier routing behind a simple unified API — is the industry-proven approach at true global scale.
Fundamentally a routing problem
A global SMS gateway is fundamentally a routing and reliability problem, not just a message-sending problem, because no single carrier connection can reach every phone number on earth.