In the compiled Service Worker (sw.js), self.registration.showNotification is triggered across three distinct flows rather than solely through the native push event:
Web Push Listener: Standard push event handler.
Polling/Sync Handler: Fires independently on background sync cycles.
EventSource / JMAP SSE Listener: Triggers whenever JMAP mailbox mutations occur (e.g., marking emails as read or moving messages).
Because there is no centralized state tracking or deduplication logic between these handlers, incoming emails often trigger up to three identical notifications. Furthermore, non-incoming state updates generate "ghost" notifications when triage actions are performed on another client.
Expected Behavior:
Notifications should be driven exclusively by the Web Push listener (or strictly filtered by checking if the unread count increased), ensuring a single notification per incoming email.
Unhandled JMAP PushSubscription Accumulation
When push notifications are re-enabled, or when a user clears their browser cache or reinstall the PWA, a new Web Push endpoint is generated. The frontend registers this endpoint using a JMAP PushSubscription/set create payload.
However, the client does not query existing subscriptions (PushSubscription/get) or prune stale tokens (destroy) beforehand. On strict RFC 8620-compliant JMAP servers (such as Stalwart), stale tokens quickly accumulate until the account hits the server's subscription threshold, returning "Error: too many subscriptions".
Expected Behavior:
Prior to issuing a PushSubscription/set create request, the client should fetch active subscriptions, match by deviceClientId, and issue a destroy for stale endpoints.
JMAP Server: Stalwart Mail Server v0.16.21
Client App: ihasmail PWA
Browser/OS: Android / Chrome PWA
1) Duplicate & Ghost Notifications (Service Worker Event Overlap)
In the compiled Service Worker (sw.js), self.registration.showNotification is triggered across three distinct flows rather than solely through the native push event:
- Web Push Listener: Standard push event handler.
- Polling/Sync Handler: Fires independently on background sync cycles.
- EventSource / JMAP SSE Listener: Triggers whenever JMAP mailbox mutations occur (e.g., marking emails as read or moving messages).
Because there is no centralized state tracking or deduplication logic between these handlers, incoming emails often trigger up to three identical notifications. Furthermore, non-incoming state updates generate "ghost" notifications when triage actions are performed on another client.
Expected Behavior:
Notifications should be driven exclusively by the Web Push listener (or strictly filtered by checking if the unread count increased), ensuring a single notification per incoming email.
------------------
2) Unhandled JMAP PushSubscription Accumulation
When push notifications are re-enabled, or when a user clears their browser cache or reinstall the PWA, a new Web Push endpoint is generated. The frontend registers this endpoint using a JMAP PushSubscription/set create payload.
However, the client does not query existing subscriptions (PushSubscription/get) or prune stale tokens (destroy) beforehand. On strict RFC 8620-compliant JMAP servers (such as Stalwart), stale tokens quickly accumulate until the account hits the server's subscription threshold, returning "Error: too many subscriptions".
Expected Behavior:
Prior to issuing a PushSubscription/set create request, the client should fetch active subscriptions, match by deviceClientId, and issue a destroy for stale endpoints.
---------------------
JMAP Server: Stalwart Mail Server v0.16.21
Client App: ihasmail PWA
Browser/OS: Android / Chrome PWA
<sub>Rebuilt from: GH Archive, notification email, session transcript.</sub>
Thanks for the detailed report. Both problems were real, though the causes turned out a little different from the ones you suspected.
Duplicate and "ghost" notifications
The service worker only shows notifications from the push event; there's no polling or SSE path in it. The problem was what ihasmail subscribed to. It asked Stalwart for every change to your mail, so marking a message read or moving it on another device arrived as a push, and the worker could only show it as "New mail". It now subscribes to new deliveries only.
Also changed:
It no longer notifies while ihasmail is open and focused.
An open tab now leaves notifications to the service worker, so the two can't double up.
Notifications now carry the message itself. Archive and Mark as read work, and tapping one opens the message.
"too many subscriptions"
We tested this against a live Stalwart 0.16.22:
Registering again with the same device id doesn't replace the old subscription; Stalwart keeps both.
It allows 15 subscriptions per account.
ihasmail registered again on every renewal, so subscriptions piled up until the account hit that limit. Now it:
extends its existing subscription instead of registering a new one;
removes its own duplicates;
clears out a stale subscription to make room if the account is already full.
When ihasmail's server runs push in subscribe mode, it now also removes the subscriptions its previous run left behind.
Fixed in #389, and it'll be in the next release. Once you've upgraded, opening ihasmail on each device is enough to tidy up. Any leftovers from devices you no longer use expire on their own within 7 days.
Thanks for the detailed report. Both problems were real, though the causes turned out a little different from the ones you suspected.
**Duplicate and "ghost" notifications**
The service worker only shows notifications from the `push` event; there's no polling or SSE path in it. The problem was what ihasmail subscribed to. It asked Stalwart for every change to your mail, so marking a message read or moving it on another device arrived as a push, and the worker could only show it as "New mail". It now subscribes to new deliveries only.
Also changed:
- It no longer notifies while ihasmail is open and focused.
- An open tab now leaves notifications to the service worker, so the two can't double up.
- Notifications now carry the message itself. Archive and Mark as read work, and tapping one opens the message.
**"too many subscriptions"**
We tested this against a live Stalwart 0.16.22:
- Registering again with the same device id doesn't replace the old subscription; Stalwart keeps both.
- It allows 15 subscriptions per account.
ihasmail registered again on every renewal, so subscriptions piled up until the account hit that limit. Now it:
- extends its existing subscription instead of registering a new one;
- removes its own duplicates;
- clears out a stale subscription to make room if the account is already full.
When ihasmail's server runs push in subscribe mode, it now also removes the subscriptions its previous run left behind.
Fixed in #389, and it'll be in the next release. Once you've upgraded, opening ihasmail on each device is enough to tidy up. Any leftovers from devices you no longer use expire on their own within 7 days.
Thanks for the detailed report. Both problems were real, though the causes
turned out a little different from the ones you suspected.
Duplicate and "ghost" notifications
The service worker only shows notifications from the push event; there's
no polling or SSE path in it. The problem was what ihasmail subscribed to.
It asked Stalwart for every change to your mail, so marking a message read
or moving it on another device arrived as a push, and the worker could only
show it as "New mail". It now subscribes to new deliveries only.
Also changed:
It no longer notifies while ihasmail is open and focused.
An open tab now leaves notifications to the service worker, so the
two can't double up.
Notifications now carry the message itself. Archive and Mark as read
work, and tapping one opens the message.
"too many subscriptions"
We tested this against a live Stalwart 0.16.22:
Registering again with the same device id doesn't replace the old
subscription; Stalwart keeps both.
It allows 15 subscriptions per account.
ihasmail registered again on every renewal, so subscriptions piled up
until the account hit that limit. Now it:
extends its existing subscription instead of registering a new one;
removes its own duplicates;
clears out a stale subscription to make room if the account is
already full.
When ihasmail's server runs push in subscribe mode, it now also removes
the subscriptions its previous run left behind.
Fixed in #389https://github.com/Coffey-Labs/ihasmail/pull/389, and
it'll be in the next release. Once you've upgraded, opening ihasmail on
each device is enough to tidy up. Any leftovers from devices you no longer
use expire on their own within 7 days.
Thanks, really appreciate all the work and the incredibly quick fixes!
Op wo 16 sep 2026, 20:43 schreef jcoffey ***@***.***>:
> *jcoffey-dev* left a comment (Coffey-Labs/ihasmail#375)
> <https://github.com/Coffey-Labs/ihasmail/issues/375#issuecomment-5702724442>
>
> Thanks for the detailed report. Both problems were real, though the causes
> turned out a little different from the ones you suspected.
>
> *Duplicate and "ghost" notifications*
>
> The service worker only shows notifications from the push event; there's
> no polling or SSE path in it. The problem was what ihasmail subscribed to.
> It asked Stalwart for every change to your mail, so marking a message read
> or moving it on another device arrived as a push, and the worker could only
> show it as "New mail". It now subscribes to new deliveries only.
>
> Also changed:
>
> - It no longer notifies while ihasmail is open and focused.
> - An open tab now leaves notifications to the service worker, so the
> two can't double up.
> - Notifications now carry the message itself. Archive and Mark as read
> work, and tapping one opens the message.
>
> *"too many subscriptions"*
>
> We tested this against a live Stalwart 0.16.22:
>
> - Registering again with the same device id doesn't replace the old
> subscription; Stalwart keeps both.
> - It allows 15 subscriptions per account.
>
> ihasmail registered again on every renewal, so subscriptions piled up
> until the account hit that limit. Now it:
>
> - extends its existing subscription instead of registering a new one;
> - removes its own duplicates;
> - clears out a stale subscription to make room if the account is
> already full.
>
> When ihasmail's server runs push in subscribe mode, it now also removes
> the subscriptions its previous run left behind.
>
> Fixed in #389 <https://github.com/Coffey-Labs/ihasmail/pull/389>, and
> it'll be in the next release. Once you've upgraded, opening ihasmail on
> each device is enough to tidy up. Any leftovers from devices you no longer
> use expire on their own within 7 days.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/Coffey-Labs/ihasmail/issues/375?email_source=notifications&email_token=CNVKZN75OGIUV56AIYOBC335PLNNDA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNZQGI3TENBUGQZKM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#issuecomment-5702724442>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/CNVKZN2SGMFKGYYGTN4ZUDL5PLNNDAVCNFSNUABGKJSXA33TNF2G64TZHMYTANZZGEYTKNRZGE5US43TOVSTWNJUG42TSMRYGM4TRILWAI>
> .
> Triage notifications, keep track of coding agent tasks and review pull
> requests on the go with GitHub Mobile for iOS
> <https://github.com/notifications/mobile/ios/CNVKZN2MROHHOEEVH6ZRY6D5PLNNDA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNZQGI3TENBUGQZKM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KUZTPN52GK4S7NFXXG>
> and Android
> <https://github.com/notifications/mobile/android/CNVKZN7WB3VFSO2MNJCDSST5PLNNDA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNZQGI3TENBUGQZKM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2K4ZTPN52GK4S7MFXGI4TPNFSA>.
> Download it today!
> You are receiving this because you authored the thread.Message ID:
> ***@***.***>
>
This repo is archived. You cannot comment on issues.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
In the compiled Service Worker (sw.js), self.registration.showNotification is triggered across three distinct flows rather than solely through the native push event:
Because there is no centralized state tracking or deduplication logic between these handlers, incoming emails often trigger up to three identical notifications. Furthermore, non-incoming state updates generate "ghost" notifications when triage actions are performed on another client.
Expected Behavior:
Notifications should be driven exclusively by the Web Push listener (or strictly filtered by checking if the unread count increased), ensuring a single notification per incoming email.
When push notifications are re-enabled, or when a user clears their browser cache or reinstall the PWA, a new Web Push endpoint is generated. The frontend registers this endpoint using a JMAP PushSubscription/set create payload.
However, the client does not query existing subscriptions (PushSubscription/get) or prune stale tokens (destroy) beforehand. On strict RFC 8620-compliant JMAP servers (such as Stalwart), stale tokens quickly accumulate until the account hits the server's subscription threshold, returning "Error: too many subscriptions".
Expected Behavior:
Prior to issuing a PushSubscription/set create request, the client should fetch active subscriptions, match by deviceClientId, and issue a destroy for stale endpoints.
JMAP Server: Stalwart Mail Server v0.16.21
Client App: ihasmail PWA
Browser/OS: Android / Chrome PWA
Rebuilt from: GH Archive, notification email, session transcript.
Thanks for the detailed report. Both problems were real, though the causes turned out a little different from the ones you suspected.
Duplicate and "ghost" notifications
The service worker only shows notifications from the
pushevent; there's no polling or SSE path in it. The problem was what ihasmail subscribed to. It asked Stalwart for every change to your mail, so marking a message read or moving it on another device arrived as a push, and the worker could only show it as "New mail". It now subscribes to new deliveries only.Also changed:
"too many subscriptions"
We tested this against a live Stalwart 0.16.22:
ihasmail registered again on every renewal, so subscriptions piled up until the account hit that limit. Now it:
When ihasmail's server runs push in subscribe mode, it now also removes the subscriptions its previous run left behind.
Fixed in #389, and it'll be in the next release. Once you've upgraded, opening ihasmail on each device is enough to tidy up. Any leftovers from devices you no longer use expire on their own within 7 days.
Thanks, really appreciate all the work and the incredibly quick fixes!
Op wo 16 sep 2026, 20:43 schreef jcoffey @.***>: