Atrix — WhatsApp & SMS infrastructure
One WhatsApp server.Nothing gets lost.
One WhatsApp server every product shares, with SMS through licensed providers when WhatsApp can’t reach. Durable in both directions, and it knows when a number is quietly dying.
How a message travels
Every product patches into one board.
What you learn the hard way
WhatsApp does not fail loudly.
It accepts, acknowledges, then quietly does nothing. Three of these cost us a production incident. The fourth was sitting in plain sight in the retry loop, losing messages nobody counted.
A restricted number does not fail. It accepts the message, returns an id, and never delivers it.
Send errors, connection state and ban webhooks all stay clean while every message spent on that number is lost. Switchboard measures delivery receipts per number and pulls one from the pool when its delivery collapses.
A banned number does not come back.
Sending at full volume from a fresh SIM is the most reliable way to lose it — and every conversation on it. Each number gets a daily cap and a warm-up ramp, paced independently.
Five attempts, thirty seconds apart, then the event is gone.
That is how the raw WhatsApp layer retries a webhook. A consumer down for a deploy loses inbound messages and nobody can tell. Switchboard writes every event to Postgres before acknowledging it, so an event that arrived is an event that gets delivered.
Connecting an already-connected instance unlinks the device.
And fetching a QR for a paired one tears the pairing down four seconds after it succeeded — so every scan appears to work and every pairing dies. Both refused here, once, for everyone.
Inbound
Seven attempts, then kept anyway.
Every event is written to Postgres before it is acknowledged, then retried out to six hours — well past any deploy. Anything that survives all of that becomes a dead letter: kept, visible and replayable, rather than dropped.
The control plane
Six things you’d otherwise build yourself.
None of this comes with WhatsApp. Every product that talks to it ends up writing these six things — separately, differently, and usually after losing something first.
Tenant isolation
Each project sees only its own numbers. A foreign resource answers 404, never 403 — ids cannot be enumerated across tenants.
Inbound that survives a deploy
Every event is written to Postgres before it is acknowledged. A retry ladder to six hours, dead letters kept and replayable, at-least-once with a dedupe header.
A durable send queue
Hand the message over and Switchboard owns it. A daily cap stops being an error and becomes “goes out tomorrow”.
Pooling, late-bound
The number is chosen when a message is due, never when queued — so one banned in the meantime doesn’t strand the work behind it.
Message status
queued → sent → delivered → read, from WhatsApp’s own receipts. One call, not an event stream you reassemble.
Told, not asked
Prometheus metrics plus six rules that notify on transitions. A pull endpoint nobody scrapes is not monitoring.
Outbound
Sent is not delivered.
A restricted number accepts every message and delivers none, so nothing looks wrong. Receipts are the only honest evidence — and the number is pulled from the pool the moment its delivery collapses.
When WhatsApp can’t reach
A login code that always arrives.
The code goes out on WhatsApp. If it hasn’t arrived in twenty seconds, an SMS copy follows through a licensed provider — and if that provider is down, the next one takes it. Your product never holds a provider key, and never sees the code.
SendPK
Pakistan · local
Infobip
Global · Gulf
Telnyx
Global · signed reports
Plivo
Global
Twilio
Everywhere · last resort
Failover, not failure
A provider that’s down or out of credit is skipped for that message and benched for two minutes. A wrong number is never retried somewhere else — that would only pay twice for the same answer.
Reports you can trust
Delivery reports are verified or fetched, never taken on faith. An unsigned “failed” could otherwise trigger a paid fallback for anyone who found the URL.
Fraud stops at the door
Codes go only to countries you serve, with per-number, per-IP and per-project limits, and an alarm when many codes are sent and few are used.
Patch in
Send without picking a number.
Switchboard chooses one of yours when the message is due, paces it so a burst is impossible, retries it, and records what happened.
202, not 200 — accepted, not sent. Saying so honestly is the difference between a caller that checks and one that assumes.
// the queue owns pooling, pacing, retries, caps const { id } = await wa.outbound.send({ to: "+971500000001", message: { text: "hello" }, }) const { state } = await wa.outbound.status(id) // queued → sent → delivered → read // inbound: verify, parse, dedupe, acknowledge export const POST = createWebhookHandler({ secret: process.env.SWITCHBOARD_WEBHOOK_SECRET, seen: (id) => db.deliveries.exists(id), onEvent: async ({ event }) => { await queue.push(event) }, })