Skip to content

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 first state event (useful for one-shot sync). Default no keeps 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:

{
  "@type": "PushVerification",
  "pushSubscriptionId": null,
  "verificationCode": "<random>"
}

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.

Next: deploying to production →