Push notifications
A client that wants to show a new message as soon as it arrives could ask the server again and again whether anything has changed, but polling wastes resources and still lags behind. JMAP clients instead learn about changes, such as a new message or an updated calendar event, through push notifications. Stalwart supports the three mechanisms defined for JMAP.
The first is Server-Sent Events (RFC 8620, section 7.3). The client keeps an HTTP request to /jmap/eventsource open, and the server writes notifications to it as text/event-stream events. This suits clients that run continuously, such as webmail.
The second is WebSocket push (RFC 8887), which delivers notifications on the client’s WebSocket connection.
The third is push subscriptions (RFC 8620, section 7.2), which reach clients that are not running. The client registers the URL of a push service, such as the one of a browser or a mobile platform. Stalwart sends each notification to that URL with an HTTP POST, and the push service delivers it to the device even when the client is not running. Notifications are encrypted for the client when it supplies keys (RFC 8291).
The settings are on the Jmap singleton (found in the WebUI under Settings › Network › JMAP › Limits, Settings › Network › JMAP › Push, Settings › Network › JMAP › WebSocket), and they take effect after a settings reload.
Server-Sent Events
Section titled “Server-Sent Events”The URL is advertised in the session resource as /jmap/eventsource/?types={types}&closeafter={closeafter}&ping={ping}, and the client fills in three parameters:
types: the data types to watch, or*for all.closeafter:stateends the response after the first notification.ping: the interval of keep-alive events in seconds, raised to at least 30 seconds, or0for none.
A burst of changes should not produce a burst of notifications, so notifications on one connection are paced. eventSourceThrottle is the minimum time between two of them, and changes that occur in between are combined into the next notification.
Push subscriptions
Section titled “Push subscriptions”A push subscription makes the server send requests to a URL that a client chose, so the server restricts where it will send them. A subscription is accepted only for a public https URL. A URL with another scheme, a URL with credentials, and a URL whose host is a private, loopback or otherwise non-public address or name are refused with invalidProperties on url, with a description such as URL must use the https scheme or Destination is a local or reserved IP address. The same rule is applied again when notifications are sent, so a name that later resolves to a private address does not receive them.
maxSubscriptions limits the push subscriptions that one account can have, to 15 unless changed.
Browser push services require the sending server to identify itself. Stalwart does so with VAPID (RFC 8292, used for JMAP by RFC 9749), which signs each request with the server’s key. webPushKey is the ECDSA P-256 signing key, generated when the server starts for the first time. While it is set, the session resource advertises the urn:ietf:params:jmap:webpush-vapid capability, with the public key. webPushContact is the contact address sent to push services, mailto:postmaster@<hostname> when it is unset.
maxPushSize limits the size of a notification, to 4 KiB unless changed. Larger email push notifications are truncated.
Delivery to push services is paced and retried. pushThrottle is the minimum time between two requests to one push subscription, and pushRequestTimeout is the timeout of each request. When a request fails, pushRetryWait is the wait before retrying, and pushAttemptWait the wait between attempts. A notification that still fails after maxPushAttempts attempts is discarded.
A new subscription must also be verified. The client verifies it with the code sent to it, within pushVerifyTimeout.
In a cluster, push subscriptions are divided among the nodes. pushShardsTotal is the number of shards, and each node serves the shard set by its STALWART_PUSH_SHARD environment variable.