Send Email

Queue a single transactional email for delivery

POST/v1/send/emails/send

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

Requires:send:write

Request body

Body parameters

fromobjectrequired
Who the email is from. The address must belong to a verified domain or be a verified sender.
tostring[]required
Recipient addresses. A single string is also accepted.
ccstring[]
Carbon-copy recipients.
bccstring[]
Blind-carbon-copy recipients.
replyTostring | string[]
Where replies go. Without it, some mail clients reply to the bounce address and the reply is lost. reply_to (snake case) is accepted as an alias.
subjectstring
Subject line, at most 500 characters. Required unless templateId is set, in which case the template's subject is used when this is omitted.
htmlstring
HTML body. Provide html, text, or both, unless using templateId.
textstring
Plain-text body. Recommended alongside html for clients that do not render HTML.
templateIdstring
Send a stored template instead of inline html/text. See Templates.
templateDataobject
Values for the template's {{variables}}. Required when templateId is set.
attachmentsobject[]
Up to 10 attachments. Each is either inline base64 content or an https URL fetched at send time.
tagsstring[]
Labels for your own categorization. Returned when you fetch the email.
metadataobject
Arbitrary key-value pairs stored with the email and returned when you fetch it. Not sent to the recipient.
prioritystring
Queue priority. high is processed ahead of normal traffic.Defaults to normal
highnormallow
categorystring
transactional (default) or marketing. marketing adds unsubscribe headers and a link, and fails the send if the recipient has unsubscribed. See Unsubscribe.Defaults to transactional
transactionalmarketing
unsubscribeboolean
Add List-Unsubscribe headers and an unsubscribe link without treating the email as marketing. Implied by category: marketing.
listIdstring
Label for the list this email belongs to, at most 64 characters (letters, digits, . _ : -). Recorded when the recipient unsubscribes.

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

Content rules

  • Provide html, text, or both. If you provide neither, you must provide templateId.
  • When 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 {}).
  • When 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.
  • When 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.

Attachments

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" }
  ]
}

Recipients

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.

Example

Send an email
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" }
}'
Response202
{
"id": "msg_3f9c2a1d7b8e4c0f9a1b2",
"status": "queued"
}

Sending a template

Send with a template
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" }
}'
Response202
{
"id": "msg_8e4c0f9a1b23f9c2a1d7b",
"status": "queued"
}

Response

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.

FieldTypeDescription
idstringMessage id, prefixed msg_. Use it to fetch status.
statusstringAlways queued at this point.

Errors

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.