Sending email

One endpoint sends your mail: POST /v1/emails. Bearer-key auth, JSON in, a message id out, webhooks for everything after.

Send with a single authenticated request to POST https://api.mail.atrix.dev/v1/emails. Authentication is an am_live_ bearer key with the email.send scope — see Authentication.

Send an email

shell
curl -X POST https://api.mail.atrix.dev/v1/emails \
  -H "Authorization: Bearer $ATRIX_MAIL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Yourco Billing <billing@yourco.com>",
    "to": ["ava@client.io"],
    "subject": "Your March invoice",
    "html": "<p>Invoice attached. Thanks!</p>",
    "text": "Invoice attached. Thanks!",
    "reply_to": "support@yourco.com"
  }'

A successful request returns 201 Created with the message id:

json
{
  "id": "6f6d2b1a-3c9e-4a51-8f2b-2d7c1a0e9b44",
  "status": "queued"
}

queued means we accepted the message; delivery is asynchronous. Track it with webhooks or fetch it back with GET /v1/emails/:id (needs the emails.read scope).

Request fields

FieldTypeNotes
fromstringRequired. Address on a verified domain; Name <addr> form allowed.
tostring | string[]Required. 1–50 addresses.
subjectstringRequired. Up to 998 characters.
htmlstringAt least one of html / text is required.
textstringPlain-text body; strongly recommended alongside html.
ccstring | string[]Optional.
bccstring | string[]Optional.
reply_tostringOptional.
headersobjectOptional map of custom header name → value.
attachmentsobject[]Optional. See below. Up to 20 files.
idempotency_keystringOptional safe-retry key; see Idempotency.

The combined recipient count across to, cc, and bcc is capped at 50 per request. Each address may be a bare addr or a Name <addr> pair.

From-address rules

The domain part of from must belong to your organization, be active (DNS verified), and have sending enabled. Otherwise the request is rejected with 403 domain_not_verified. There are no shared or sandbox sending domains — you always send from a domain you own.

Attachments

Attach files inline as base64. Each entry is { filename, content, contentType? } — the decoded total across all attachments must stay under 10 MB, with at most 20 files.

json
{
  "from": "billing@yourco.com",
  "to": "ava@client.io",
  "subject": "Your March invoice",
  "text": "Invoice attached.",
  "attachments": [
    {
      "filename": "invoice-1042.pdf",
      "content": "JVBERi0xLjQKJ...",     // base64
      "contentType": "application/pdf"    // optional; inferred if omitted
    }
  ]
}

Idempotency

Network timeouts happen mid-request. To retry safely, include an idempotency_key field — any unique string per logical message. If a send with that key was already accepted for your organization, the original message id is returned and no second email is sent.

shell
curl -X POST https://api.mail.atrix.dev/v1/emails \
  -H "Authorization: Bearer $ATRIX_MAIL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "billing@yourco.com",
    "to": ["ava@client.io"],
    "subject": "Your March invoice",
    "text": "Attached.",
    "idempotency_key": "invoice-1042"
  }'
idempotency_key is a field in the JSON body, not an HTTP header. Reuse the exact same key on every retry of the same message.

Errors

Errors are JSON with a stable machine-readable code and a human message. Validation errors add a fields map keyed by the offending field:

json
{
  "error": {
    "code": "validation_error",
    "message": "Validation failed",
    "fields": {
      "html": "Either html or text body is required"
    }
  }
}
StatusCodeMeaning
422validation_errorBody failed schema validation; fields lists what.
401unauthorizedMissing, malformed, or revoked API key.
403forbiddenKey lacks the email.send scope.
403domain_not_verifiedThe from domain isn't verified/send-enabled on your account.
403org_suspendedYour organization is suspended from sending.
422suppressed_recipientA recipient is on your suppression list; details lists them.
429rate_limitedOver your plan's send budget. Honour the Retry-After header.
404not_foundUnknown message id (on GET /v1/emails/:id).
500internal_errorOur fault. Safe to retry with the same idempotency_key.
Treat 429 and 5xx as retryable with exponential backoff; treat every other 4xx as a bug in the request, not something to retry. See Rate limits & suppression.