WebSocket
Subscribe to real-time inbox events and campaign status updates via WebSocket.
Overview
MisarMail exposes a WebSocket server at wss://api.misar.io/mail/ws/* for real-time event delivery. Two channels are available: inbox (user events) and campaign status (per-campaign progress).
Authentication
Authenticate with your API key (msk_...) via the Authorization: Bearer header (Node.js) or ?token= query parameter (browser). WebSocket connections from browsers cannot set Authorization headers.
Channels
| Channel | Path | Description |
|---|---|---|
| Inbox | wss://api.misar.io/mail/ws/inbox | Real-time inbox events for the authenticated user |
| Campaign status | wss://api.misar.io/mail/ws/campaigns/:id | Live campaign delivery progress |
Inbox channel
wss://api.misar.io/mail/ws/inbox?token=msk_your_api_key
Event types
type | Description |
|---|---|
email_received | New inbound email received |
bounce | An outbound email bounced |
complaint | Spam complaint received |
unsubscribe | A contact unsubscribed |
campaign_started | Campaign send began |
campaign_completed | Campaign send finished |
Example events
{
"type": "email_received",
"from": "[email protected]",
"subject": "Re: Your email",
"receivedAt": "2026-05-27T10:15:00Z"
}{
"type": "campaign_completed",
"campaignId": "uuid",
"campaignName": "May Newsletter",
"sent": 10000,
"delivered": 9855,
"opened": 2340,
"completedAt": "2026-05-27T11:00:00Z"
}Campaign status channel
wss://api.misar.io/mail/ws/campaigns/:id?token=msk_your_api_key
Ownership verified
The server checks that the campaign belongs to the authenticated user before upgrading the WebSocket connection. An unauthenticated or unauthorized request receives HTTP 403 before the WebSocket handshake completes.
Event types
type | Description |
|---|---|
progress | Periodic send progress update |
completed | Campaign send finished |
failed | Campaign send failed |
cancelled | Campaign was cancelled |
Example events
{
"type": "progress",
"campaignId": "uuid",
"sent": 2500,
"total": 10000,
"delivered": 2480,
"bounced": 20,
"opened": 450
}{
"type": "completed",
"campaignId": "uuid",
"sent": 10000,
"delivered": 9855,
"bounced": 145,
"opened": 2340
}TypeScript example
import WebSocket from "ws";
const ws = new WebSocket(
`wss://api.misar.io/mail/ws/campaigns/${campaignId}`,
{ headers: { Authorization: `Bearer msk_your_api_key` } }
);
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.type === "progress") {
const pct = Math.round((event.sent / event.total) * 100);
console.log(`Progress: ${pct}% (${event.sent}/${event.total})`);
}
if (["completed", "failed", "cancelled"].includes(event.type)) {
ws.close();
}
});const ws = new WebSocket(
`wss://api.misar.io/mail/ws/inbox?token=${apiKey}`
);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "email_received") {
showNotification(`New email from ${data.from}`);
}
};Heartbeat
The server sends a ping frame every 30 seconds. The client must respond with a pong. Unresponsive connections are closed after 30 seconds with code 1001.
Close codes
| Code | Meaning |
|---|---|
1000 | Normal closure |
1001 | Server restart or heartbeat timeout |
4401 | Unauthorized — missing or invalid API key |
4403 | Forbidden — campaign does not belong to this account |