Skip to content

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 same Dispatcher as the HTTP endpoint.
  • Push subscriptions ({"@type": "WebSocketPushEnable"}) → server starts pushing StateChange events on this connection.

Webhooks (RFC 8620 §7.2)

PushSubscription/get and PushSubscription/set are part of the core capability. Creating a subscription:

  1. Stores a row in push_subscriptions with a random verificationCode.
  2. POSTs a PushVerification payload to the URL.
  3. If the URL replies 2xx, marks the subscription verified.
  4. From that point on, every StateChange for 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.