Queue a single transactional email for delivery
/v1/send/emails/sendValidate the request, check the sender and recipients, and queue the email. Returns 202 with a message id as soon as the email is accepted; delivery happens asynchronously.
fromobjectrequiredtostring[]requiredccstring[]bccstring[]replyTostring | string[]subjectstringhtmlstringtextstringtemplateIdstringtemplateDataobjectattachmentsobject[]tagsstring[]metadataobjectprioritystringnormalhighnormallowcategorystringtransactionaltransactionalmarketingunsubscribebooleanlistIdstringUnknown fields are rejected with 400 validation_error, so a typo such as subjcet fails loudly rather than sending an email with no subject. Fields some other providers accept but send.dev deliberately does not (headers, track_opens, track_clicks) are rejected with a message saying so: send.dev does not set arbitrary custom headers, inject tracking pixels, or rewrite links. The one header pair it does add is List-Unsubscribe, when you ask for it; see Unsubscribe.
html, text, or both. If you provide neither, you must provide templateId.templateId is set, the template's html and text are used and any inline html/text in the request is ignored. templateData is required, even if the template has no variables (send {}).templateId is set and subject is omitted, the template's subject (with variables substituted) is used. A subject in the request overrides the template's subject.subject, from.name, and all addresses must not contain newlines or control characters.category is marketing or unsubscribe is true, the email must have exactly one recipient in to and no cc or bcc, because the unsubscribe link is per recipient. Use batch for many recipients. If html or text contains {{unsubscribe_url}} it is replaced with the recipient's link; otherwise a short footer is appended. Details on Unsubscribe.The whole request, including base64 content, must be under about 236 KB (241,664 bytes). Base64 inflates files by a third, so that is roughly 170 KB of raw attachment data per email. For anything larger, host the file at an https:// URL and pass it as path; it is fetched when the email is sent, so the URL must be reachable from the public internet at that moment. Limit: 10 attachments per email.
{
"attachments": [
{ "filename": "receipt.pdf", "contentType": "application/pdf", "content": "JVBERi0xLjQK..." },
{ "filename": "statement.pdf", "contentType": "application/pdf", "path": "https://files.example.com/statements/2026-09.pdf" }
]
}Before an email is queued, every recipient across to, cc, and bcc is checked against your suppression list and for basic deliverability (the domain must have an MX record and must not be a disposable-address service). A request with any bounced, complained, or undeliverable recipient is rejected as a whole; nothing is sent to the other recipients. Fix or remove the flagged address and resend.
A recipient who has unsubscribed is treated differently: the send is rejected with 400 recipient_unsubscribed only when category is marketing (or your account has "block all" enabled). Transactional mail to an unsubscribed address is delivered.
curl -X POST https://api.do.dev/v1/send/emails/send \
-H "Authorization: Bearer $SEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": { "email": "receipts@mail.example.com", "name": "Example Store" },
"to": ["customer@example.net"],
"replyTo": "support@example.com",
"subject": "Your order #4821 has shipped",
"html": "<p>Your order is on its way.</p>",
"text": "Your order is on its way.",
"tags": ["order-shipped"],
"metadata": { "orderId": "4821" }
}'{
"id": "msg_3f9c2a1d7b8e4c0f9a1b2",
"status": "queued"
}curl -X POST https://api.do.dev/v1/send/emails/send \
-H "Authorization: Bearer $SEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": { "email": "hello@mail.example.com", "name": "Example App" },
"to": ["new-user@example.net"],
"templateId": "tpl_m8x9q4wzk17c2",
"templateData": { "firstName": "Ada", "loginUrl": "https://app.example.com/login" }
}'{
"id": "msg_8e4c0f9a1b23f9c2a1d7b",
"status": "queued"
}A 202 Accepted means the email passed validation and is queued. It does not mean it has been delivered. Use the returned id with Get an email, or subscribe to webhooks, to follow it.
| Field | Type | Description |
|---|---|---|
id | string | Message id, prefixed msg_. Use it to fetch status. |
status | string | Always queued at this point. |
All errors use the standard envelope.
{
"error": {
"code": "validation_error",
"message": "Validation failed",
"details": [
{ "field": "to[0]", "message": "Invalid email in 'to' field: not-an-address" },
{ "field": "subjcet", "message": "Unknown field" }
]
}
}{
"error": {
"code": "sender_not_verified",
"message": "Sender not verified. Either verify the email address \"hello@mail.example.com\" or verify the domain \"mail.example.com\".",
"help": "Use POST /v1/senders/verify to verify a single email address, or POST /v1/domains to verify a domain."
}
}See Rate limits for what the 429 headers mean and when to retry.