https://api.senddio.comAPI Reference
The Senddio API accepts JSON request bodies and returns JSON responses. Every response includes standard error shapes, rate-limit headers, and a stable machine-readable code string so your error handling never breaks across releases.
30-second quickstart
Get your API key from the dashboard → API Keys tab. Then send your first email.
curl https://api.senddio.com/v1/send \
-H "Authorization: Bearer sdo_live_<your_key>" \
-H "Content-Type: application/json" \
-d '{
"channel": "email",
"from": "you@yourdomain.com",
"to": "recipient@example.com",
"subject": "Hello from Senddio",
"html": "<p>It works.</p>"
}'{ "id": "sdo_msg_abc123", "status": "queued" }A successful immediate send returns HTTP 202. Delivery happens asynchronously — use GET /v1/messages/:id to check status, or configure a webhook to receive delivery events.
from address must use a domain you have added and verified in your dashboard → Domains tab. SPF, DKIM, and DMARC records must all pass. Sends from unverified domains are rejected with a 422 DOMAIN_UNVERIFIED error.Authentication
Pass your API key in the Authorization header on every request.
Authorization: Bearer sdo_live_<32 chars>API key types
| Field | Type | Description |
|---|---|---|
sdo_live_ | live key | Production key. All channels. Rate-limited to your plan quota. |
sdo_mcp_ | mcp key | MCP key for the Claude connector (@senddio/mcp-server). Identical permissions — the prefix lets you audit AI sends separately in your logs. |
Key scopes
| Field | Type | Description |
|---|---|---|
send | scope | POST /v1/send and /v1/emails |
read | scope | GET /v1/messages, /v1/stats, /v1/insights |
manage | scope | API keys, domains, webhooks, team (ADMIN+ only) |
Auth errors
Authorization header.POST /v1/send
The universal send endpoint. Set the required channel field to select the delivery provider. Validation is synchronous; delivery is queued asynchronously.
Legacy alias: POST /v1/emails accepts the same email body without a channel field. Prefer /v1/send for new integrations.
{ "id": "sdo_msg_<cuid2>", "status": "queued" }{
"channel": "email",
"from": "hello@yourdomain.com",
"fromName": "Your Company",
"to": "user@example.com",
"subject": "Welcome",
"html": "<p>Hello!</p>",
"text": "Hello!",
"replyTo": "support@yourdomain.com",
"cc": ["cc@example.com"],
"bcc": ["bcc@example.com"],
"tag": "onboarding",
"messageType": "transactional",
"trackOpens": true,
"trackClicks": true,
"headers": { "X-Custom-Header": "value" }
}| Field | Type | Description |
|---|---|---|
channelreq | "email" | Literal "email". Omit only when using the /v1/emails alias. |
fromreq | string | Sender email. Domain must be verified in dashboard → Domains. Returns 422 DOMAIN_UNVERIFIED if not. |
fromName | string | Display name in the inbox. Max 80 chars. |
toreq | string | string[] | Recipient(s). Single address or array of up to 50 addresses. |
subjectreq | string | Email subject line. Min 1 char, max 998 chars. |
html | string | HTML body. At least one of html or text is recommended. |
text | string | Plain-text fallback. Used by email clients that do not render HTML and by spam filters. |
replyTo | string | Reply-to address. Overrides the from address for replies. |
cc | string[] | CC recipients. Max 50 addresses. |
bcc | string[] | BCC recipients. Max 50 addresses. Not visible to other recipients. |
tag | string | Free-form label for filtering in logs. Max 100 chars. |
messageType | "transactional" | "broadcast" | Selects the isolated email delivery stream and IP pool. Defaults to transactional. Set broadcast for newsletters, campaigns, and other bulk mail. |
trackOpens | boolean | Enable open tracking via pixel. Defaults to your account setting. |
trackClicks | boolean | Enable click tracking by rewriting links. Defaults to your account setting. |
headers | Record<string, string> | Additional SMTP headers. Useful for List-Unsubscribe, X-Entity-Ref-ID etc. |
Delivery isolation: transactional and broadcast email use separate sending IP pools, so bulk-mail IP reputation spillover is reduced for password resets, receipts and account notifications. Shared domain reputation and recipient policies still affect delivery. Existing integrations that omit messageType remain transactional. The legacy send_broadcast tag is recognized as broadcast for compatibility, but new integrations should classify mail explicitly.
Node.js example
const res = await fetch('https://api.senddio.com/v1/send', {
method: 'POST',
headers: {
'Authorization': 'Bearer sdo_live_<your_key>',
'Content-Type': 'application/json',
},
body: JSON.stringify({
channel: 'email',
from: 'hello@yourdomain.com',
to: 'user@example.com',
subject: 'Welcome',
html: '<p>Hello!</p>',
}),
});
const { id, status } = await res.json();
// { id: 'sdo_msg_abc123', status: 'queued' }SMS
Requires an SMS sender number provisioned in dashboard → Channels. Numbers must be in E.164 format.
{
"channel": "sms",
"from": "+14155552671",
"to": "+447911123456",
"body": "Your verification code is 482912",
"tag": "otp"
}| Field | Type | Description |
|---|---|---|
channelreq | "sms" | Literal "sms". |
fromreq | string (E.164) | Your provisioned Senddio SMS number. Must be configured in Channels. |
toreq | string (E.164) | Recipient phone number in E.164 format. |
bodyreq | string | SMS message body. Max 1600 chars. |
tag | string | Label for log filtering. Max 100 chars. |
Requires WhatsApp Business API configured via Channels. Sender must be your WhatsApp Business number.
{
"channel": "whatsapp",
"from": "+14155552671",
"to": "+447911123456",
"body": "Hello from WhatsApp",
"tag": "support"
}| Field | Type | Description |
|---|---|---|
channelreq | "whatsapp" | Literal "whatsapp". |
fromreq | string (E.164) | Your WhatsApp Business number. |
toreq | string (E.164) | Recipient WhatsApp number in E.164 format. |
bodyreq | string | Message body. Max 4096 chars. |
tag | string | Label for log filtering. Max 100 chars. |
Push (FCM)
Requires Firebase Cloud Messaging configured in Channels. Pass the device FCM registration token as to.
{
"channel": "push",
"to": "<FCM_registration_token>",
"title": "New message",
"body": "You have a new message from Alex",
"data": { "messageId": "msg_123", "type": "chat" },
"tag": "chat"
}| Field | Type | Description |
|---|---|---|
channelreq | "push" | Literal "push". |
toreq | string | FCM device registration token. Max 4096 chars. |
titlereq | string | Notification title. Max 120 chars. |
bodyreq | string | Notification body. Max 2000 chars. |
data | Record<string, string> | Key/value pairs passed to the app. All values must be strings. |
tag | string | Label for log filtering. Max 100 chars. |
Slack
Requires a Slack app token configured in Channels. Pass a channel ID (C01234567) or user ID (U01234567) as to.
{
"channel": "slack",
"to": "C01234ABCDE",
"body": "Deployment to production succeeded",
"tag": "ops"
}| Field | Type | Description |
|---|---|---|
channelreq | "slack" | Literal "slack". |
toreq | string | Slack channel ID (C…) or user ID (U…). Max 80 chars. |
bodyreq | string | Message text. Supports Slack mrkdwn. Max 40,000 chars. |
tag | string | Label for log filtering. Max 100 chars. |
Telegram
Requires a Telegram Bot token configured in Channels. Pass the chat ID as to.
{
"channel": "telegram",
"to": "123456789",
"body": "Your order #4821 has shipped",
"tag": "fulfillment"
}| Field | Type | Description |
|---|---|---|
channelreq | "telegram" | Literal "telegram". |
toreq | string | Telegram chat ID. User IDs are positive integers; group/channel IDs are negative. Max 64 chars. |
bodyreq | string | Message body. Supports HTML formatting. Max 4096 chars. |
tag | string | Label for log filtering. Max 100 chars. |
GET /v1/messages
List sent messages for your organisation with optional filters. Cursor-paginated.
curl "https://api.senddio.com/v1/messages?channel=email&status=delivered&limit=25" \
-H "Authorization: Bearer sdo_live_<your_key>"Query parameters
| Field | Type | Description |
|---|---|---|
channel | email | sms | whatsapp | push | slack | telegram | Filter by channel. |
status | queued | sending | delivered | bounced | failed | cancelled | Filter by delivery status. |
tag | string | Filter by tag. |
q | string | Full-text search across subject and recipient. |
from | ISO 8601 | Return messages created on or after this timestamp. |
to | ISO 8601 | Return messages created on or before this timestamp. |
limit | number | Page size. Default 25, max 100. |
cursor | string | Opaque cursor from the previous response nextCursor field. |
GET /v1/messages/:id
Retrieve a single message with its full delivery event timeline. The id is the sdo_msg_-prefixed value returned by POST /v1/send.
curl "https://api.senddio.com/v1/messages/sdo_msg_abc123" \
-H "Authorization: Bearer sdo_live_<your_key>"Errors
Every error response uses the same JSON shape. message is a human-readable instruction. code is a stable machine string you can switch on. issues appears on 422 responses with field-level detail.
{
"error": "validation_error",
"message": "Request body failed validation. See issues for field-level detail.",
"code": "VALIDATION_FAILED",
"issues": [
{ "path": ["from"], "message": "Invalid email" }
]
}HTTP status codes
status: "queued".Authorization header.issues array. Common causes: missing required fields, wrong type, unverified domain in from, phone not in E.164 format.X-RateLimit-Remaining and X-RateLimit-Reset. Limit resets at midnight UTC.Machine error codes
| Field | Type | Description |
|---|---|---|
VALIDATION_FAILED | 422 | One or more fields failed validation. See the issues array. |
DOMAIN_UNVERIFIED | 422 | The from domain has not been verified. Go to dashboard → Domains and complete SPF/DKIM/DMARC setup. |
CHANNEL_NOT_CONFIGURED | 422 | Channel requested but no provider credentials configured for this org. |
CHANNEL_NOT_ALLOWED | 403 | Your plan does not include this channel. |
PLAN_LIMIT_REACHED | 429 | Daily or monthly send limit for your plan exhausted. |
RATE_LIMIT_EXCEEDED | 429 | Too many requests in the current window. |
INSUFFICIENT_ROLE | 403 | API key or session user does not have the required org role. |
QUEUE_UNAVAILABLE | 503 | Message queuing temporarily down. Retry with backoff. |
Rate limits
Every response includes rate-limit headers. The daily limit resets at midnight UTC — not a rolling 24-hour window — so you can predict exactly when capacity returns.
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 842
X-RateLimit-Reset: 1727913600Plan limits
| Plan | Daily | Monthly |
|---|---|---|
| Free | 300 emails | 9,000 emails |
| Starter ($9/mo) | 1,000 emails | 30,000 emails |
| Pro ($79/mo) | 5,000 emails | 150,000 emails |
| Enterprise | Custom | Custom |
SMS, WhatsApp, and push channels have separate per-plan safety caps. Check your dashboard for current channel quotas.
Questions not covered here?