HTTP Send API
Send email over HTTP from any language or runtime.
Replace example addresses with your enrolled sending address and a real recipient address you control. The relay rejects IANA example domains such as example.com as recipients.
Endpoint
Section titled “Endpoint”POST https://smtp.atmos.email/v1/sendAuthentication
Section titled “Authentication”Pass your DID and API key in the request headers:
X-Atmos-DID: did:plc:your-did-hereAuthorization: Bearer atmos_your-api-keyYou can also pass the DID as a ?did= query parameter.
The key must be allowed to send from the domain in the message’s from address. See creating and limiting API keys.
Request body
Section titled “Request body”JSON with Content-Type: application/json. Maximum 1 MiB.
{ "from": "you@yourdomain.com", "to": "recipient@example.com", "subject": "Hello from comail", "text": "Plain text body", "html": "<p>HTML body (optional)</p>", "replyTo": "replies@yourdomain.com", "headers": { "X-Custom": "value" }, "category": "login-link"}| Field | Type | Required | Description |
|---|---|---|---|
from |
string | yes | Sender address. Must use an enrolled domain. |
to |
string or array | yes | One recipient or an array of recipients. |
subject |
string | yes | Email subject line. |
text |
string | yes* | Plain text body. Required unless html is set. |
html |
string | no | HTML body. The relay sends it as a multipart alternative alongside text. |
replyTo |
string | no | Reply-To address. |
headers |
object | no | Additional headers. The relay reserves From, To, Subject, and DKIM-related headers. |
category |
string | no | One of login-link, password-reset, mfa-otp, verification, bulk, broadcast. The first four categories identify user-initiated transactional messages. They omit List-Unsubscribe and bypass suppressions caused only by an unsubscribe. Other suppression sources still block delivery. bulk and broadcast both add List-Unsubscribe and enforce the suppression list. An untagged message gets no List-Unsubscribe header, but the suppression list still applies. If you include your own List-Unsubscribe or List-Unsubscribe-Post header (via the headers field) on a bulk or broadcast message, comail detects it and does not inject its own. You own the pair and are responsible for a valid RFC 8058 combination. CR/LF characters in any header-bound field (from, subject, replyTo, category, custom header names or values, recipient addresses) are rejected with INVALID_REQUEST. |
Response
200 on success:
{ "accepted": [ { "recipient": "user@example.com", "messageId": 448 } ], "rejected": []}Errors return 4xx/5xx with:
{ "error": "description of what went wrong", "code": "ERROR_CODE"}| Code | HTTP | Meaning |
|---|---|---|
AUTH_REQUIRED |
400 / 401 | Missing DID (X-Atmos-DID header or ?did= query) or missing Bearer API key. |
INVALID_REQUEST |
400 / 413 | Malformed JSON, invalid DID format, missing required fields, header injection (CR/LF in from/subject/etc.), body over 1 MiB, more than 50 recipients. |
AUTH_FAILED |
401 | API key does not match any enrolled domain on this DID. |
SUSPENDED |
403 | Member is suspended. |
PENDING |
403 | Member is pending approval. |
DOMAIN_MISMATCH |
403 | From address domain is not enrolled under this DID. |
LABEL_DENIED |
403 | Member’s reputation label blocks sending. |
INVALID_RECIPIENT_DOMAIN |
422 | A recipient uses an IANA example domain that cannot receive mail. Remove or correct the address; do not retry unchanged. |
RATE_LIMITED |
429 | Warming tier or per-member rate limit exceeded. Retry later. |
QUEUE_FULL |
503 | Submission queue is at capacity. Retryable. |
TEMPORARILY_UNAVAILABLE |
503 | Internal dependency unavailable. Retryable. |
INTERNAL_ERROR |
500 | Server-side error. Retryable; check status. |
Examples
Section titled “Examples”curl -X POST https://smtp.atmos.email/v1/send \ -H "Content-Type: application/json" \ -H "X-Atmos-DID: did:plc:your-did" \ -H "Authorization: Bearer atmos_your-api-key" \ -d '{ "from": "you@yourdomain.com", "to": "friend@example.com", "subject": "Hello", "text": "Sent via comail" }'JavaScript (fetch)
Section titled “JavaScript (fetch)”const res = await fetch("https://smtp.atmos.email/v1/send", { method: "POST", headers: { "Content-Type": "application/json", "X-Atmos-DID": "did:plc:your-did", "Authorization": "Bearer atmos_your-api-key", }, body: JSON.stringify({ from: "you@yourdomain.com", to: "friend@example.com", subject: "Hello", text: "Sent via comail", }),});const data = await res.json();Python
Section titled “Python”import requests
resp = requests.post( "https://smtp.atmos.email/v1/send", headers={ "X-Atmos-DID": "did:plc:your-did", "Authorization": "Bearer atmos_your-api-key", }, json={ "from": "you@yourdomain.com", "to": "friend@example.com", "subject": "Hello", "text": "Sent via comail", },)print(resp.json())- The relay applies DKIM signatures for your domain and
d=atmos.email. See DNS records for what gets signed. - Messages tagged
bulkorbroadcastgetList-UnsubscribeandList-Unsubscribe-Postheaders per RFC 8058 (the Gmail/Yahoo one-click format). One-click unsubscribes add the recipient to your suppression list. If you already include your ownList-UnsubscribeorList-Unsubscribe-Postheader via theheadersfield, comail detects it and does not inject its own. You own the pair and are responsible for a valid RFC 8058 combination:List-Unsubscribemust contain an HTTPS URI (amailto:URI may additionally be present) andList-Unsubscribe-Postmust be exactlyList-Unsubscribe=One-Click. Suppression-list enforcement stays active either way. - The four user-initiated categories (
login-link,password-reset,mfa-otp,verification) omit the List-Unsubscribe header and bypass suppressions caused only by an unsubscribe. Suppressions from bounces, complaints, operator actions, and unknown sources still apply. Use these only for messages the recipient just triggered. - Untagged messages get no List-Unsubscribe header, but suppression-list checks still apply. The strict policy in the AUP treats untagged as
bulkfor compliance purposes; tag explicitly to make intent legible. - The relay returns int64
messageIdvalues. If your integration assumes string IDs, convert. - New domains hit warming tier rate limits during the first 14 days.
- The
fromaddress must use a domain enrolled under the authenticating DID. See Identity and sending domains. - Accepted messages are fsynced before the response. The relay retries failed deliveries. See Reliability for the retry schedule and status vocabulary.
- See Troubleshooting for common errors.