Skip to content

Event stream

The event stream is a WebSocket that carries your account’s events the moment they happen: the same events, in the same shape, as webhooks. Use it when your code is running and wants to react at once, without a public URL for webhooks to call.

A signed-in session can open it on every plan. An API key can open it on Operator and above, which is every plan with API keys.

GET /accounts/live, upgraded to a WebSocket. Put the token in the subprotocol list: offer mailcycle.v1 first and the session token or API key second. The server answers with mailcycle.v1.

const socket = new WebSocket('wss://api.mailcycle.email/accounts/live', ['mailcycle.v1', process.env.MAILCYCLE_API_KEY]);
socket.onmessage = (message) => {
if (message.data === 'pong') return;
const event = JSON.parse(message.data);
if (event.type === 'error') return console.error(event.code, event.message);
console.log(event.type, event.payload);
};
// Keep the connection alive through NATs and proxies.
setInterval(() => socket.send('ping'), 4 * 60_000);

Each event is one JSON frame, exactly the body a webhook would get:

{
"id": "evt_4k2m9x7q1b8c3v6n0z5a",
"type": "message.received",
"createdAt": "2026-09-19T09:14:03.000Z",
"payload": { "messageId": "msg_91be02c4a7d1", "inboxId": "ibx_k3v9x2mq7a" }
}

The stream carries every event type. There is no filter, and no webhook.test. Events carry ids, never content: fetch a message by id and decrypt it in your client.

The connection is receive-only. Send ping and the server answers pong; anything else is ignored.

Close codeWhyWhat to do
4001The session signed out, was ended or expired, the API key was revoked, the token was not valid, or the account was suspended or deleted.Sign in again or use a new key before reconnecting.
4003The plan does not include the stream for API keys, or stopped including it.Upgrade, or use a session.
4000The account has 50 sockets open and this was the oldest.Close sockets you no longer need.
Anything elseA network drop or a server restart.Reconnect with backoff.

When the server refuses a connection, it accepts the upgrade just long enough to send one frame saying why, { "type": "error", "code": …, "message": … }, and then closes with the code above.

The stream is live, not durable. Events that happen while you are disconnected are not replayed, so after reconnecting, fetch what you show again. If you need every event, even while your code is down, use webhooks, which retry for about a day and a half.

Paired devices have their own live connection, which carries only what concerns that device.

© Northlab Studios Ltd, trading as Mailcycle. Registered in England and Wales, company no. 17462734. Registered office: 167-169 Great Portland Street, London W1W 5PF.