HTTP Events API
Pull your account’s delivery events over HTTP. This is the read counterpart to Webhooks: the same event vocabulary, fetched on your schedule instead of pushed to an endpoint.
Endpoint
Section titled “Endpoint”GET https://smtp.atmos.email/v1/eventsAuthentication
Section titled “Authentication”Identical to the Send API. 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. Every event returned is scoped to the authenticating DID. There is no parameter that widens the scope to another account.
Query parameters
Section titled “Query parameters”| Param | Type | Default | Description |
|---|---|---|---|
limit |
integer | 50 | Page size. Values above 200 are clamped to 200. Must be a positive integer. |
cursor |
string | none | Opaque pagination token from a previous response’s nextCursor. Fetches the next (older) page. |
type |
string | none | Filter to a single event type. One of message.delivered, message.bounced, message.complained, domain.verified. Omit to return all types. |
since |
string | none | RFC3339 timestamp. Returns only events at or after this time. |
Event types
Section titled “Event types”The pull API covers message delivery events and domain verification. Webhooks additionally carry account, domain, effective-policy, rejection, and throttling events because those are low-latency state notifications rather than delivery-history rows.
| Type | Meaning |
|---|---|
message.delivered |
A message you sent reached the recipient’s mail server. |
message.bounced |
A message you sent was rejected by the recipient’s mail server. |
message.complained |
A recipient marked one of your messages as spam (feedback loop report). |
domain.verified |
A sending domain finished verification. |
Response
Section titled “Response”200 on success:
{ "events": [ { "id": "evt_1042", "type": "message.delivered", "timestamp": "2026-06-08T17:04:11Z", "domain": "yourdomain.com", "recipientDomain": "gmail.com", "smtpCode": 250 } ], "nextCursor": "MjAyNi0wNi0wOFQxNzowNDoxMVp8MTA0Mg"}| Field | Type | Description |
|---|---|---|
id |
string | Stable identifier for the event. |
type |
string | One of the event types above. |
timestamp |
string | RFC3339 time the event occurred. |
domain |
string | Your sending domain the event belongs to. |
recipientDomain |
string | The recipient’s domain, when the event carries one. |
smtpCode |
integer | The recipient server’s SMTP response code, on delivery and bounce events. |
Events are returned newest first, ordered by timestamp then id.
Pagination
Section titled “Pagination”Pagination is cursor based, not offset based, so a steady stream of new events never shifts a page boundary and causes you to miss or repeat a row.
- Make the first request with a
limitand nocursor. - If the response includes a non-null
nextCursor, pass it back as?cursor=to fetch the next page. - When
nextCursorisnull, you have reached the end.
The cursor is opaque. Do not parse or construct it. A malformed cursor returns INVALID_REQUEST (400).
To poll for new events, record the timestamp of the newest event you have seen and pass it as since on the next poll. The filter includes that timestamp, so deduplicate events by id.
Errors return 4xx/5xx with the same shape as the Send API:
{ "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 | Invalid DID format, unknown type, bad since, non-positive limit, or malformed cursor. |
AUTH_FAILED |
401 | API key does not match any enrolled domain on this DID. |
METHOD_NOT_ALLOWED |
405 | Use GET. |
INTERNAL_ERROR |
500 | Server-side error. Retryable. |
Examples
Section titled “Examples”curl "https://smtp.atmos.email/v1/events?limit=50" \ -H "X-Atmos-DID: did:plc:your-did" \ -H "Authorization: Bearer atmos_your-api-key"Filter to bounces since a point in time:
curl "https://smtp.atmos.email/v1/events?type=message.bounced&since=2026-06-01T00:00:00Z" \ -H "X-Atmos-DID: did:plc:your-did" \ -H "Authorization: Bearer atmos_your-api-key"JavaScript (fetch)
Section titled “JavaScript (fetch)”async function* allEvents(did, apiKey) { let cursor = null; do { const url = new URL("https://smtp.atmos.email/v1/events"); url.searchParams.set("limit", "200"); if (cursor) url.searchParams.set("cursor", cursor); const res = await fetch(url, { headers: { "X-Atmos-DID": did, "Authorization": `Bearer ${apiKey}`, }, }); const page = await res.json(); yield* page.events; cursor = page.nextCursor; } while (cursor);}Python
Section titled “Python”import requests
def all_events(did, api_key): cursor = None while True: params = {"limit": 200} if cursor: params["cursor"] = cursor resp = requests.get( "https://smtp.atmos.email/v1/events", headers={ "X-Atmos-DID": did, "Authorization": f"Bearer {api_key}", }, params=params, ) page = resp.json() yield from page["events"] cursor = page["nextCursor"] if cursor is None: break- The events vocabulary is the same as Webhooks. Use webhooks for low-latency push, the events API for backfill, reconciliation, or when running an endpoint is not practical.
- A bounce event carries the recipient server’s
smtpCode. Codes at 500 and above are permanent (hard) bounces; see Bounces and suppression. - The API returns at most 200 events per request. Walk the
nextCursorto read more. - See Troubleshooting for common errors.