# Event types

Source: https://docs.simail.ai/event-types/

> Every simail event type, the thin payload shared by polling, webhooks and WebSocket, and which events wake your agent.

## Payload

Payloads are **thin** and identical on every channel ([polling](/events/), [webhooks](/webhooks/),
[WebSocket](/realtime/)). They never include the subject, body or attachment names; fetch the message
with your key when you need content (held messages stay metadata-only).

```json
{
  "event_id": "evt_…",
  "event_type": "message.received",
  "occurred_at": "2026-10-18T17:02:12Z",
  "organization_id": "org_…",
  "pod_id": "pod_…",
  "inbox_id": "shop-helper@simail.email",
  "thread_id": "thr_…",
  "message_id": "msg_…",
  "data": { "from": "ada@example.com", "labels": ["received", "unread"], "held": false, "classification": "normal", "wake": true }
}
```

| Field | Notes |
|---|---|
| `event_id` | unique; use it to de-duplicate (webhooks send it as `webhook-id`) |
| `occurred_at` | ISO 8601, UTC |
| `pod_id`, `inbox_id`, `thread_id`, `message_id` | present when the event is about that object |
| `data` | small, type-specific fields (below) |

## Types

| Type | When | `data` |
|---|---|---|
| `message.received` | a message was stored | `from`, `labels`, `held`, `classification`, `wake` |
| `message.received.spam` | a message classified as spam was stored | same |
| `message.received.blocked` | a message was stored with the `blocked` label | same |
| `message.held` | a message was held for human review (metadata only) | `from`, `hold_reasons` |
| `message.released` | a human released a held message | `from`, `wake` |
| `message.sent` | an outbound message was handed to the mail network | `send_id` |
| `message.delivered` | the recipient's server accepted it | `recipient` |
| `message.bounced` | it bounced | `recipient`, `bounce_type` (`hard` or `soft`) |
| `message.complained` | the recipient marked it as spam | `recipient` |
| `message.rejected` | the sending network rejected it | `recipient` |
| `draft.submitted` | a draft was submitted for sending | `draft_id`, `send_id` |
| `approval.requested` | a send is waiting for approval | `approval_id`, `send_id`, `flags` |
| `approval.decided` | an approver approved or rejected it, or it expired | `approval_id`, `send_id`, `decision` |
| `send.blocked` | a send was blocked | `send_id`, `reasons` |
| `send.cancelled` | a scheduled or pending send was cancelled | `send_id` |
| `send.deferred` | a send is waiting (pause, lock or daily limit) and will be retried | `send_id`, `reason` |
| `thread.locked` | a human took over a thread | |
| `thread.unlocked` | the human released it | |
| `label.added` | a label was added | `label` |
| `label.removed` | a label was removed | `label` |
| `inbox.digest` | daily counts of events that did not wake your agent | `counts` |
| `inbox.created` | an inbox was created | |
| `inbox.deleted` | an inbox was deleted | |
| `org.suspended` | sending was paused for the organization | `reason` |
| `org.resumed` | sending resumed | |

## Waking your agent

Every `message.received` (and `message.released`) event has `data.wake`. It is `true` when the
message belongs to a thread you are part of, comes from a contact of the inbox, or matches your
receive allow list. Mail from strangers wakes your agent only up to a budget per inbox per day
(`inbound.wake.stranger_daily`, 50 by default) and per sender per hour (`inbound.wake.per_sender_hourly`,
5 by default). Beyond that, events are still stored and returned by `GET /v0/events`, but webhooks and
WebSocket skip them unless the subscription sets `include_non_wake: true`. A daily `inbox.digest`
event reports what was skipped. Held mail never wakes.

This keeps a flood of mail from turning into a flood of model calls.
