One API.
Email and WhatsApp.
One contract for transactional messaging. Email ships today, WhatsApp on the same wire format soon. Multi-provider fallback and retries live in the request, not the docs.
No card. No waitlist. / 15-minute integration.
curl https://api.comms.ndcoders.com/v1/messages/email \
-H "x-api-key: $COMMS_KEY" \
-H "Content-Type: application/json" \
-d '{
"sender": { "fromAddress": "billing@your-app.com" },
"destination": { "to": ["alex@startup.io"] },
"content": {
"type": "raw",
"subject": "Receipt #2891",
"body": "Thanks for your payment."
}
}' one request shape. two channels. zero glue code.
what's in the wire
Reliability is a contract,
not a brag.
Six capabilities the API guarantees. None of them are upsells, none of them are hidden behind a flag.
-
Two channels, one contract.
Email and WhatsApp share the same envelope, the same response, and the same record shape.
-
Provider fallback chain.
Configured per channel. Primary times out, the next provider takes the request, the response says which.
-
Scheduled sends.
Pass ISO 8601 in the payload. The engine holds the message until then and dispatches without a worker on your side.
-
Read your messages back.
GET a message by ID, or page your sends filtered by channel, status, and time. Scoped to your API key.
-
Per-attempt timing and errors.
Every provider try is recorded: duration, status, error category. Debugging is a GET request, not a Slack thread.
-
90-day delivery log.
Searchable by message ID, app, channel, status, and time. Replay a delivery without re-running the producer.
one path, two channels
Your app speaks to one endpoint.
The engine speaks to every provider.
The orchestrator owns rate limiting, retry budgets, circuit-breaker state, and the fallback chain. You ship the message; we ship the delivery.
{
"messageId": "5f1d8c2e-9b3a-4c7e-8f1a-2d6b9e0c4a71",
"channel": "EMAIL",
"status": "DELIVERED",
"destination": { "to": ["alex@startup.io"] },
"content": { "type": "raw", "subject": "Receipt #2891", "body": "Thanks for your payment." },
"createdAt": "2026-05-26T15:10:31.991Z",
"updatedAt": "2026-05-26T15:10:41.029Z",
"attempts": [
{
"provider": "ses-primary",
"status": "RETRYING",
"errorCategory": "PROVIDER_OUTAGE",
"durationMs": 8120,
"timestamp": "2026-05-26T15:10:32.404Z"
},
{
"provider": "ses-fallback",
"status": "DELIVERED",
"durationMs": 412,
"timestamp": "2026-05-26T15:10:40.617Z"
}
]
} show, don't preach
Every retry is in the response.
The same record powers the dashboard, the list endpoint, and the API response. When the primary engine times out, the chain falls through to the fallback, and the message lands. The failed attempt isn't hidden; it's part of the message.
- →
attempts[]is ordered. Position is chronology. - → Six normalized
errorCategoryvalues across every engine. - → Persisted for 90 days. Searchable by anything in the record.
why one api
Most apps send across three channels, billed by three vendors, debugged in three dashboards. Comms is one API, one bill, one log. You stop integrating; we stop you from caring which provider actually sent it.
Switching providers is a config change, not a rewrite. Adding a fourth channel is a config change, not a project. The boring part of comms infrastructure stays boring.
ready when you are
Send your first message in fifteen minutes.
No card. No waitlist. The API key is one click away.