Skip to main content
Base URLhttps://api.senddio.com

API 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.

start here

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.

Domain required for live sends. The 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

Authentication

Pass your API key in the Authorization header on every request.

Authorization: Bearer sdo_live_<32 chars>

API key types

FieldTypeDescription
sdo_live_live keyProduction key. All channels. Rate-limited to your plan quota.
sdo_mcp_mcp keyMCP key for the Claude connector (@senddio/mcp-server). Identical permissions — the prefix lets you audit AI sends separately in your logs.

Key scopes

FieldTypeDescription
sendscopePOST /v1/send and /v1/emails
readscopeGET /v1/messages, /v1/stats, /v1/insights
managescopeAPI keys, domains, webhooks, team (ADMIN+ only)

Auth errors

401Missing or malformed Authorization header.
403Key valid but lacks the required scope for this route.
universal send

POST /v1/send

202 queuedAccepted for asynchronous delivery

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

Email

{
  "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" }
}
FieldTypeDescription
channelreq"email"Literal "email". Omit only when using the /v1/emails alias.
fromreqstringSender email. Domain must be verified in dashboard → Domains. Returns 422 DOMAIN_UNVERIFIED if not.
fromNamestringDisplay name in the inbox. Max 80 chars.
toreqstring | string[]Recipient(s). Single address or array of up to 50 addresses.
subjectreqstringEmail subject line. Min 1 char, max 998 chars.
htmlstringHTML body. At least one of html or text is recommended.
textstringPlain-text fallback. Used by email clients that do not render HTML and by spam filters.
replyTostringReply-to address. Overrides the from address for replies.
ccstring[]CC recipients. Max 50 addresses.
bccstring[]BCC recipients. Max 50 addresses. Not visible to other recipients.
tagstringFree-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.
trackOpensbooleanEnable open tracking via pixel. Defaults to your account setting.
trackClicksbooleanEnable click tracking by rewriting links. Defaults to your account setting.
headersRecord<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' }
channel — sms

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"
}
FieldTypeDescription
channelreq"sms"Literal "sms".
fromreqstring (E.164)Your provisioned Senddio SMS number. Must be configured in Channels.
toreqstring (E.164)Recipient phone number in E.164 format.
bodyreqstringSMS message body. Max 1600 chars.
tagstringLabel for log filtering. Max 100 chars.
channel — whatsapp

WhatsApp

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"
}
FieldTypeDescription
channelreq"whatsapp"Literal "whatsapp".
fromreqstring (E.164)Your WhatsApp Business number.
toreqstring (E.164)Recipient WhatsApp number in E.164 format.
bodyreqstringMessage body. Max 4096 chars.
tagstringLabel for log filtering. Max 100 chars.
channel — push

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"
}
FieldTypeDescription
channelreq"push"Literal "push".
toreqstringFCM device registration token. Max 4096 chars.
titlereqstringNotification title. Max 120 chars.
bodyreqstringNotification body. Max 2000 chars.
dataRecord<string, string>Key/value pairs passed to the app. All values must be strings.
tagstringLabel for log filtering. Max 100 chars.
channel — slack

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"
}
FieldTypeDescription
channelreq"slack"Literal "slack".
toreqstringSlack channel ID (C…) or user ID (U…). Max 80 chars.
bodyreqstringMessage text. Supports Slack mrkdwn. Max 40,000 chars.
tagstringLabel for log filtering. Max 100 chars.
channel — telegram

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"
}
FieldTypeDescription
channelreq"telegram"Literal "telegram".
toreqstringTelegram chat ID. User IDs are positive integers; group/channel IDs are negative. Max 64 chars.
bodyreqstringMessage body. Supports HTML formatting. Max 4096 chars.
tagstringLabel for log filtering. Max 100 chars.
messages

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

FieldTypeDescription
channelemail | sms | whatsapp | push | slack | telegramFilter by channel.
statusqueued | sending | delivered | bounced | failed | cancelledFilter by delivery status.
tagstringFilter by tag.
qstringFull-text search across subject and recipient.
fromISO 8601Return messages created on or after this timestamp.
toISO 8601Return messages created on or before this timestamp.
limitnumberPage size. Default 25, max 100.
cursorstringOpaque 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

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

202
Accepted. Immediate send endpoints return 202 with status: "queued".
401
Missing or invalid API key. Check the Authorization header.
403
Key valid but lacks the required scope for this route.
422
Validation failed. Check the issues array. Common causes: missing required fields, wrong type, unverified domain in from, phone not in E.164 format.
429
Daily rate limit hit. Check X-RateLimit-Remaining and X-RateLimit-Reset. Limit resets at midnight UTC.
503
Queue temporarily unavailable. Safe to retry with exponential backoff.

Machine error codes

FieldTypeDescription
VALIDATION_FAILED422One or more fields failed validation. See the issues array.
DOMAIN_UNVERIFIED422The from domain has not been verified. Go to dashboard → Domains and complete SPF/DKIM/DMARC setup.
CHANNEL_NOT_CONFIGURED422Channel requested but no provider credentials configured for this org.
CHANNEL_NOT_ALLOWED403Your plan does not include this channel.
PLAN_LIMIT_REACHED429Daily or monthly send limit for your plan exhausted.
RATE_LIMIT_EXCEEDED429Too many requests in the current window.
INSUFFICIENT_ROLE403API key or session user does not have the required org role.
QUEUE_UNAVAILABLE503Message queuing temporarily down. Retry with backoff.
rate limits

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: 1727913600

Plan limits

PlanDailyMonthly
Free300 emails9,000 emails
Starter ($9/mo)1,000 emails30,000 emails
Pro ($79/mo)5,000 emails150,000 emails
EnterpriseCustomCustom

SMS, WhatsApp, and push channels have separate per-plan safety caps. Check your dashboard for current channel quotas.

Questions not covered here?

Get an API keysupport@senddio.com