Events
Reference of every event type SES emits and we forward to webhooks.
Events are immutable records of something that happened to an
email. They live on email_events server-side and feed both the
Email events timeline in the dashboard AND outbound webhook
subscriptions.
Event types
| Type | Triggered when |
|---|---|
sent | SES accepted the message for delivery (handoff complete). |
delivered | The receiving mail server accepted the message (most positive outcome you can verify). |
opened | A tracking pixel fired. Estimate only — many clients block images. Requires open tracking ON. |
clicked | A tracked link in the body was followed. Requires click tracking ON. |
bounced | The receiving mail server rejected the message. Soft (transient) vs hard (permanent) in payload. |
complained | The recipient marked the message as spam. Treat as a strong "stop sending" signal. |
rejected | SES itself rejected the message before delivery (rare — usually means we sent something invalid). |
rendering_failed | Template render failed. Check variables against the template body. |
delivery_delayed | SES couldn't deliver yet (greylist, soft bounce, queue depth) — will retry. |
Payload shape
Every event row has the same shape regardless of type:
{
"id": "evt_2bV4dXyZ8AfQpkw7Lp",
"emailId": "em_...",
"type": "delivered",
"eventTimestamp": "2026-05-22T09:14:32.500Z",
"createdAt": "2026-05-22T09:14:32.700Z",
"payloadJson": "..."
}payloadJson is the raw SES event JSON, stringified. Field
structure differs by type — bounces carry bounce.bounceType +
bounceSubType, complaints carry complaint.complaintFeedbackType,
etc. We don't normalise these — when you need them, parse the
JSON.
Listing events directly via REST isn't supported — they're attached to their parent email via GET /emails/:id. Subscribe to a webhook for push delivery.
Webhook payload
When forwarded to a webhook, the event is wrapped:
{
"id": "wd_...",
"type": "email.event",
"createdAt": "2026-05-22T09:14:32.700Z",
"data": {
"emailId": "em_...",
"event": { "eventId": "evt_..." }
}
}The X-Zerokit-Event-Type header carries the underlying event
type (delivered, bounced, …) so you can route without parsing
the body.
Idempotency
Events are de-duplicated server-side using the SNS message ID as a
unique key. SNS occasionally re-delivers the same event on
transient 5xx; we swallow the duplicate insert via the partial
unique index on provider_event_id.
Webhook deliveries are NOT deduplicated automatically — same event
that takes 3 retries will fire 3 POSTs at your endpoint with the
same X-Zerokit-Delivery-Id. Use that header for client-side
dedup if you process events more than once.
Send email POST
Queue a transactional email for delivery. Returns immediately with the email ID — SES handoff happens in the background. Subscribe to a [webhook](/docs/webhooks) to learn about delivery, bounces, opens, and clicks. The `from` domain must be verified for the workspace — see the [Quickstart](/docs/quickstart) for the DNS records.
Errors
Status code + error code catalog across every endpoint.