HTTP Send API
Send email over HTTP from any language or runtime.
Endpoint
POST https://smtp.atmos.email/v1/send
Authentication
Pass your DID and API key in the request headers:
X-Atmos-DID: did:plc:your-did-here
Authorization: Bearer atmos_your-api-key
You can also pass the DID as a ?did= query parameter.
Request body
JSON with Content-Type: application/json. Maximum 1 MiB.
{
"from": "you@yourdomain.com",
"to": "recipient@example.com",
"subject": "Hello from Atmosphere",
"text": "Plain text body",
"html": "<p>HTML body (optional)</p>",
"replyTo": "replies@yourdomain.com",
"headers": { "X-Custom": "value" },
"category": "transactional"
}
| 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 | "transactional" skips List-Unsubscribe. Default is bulk. |
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 | Missing DID or API key. |
INVALID_DID |
400 | DID is not a valid did:plc: or did:web:. |
AUTH_FAILED |
401 | API key does not match. |
MEMBER_SUSPENDED |
403 | Membership is suspended. |
DOMAIN_MISMATCH |
403 | From address domain is not enrolled under this DID. |
RATE_LIMITED |
429 | Warming tier rate limit exceeded. Retry later. |
INVALID_REQUEST |
400 | Malformed JSON or missing required fields. |
Examples
curl
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 Atmosphere Mail"
}'
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 Atmosphere Mail",
}),
});
const data = await res.json();
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 Atmosphere Mail",
},
)
print(resp.json())
Notes
- The relay applies DKIM signatures for your domain and
d=atmos.email. See DNS records for what gets signed. - Non-transactional mail gets
List-UnsubscribeandList-Unsubscribe-Postheaders per RFC 8058 (the Gmail/Yahoo one-click format). One-click unsubscribes add the recipient to your suppression list. - Set
"category": "transactional"to skip the List-Unsubscribe header. Use this only for password resets, receipts, and similar one-to-one messages a recipient initiated. - 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.
- Stuck? See Troubleshooting.