[Bug] JMAP EventSource connection hangs indefinitely on valid Authorization

Issue Description

I am encountering an issue with the JMAP EventSource (SSE) implementation in Stalwart. While postman test SSE endpoints work perfectly in my environment, requests to the Stalwart EventSource endpoint hang indefinitely in a “Loading” state when valid credentials are provided.

Expected Behavior

The Stalwart EventSource endpoint should successfully establish the connection and stream data/pings rather than hanging indefinitely when proper credentials are provided.

Actual Behavior

Postman shows a continuous, infinite “Loading…” state. No data or initial events are ever returned or rendered.

Reproduction Steps

Baseline Test (Working Control)
To verify that my client-side setup and network support Server-Sent Events (SSE), I tested using Postman’s official echo endpoint:

Opened Postman on macOS and created a new HTTP GET request.

URL: https://postman-echo.com/server-events/5

Clicked Send.

Result: It works perfectly as expected, and events are streamed seamlessly (see attached file).

Steps to Reproduce (with Stalwart Endpoint)
I tested the following request using Postman against my Stalwart server:

Method: GET

URL: https://mail.your-domain.com/jmap/eventsource/?types=Email,Mailbox,Thread&closeafter=no&ping=30 (Note: replace with actual domain)

Test Case 1: Request WITHOUT Authorization header
Expected Behavior: The server should reject the request and return a 401 Unauthorized status code.

Actual Behavior: [Ok] Server returns 401 Unauthorized as expected.

Test Case 2: Request WITH valid Authorization header
Expected Behavior: The server should establish a successful persistent connection and begin streaming JMAP events or ping data.

Actual Behavior: [Error] Postman shows a continuous, infinite “Loading…” state. No data or initial events are ever returned or rendered.

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

S3-compatible

Search Engine

Internal

Directory Backend

Internal

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

Based on the source, this is very likely expected SSE behavior rather than a bug, though a reverse proxy could also be involved.

A few key things about how Stalwart’s /jmap/eventsource/ endpoint works:

  1. The connection is meant to stay open indefinitely. With closeafter=no, Stalwart never closes the stream on its own — it keeps it alive and pushes event: state frames only when there’s an actual mailbox/state change, and event: ping frames at the configured ping interval otherwise. So Postman showing a perpetual “Loading…” state for the request itself is actually correct: SSE requests never “complete” like the postman-echo.com/server-events/5 example does (that one closes after 5 events). What matters is whether ping/data frames are arriving within that ongoing response, not whether the request finishes.

  2. Ping interval is clamped to a 30s minimum. In your URL you passed ping=30, so you should see a ping event roughly every 30 seconds streamed into the still-open response. If you’re not seeing any bytes appear over time (not even pings), check whether something in front of Stalwart is buffering the response — a reverse proxy (nginx, Cloudflare, etc.) with response buffering enabled will hold the whole stream until it closes or a buffer fills, making it look like a hang in Postman even though Stalwart is emitting data correctly.

  3. Confirm you’re hitting Stalwart directly (or that any reverse proxy has buffering disabled and doesn’t impose a response timeout) and watch the raw response body in Postman over 30+ seconds rather than waiting for the request to “finish” — it isn’t supposed to finish while connected.

Reference: Push notifications | Stalwart (Event Source section)

If you can confirm you’re connecting directly to Stalwart (no proxy in between) and still see zero bytes even after the ping interval elapses, that would point to an actual server-side issue and it’d help to know your eventSourceThrottle/ping config and whether other JMAP calls to the same account work normally.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

Screenshots:

Test a postman endpoint:

Test with Stalwart endpoint:

Endpoint URL is correct;

But do not work as expected:

The endpoint itself looks to be working: it returns 200 text/event-stream and auth succeeds (you get the correct 401 when unauthenticated). The catch is that it emits nothing until the first event or ping, and with ping=30 the interval is floored to 30 seconds, so a client can sit on “Loading” for up to ~30s before the first ping. Does it eventually stream a ping after about half a minute, or does it truly hang forever?

If it hangs indefinitely, the prime suspect is the reverse proxy buffering the SSE response (your headers show Caddy in front). SSE needs buffering disabled there (in Caddy, flush_interval -1 on the reverse_proxy).

The issue was indeed caused by the Nginx proxy on my end. For anyone else running into the same problem, you need to add these directives inside the location / block:

proxy_buffering off; 
proxy_cache off; 
chunked_transfer_encoding on;