# Realtime (WebSocket)

Source: https://docs.simail.ai/realtime/

> Stream simail events over a WebSocket from a server-side agent; subscribing, limits, revocation and reconnecting without gaps.

Long-running agents can keep one connection open and receive events as they happen.

`GET wss://api.simail.ai/v0/ws`

Needs `event_read`. Authenticate the upgrade request with `Authorization: Bearer <key>`. This is for
**server-side** agents only: never put an API key in a browser.

```ts
import WebSocket from "ws";

const ws = new WebSocket("wss://api.simail.ai/v0/ws", {
  headers: { Authorization: `Bearer ${process.env.SIMAIL_API_KEY}` },
});
ws.on("open", () => {
  ws.send(JSON.stringify({ type: "subscribe", inbox_ids: ["shop-helper@simail.email"], event_types: ["message.received", "thread.locked"] }));
});
ws.on("message", (raw) => {
  const msg = JSON.parse(raw.toString());
  if (msg.type === "event") handle(msg.event); // de-duplicate on msg.event.event_id
});
ws.on("close", (code) => scheduleReconnect(code));
```

## Messages

Client to server, optional, any time:

```json
{ "type": "subscribe", "inbox_ids": ["shop-helper@simail.email"], "event_types": ["message.received"], "include_non_wake": false }
```

A subscription can only narrow what your key may see. Without one you get every event in your key's
scope that woke your agent.

Server to client:

```json
{ "type": "event", "event": { "event_id": "evt_…", "event_type": "message.received", "occurred_at": "2026-10-18T17:02:12Z", "inbox_id": "shop-helper@simail.email", "thread_id": "thr_…", "message_id": "msg_…", "data": { "wake": true } } }
```

The `event` object is the same thin payload as everywhere else; see [Event types](/event-types/).
Events that did not wake your agent are skipped unless you subscribe with `include_non_wake: true`.

## Lifetime and limits

- A connection lives **at most 1 hour** (less if the key expires sooner). Then it closes and you
  reconnect, which re-checks the key.
- Revoking or narrowing the key, removing the member, signing out a console session or pausing the
  organization closes affected connections at once with code **`4401`** (`revoked`).
- 50 connections per organization, 10 per key, 10 handshakes per minute.
- A client that falls more than 1 MiB behind is disconnected.
- Pings are answered by the server without waking anything; idle connections are cheap.

## Reconnecting without gaps

The WebSocket is a fast path, not a log. To never miss an event, remember the `event_id` of the last
event you handled and, after reconnecting, catch up with [`GET /v0/events`](/events/) using your stored
cursor. De-duplicate on `event_id`. Back off between reconnects (for example 1 s, 2 s, 4 s … up to
60 s) and do not reconnect after `4401` with the same key: it was revoked or narrowed.
