Push¶
Jmaple implements all three JMAP push transports against a single in-process
event broker. The dispatcher publishes a StateChange event after every
successful mutation; the broker fans out to subscribers.
The PushBroker¶
jmaple.push.broker.PushBroker is the central pub/sub. It exposes:
publish(account_id, type_states)— called by the dispatcher.subscribe(account_ids, type_filter)— used by SSE and WS handlers to open a per-connection queue.add_on_publish(hook)— register a coroutine called for every publish (webhook delivery uses this).
The v1 broker is in-process — fine for single-instance deployments. For horizontal scaling, swap it for a Redis-backed implementation; the call sites don't care.
SSE¶
GET /jmap/eventsource opens a long-lived text/event-stream connection.
The handler in jmaple.api.eventsource subscribes to the broker on behalf of
the connection, formats events as SSE frames, and emits periodic pings.
Query params:
types=*(or comma-separated list) — which data types to push.closeafter=state— close after first state event.ping=<seconds>— heartbeat interval.
WebSocket (RFC 8887)¶
WS /jmap/ws upgrades to a JMAP-over-WebSocket session. The same connection
carries:
- Requests (
{"@type": "Request", ...}) → run through the sameDispatcheras the HTTP endpoint. - Push subscriptions (
{"@type": "WebSocketPushEnable"}) → server starts pushingStateChangeevents on this connection.
Webhooks (RFC 8620 §7.2)¶
PushSubscription/get and PushSubscription/set are part of the core
capability. Creating a subscription:
- Stores a row in
push_subscriptionswith a randomverificationCode. - POSTs a
PushVerificationpayload to the URL. - If the URL replies 2xx, marks the subscription verified.
- From that point on, every
StateChangefor an account the subscription has access to is POSTed to the URL.
Delivery is wired in jmaple.app.lifespan: it registers
jmaple.push.webhook.fanout_state_change as an on_publish hook on the
broker.
Picking a transport¶
| Client | Use |
|---|---|
| Browser (no WS) | SSE |
| Browser / mobile (richer two-way) | WebSocket |
| Backend service | Webhook |
The StateChange payload shape is identical across all three.