# 轮询事件

Source: https://docs.simail.ai/zh/events/

> 用 GET /v0/events 和游标读到每一条事件；不需要公网地址。游标、过滤、保留期和轮询循环。

`GET /v0/events` 是对邮件做出反应的最简单办法：没有公网 URL，不用验签名，agent 离线时也不会漏（事件保留 30 天）。它返回的薄载荷与 [webhook](/zh/webhooks/) 和 [WebSocket](/zh/realtime/) 相同；见[事件类型](/zh/event-types/)。

`GET /v0/events`

需要 `event_read`。结果限于 key 的作用域：邮箱作用域的 key 只看得到自己邮箱的事件。

| 查询参数 | 说明 |
|---|---|
| `after` | 上一页响应里的游标；第一次调用省略它，从仍保留的最早事件开始 |
| `types` | 逗号分隔的事件类型，例如 `message.received,thread.locked` |
| `inbox_ids` | 逗号分隔的、在你作用域内的邮箱 ID |
| `limit` | 1–200，默认 50 |

```sh
curl -s "https://api.simail.ai/v0/events?types=message.received&limit=100" \
  -H "Authorization: Bearer $SIMAIL_API_KEY"
```

```json
{
  "count": 1,
  "limit": 100,
  "events": [
    { "event_id": "evt_…", "event_type": "message.received", "occurred_at": "2026-10-18T17:02:12Z", "organization_id": "org_…", "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 } }
  ],
  "next_cursor": "c_…"
}
```

记下 `next_cursor`，下次当作 `after` 传入。事件按记录顺序给出。轮询返回**每一条**事件，包括没有唤醒 agent 的（`data.wake: false`）；如果只要会唤醒的，自己过滤。

## 游标

- 游标不透明且有签名。它绑定你的组织、key 的作用域和你用过的过滤条件；换一把 key 或其他过滤条件会返回 `400 invalid_request`。
- 事件保留 30 天。比这更老的游标返回 `410 gone`：去掉 `after` 重新开始，并用列出线程来对账。
- 投递至少一次。按 `event_id` 去重。

## 轮询循环

```ts
let cursor: string | undefined = await loadCursor();
for (;;) {
  const url = new URL("https://api.simail.ai/v0/events");
  url.searchParams.set("types", "message.received,thread.locked,thread.unlocked");
  url.searchParams.set("limit", "100");
  if (cursor) url.searchParams.set("after", cursor);
  const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.SIMAIL_API_KEY}` } });
  if (res.status === 429) { await sleep(Number(res.headers.get("retry-after") ?? 5) * 1000); continue; }
  const page = await res.json();
  for (const event of page.events) await handle(event); // idempotent on event.event_id
  if (page.next_cursor) { cursor = page.next_cursor; await saveCursor(cursor); }
  if (page.count < 100) await sleep(5000);
}
```

先处理事件再保存游标，这样崩溃会重放事件，而不是丢掉它们。

## 单个邮箱的事件

`GET /v0/inboxes/{inbox_id}/events`

列出一个邮箱的事件，参数相同（没有 `inbox_ids`）。需要 `event_read`。

MCP 工具 `list_events` 接受同样的 `after`、`types`、`inbox_ids` 和 `limit`。
