Events
The event stream is every delivery and engagement event for your sent mail, queryable on demand. Where a webhook pushes each event to you as it happens, the events API lets you pull a window of history back whenever you need it.
Events vs webhooks
Both surfaces carry the same event types. They differ in how you consume them.
- Webhooks push. Anypost POSTs each event to your endpoint as it happens. Best for reacting in real time. See Webhooks.
- Events pull. You query the stored stream when you want it. Best for backfilling a gap, reconciling your own records, or one-off lookups.
The two compose. When a webhook endpoint is down long enough to be disabled, events that occur while it is disabled are not queued. Page the events API once the endpoint is healthy to recover that gap.
Event types
Only customer-facing events are returned. Operational events are excluded.
| Type | Emitted when |
|---|---|
email.sent | Anypost accepted the message and handed it to the outbound mail system. |
email.delivered | The receiving server accepted the message. |
email.delayed | A delivery attempt failed temporarily and will be retried. |
email.bounced | Delivery ended without reaching the recipient: the receiving server refused the address, or the message ran out its retry window. |
email.complained | The recipient marked the message as spam. |
email.suppressed | The send was dropped because the address is on your suppression list. |
email.unsubscribed | The recipient used a one-click unsubscribe link. |
email.opened | The recipient opened the message. |
email.clicked | The recipient clicked a tracked link. |
Listing events
GET /v1/events returns events newest-first, with cursor pagination. The
TypeScript list returns a page you can iterate to walk every page:
curl https://api.anypost.com/v1/events \
-H "Authorization: Bearer $ANYPOST_API_KEY"Events are read-only and not addressable by id — there is no GET /v1/events/{id}.
Time window
The start and end parameters take ISO 8601 timestamps. The default window
is the last 24 hours. The window is clamped to your plan's event retention —
3, 7, or 30 days. A request for 30 days of history on a 3-day-retention plan
silently narrows to the last 3 days rather than erroring, so the same call
keeps working as you upgrade.
Filters
All filters are exact-match, except tags. A value that matches no stored row
returns an empty page; there is no substring search.
| Parameter | Restricts to |
|---|---|
event_type | One of the nine event types above. |
recipient | One exact recipient address, lowercased server-side. |
email_id | One message's email_<uuidv7> id. |
message_id | An exact Message-ID: header match. |
domain | A sending domain hostname (not the domain_<uuid> id). An unknown domain returns 400. |
topic | The send-time topic the message was tagged with. |
campaign | The send-time campaign value. Case-sensitive. |
template_id | The template the originating send used. |
ip_pool | Mail that egressed from one of your named IP pools. Dedicated IPs only; returns an empty page otherwise. |
tags | Events carrying any of the given tags. Pass comma-separated (tags=a,b); up to 10. |
Pass several at once to narrow further. To trace one message end to end, filter
by its email_id:
curl -G https://api.anypost.com/v1/events \
-H "Authorization: Bearer $ANYPOST_API_KEY" \
--data-urlencode "email_id=email_019e1972-e87e-7000-bf74-ba09e0ed0d62" \
--data-urlencode "limit=100"The event object
Every event carries the same fields. The ones that do not apply to a given type
are null rather than omitted: smtp_code is null on an open, bounce_type
and bounce_classification are set only on email.bounced, attempt is null
on non-delivery events, and tracking is null except on opens and clicks. The
Events API reference lists every field.
bounce_type separates the three ways a send can end in a bounce. permanent
is a 5xx refusal from the receiving server, and is the only one that suppresses
the address. transient is a temporary failure reported on a final delivery
report. expired means no verdict ever arrived: the message retried for 72
hours and was dropped from the queue, which points at the receiving side being
down or throttling rather than at a bad address. Filter on
bounce_type = "permanent" when you are measuring list quality.
The correlation dimensions — tags, topic, campaign, and template_id —
travel onto every event for a message, engagement events included, so you can
measure a campaign or template at open and click time, not just at send.
ip_pool is not one of those dimensions. It records which of your dedicated IP
pools carried the message rather than labeling it, and it travels onto every
event for the same reason: a bounce or complaint rate per pool is the number
that tells you whether one stream's reputation is slipping. It is null on
accounts sending from the shared pool, and on any send that named no pool.
On an open or click, a non-null tracking object carries a bot block when
the interaction came from a mailbox image proxy rather than the recipient's own
client. Machine traffic that is pure noise — prefetchers and scanners — never
becomes an event at all, so it cannot inflate a count here, in the dashboard,
or on your webhooks. See Open & click tracking for the
full model.
Where to go next
- Webhooks: the same events, pushed to your endpoint in real time.
- Open & click tracking: what
email.openedandemail.clickedcapture. - Tags, topics & campaigns: the dimensions you filter events by.