6. Push notifications¶
JMAP supports three push transports — the server emits a StateChange event
whenever any account's state bumps, and clients pick the transport that
matches their environment.
Server-Sent Events (RFC 8620 §7.3)¶
A long-lived HTTP connection that streams events. Best for browsers and any HTTP-only client.
curl -N -H "Authorization: Bearer $TOKEN" \
'http://localhost:8000/jmap/eventsource?types=*&closeafter=no&ping=30'
You'll see:
id: 5f2a9c
event: ping
data: {}
id: 8e1d3b
event: state
data: {"@type":"StateChange","changed":{"ACC":{"Todo":"4"}}}
Query params:
types=*— subscribe to every data type. Pass a comma-separated list to filter (types=Todo,Note).closeafter=state— close the connection after the firststateevent (useful for one-shot sync). Defaultnokeeps it open.ping=30— heartbeat interval in seconds (lets clients detect dead connections through proxies).
WebSocket (RFC 8887)¶
For clients that already speak WS. Two-way: clients can send JMAP requests through the same connection.
const ws = new WebSocket('wss://localhost:8000/jmap/ws', ['jmap']);
ws.onopen = () => {
ws.send(JSON.stringify({
"@type": "WebSocketPushEnable",
dataTypes: ["Todo"]
}));
};
ws.onmessage = (e) => console.log(JSON.parse(e.data));
Send {"@type": "Request", "id": "r0", "using": [...], "methodCalls": [...]}
to make a JMAP call. Responses come back as {"@type": "Response", "requestId": "r0", ...}.
Webhooks (RFC 8620 §7.2)¶
Register a webhook URL, the server POSTs StateChange events to it. Best for
backend services that prefer "push to me" over "I'll poll".
Register via JMAP:
["PushSubscription/set", {
"create": {
"p1": {
"deviceClientId": "my-service",
"url": "https://my-service.example/webhook",
"types": ["Todo"]
}
}
}, "c0"]
The server immediately POSTs a verification payload to that URL:
Your service replies 2xx with the code echoed back via PushSubscription/set
update. Only verified subscriptions receive events.
Which one should I use?¶
| Client | Use |
|---|---|
| Browser / mobile app | SSE (simpler) or WebSocket (richer) |
| Backend service | Webhook |
| CLI / one-shot script | SSE with closeafter=state |
The same StateChange payload shape is emitted by all three.