Recreating the subscription removed the account's stored id before the
replacement was created and verified, and the resync now requires that
id. One interrupted or timed-out attempt (up to 75 seconds of
verification) therefore ended recovery for that account for good. The
id now stays until a verified replacement overwrites it.
A failed resync also forgot its timestamp, so every return to the tab
reran the whole enable. It now waits 15 minutes before the next try.
The background re-sync skipped any account whose browser subscription
was gone (isWebPushEnabled needs a live one), which is exactly the
state that needs repairing: the browser or the user cleared the
endpoint while the opt-in and the server registration survived, and
push stopped for good. It now runs whenever the user opted in and
permission is granted, so the normal enable flow recreates the
subscription. It still never prompts. A failed attempt no longer waits a
day for the next one.
Follow-ups to the review of the push, keyword and identity work:
- A send that names no sender, like the List-Unsubscribe mailto: of the
unified view, fell back to the first identity in server order, which
on Stalwart 1.0 is a group's. It now takes the login's own identity,
with addresses compared regardless of case.
- Re-register creates the replacement subscription before it destroys
the old one, so at Stalwart's limit (15 per account by default) it
failed with `overQuota`. It now frees the old record's place and
tries once more.
- On 1.0 the push settings note goes by the emailpush capability alone
and ignores refusals remembered under 0.16. On 0.16 a group the user
belongs to is recognised by its identity, so a member without
calendars, contacts or files keeps the group's junk filter and is no
longer told the group sends no notifications.
- Plugin imports can no longer set Stalwart 1.0's attachment keywords,
and `keywords.discover` leaves them out.
- Batch mark-as-read showed messages the server refused as read. It now
updates only what was written and reports the rest, and thread
expansion and category-tab moves keep what a partly refused batch
did write.
- Refused read and star changes from shortcuts, the list, the viewer
and Pro tabs show "Failed to update email" instead of an unhandled
rejection. The automatic mark-as-read on opening stays quiet.
- A tag whose id predates the 128-byte limit can have its colour
changed again.
- The state-regression guard notes that a multi-node 1.0 cluster can
trip it harmlessly, at the cost of one extra full fetch.
Firefox rejects pushManager.getSubscription() with
`AbortError: Error retrieving push subscription` when its local push
record is stale or the push service is unreachable. enableWebPush let
that reject the whole call, so the onboarding banner showed the browser's
bare error and push could never be turned on again - even though
subscribing fresh is exactly the recovery.
Treat a failed lookup as "no subscription to reuse" and continue to
subscribe(), whose error still surfaces when the push service is really
unavailable. Same reasoning for unsubscribe() of a mismatched
subscription: getting the new subscription is what matters.
Claude-Session: https://claude.ai/code/session_019KwXZEwxijHRbfobfXmcK2
Stalwart refuses a push subscription's whole emailPush map as
`forbidden` when it names a mailbox shared by ACL (0.16.16 and later,
and 1.0). Bulwark put every account with mailboxes in the map, so
enabling push showed raw JSON, and the background re-sync destroyed the
working subscription after its refresh was refused and dropped its key:
push stopped without a word and could not be turned back on while the
share existed. A network error, a timeout or rate limiting during the
refresh ended it the same way.
- The map holds the primary account and the accounts that list
`urn:ietf:params:jmap:emailpush`, which 1.0 leaves off ACL shares.
0.16 lists it everywhere, so a refused map narrows to the accounts
that look owned, then to the primary account, as the mobile app does.
Refused accounts are remembered under
`bulwark.push.emailPushRefused.v1.<id>`; Re-register and disable
clear the list.
- A subscription ends only when a verified replacement exists, the user
turns push off, or the server no longer has it. The old record is
destroyed after the new one is verified. A failed listing, a network
error or a refused method call leave it and its key alone.
- PushSubscription/set failures throw typed errors (`jmapSetError` for
a refused object, `jmapError` for a refused call) instead of JSON in
the message or `false`, and the client exposes `getAccountInfo`.
- Settings says that mailboxes others share don't send background
notifications, and `forbidden` and `overQuota` read as plain text in
settings and the onboarding prompt.
Bulwark asks for a 90-day push subscription, but Stalwart clamps
`expires` to 7 days. The background renewal ran once per page load,
so a tab or installed app left open for more than a week let the
subscription lapse and notifications stopped without a hint.
The renewal now runs at most once a day instead of once per load, and
also when the tab comes back to the foreground.
The notification panel only ever addresses the account on screen, so
arming a browser for five accounts meant switching account five times
and answering the same permission prompt on each pass.
The browser holds a single push subscription: enabling an account
registers it against that one subscription rather than creating another.
A section in the notification settings now lists every connected account
with its current state and turns push on or off for the ones selected,
walking them against the same subscription.
A refusal at the browser level - unsupported, or permission denied -
applies to every account, so it stops the walk instead of failing each
account in turn and prompting repeatedly.
enableWebPush reused any existing browser PushSubscription that exposed an
applicationServerKey, without comparing that key to the relay's. A push
subscription is bound to the key it was created with. So a browser that
subscribed against another relay, or before this relay rotated its keys,
kept a subscription the push service rejects on every send. Mozilla
answers 401 "VAPID public key mismatch". isWebPushEnabled still reports
push as on, so nothing prompts the user to fix it.
Compare the key bytes and resubscribe on a mismatch. Because
resyncWebPush runs enableWebPush on app start, affected browsers repair
themselves the next time the app opens.
Claude-Session: https://claude.ai/code/session_019KwXZEwxijHRbfobfXmcK2