PushSubscription skipped by the per-account verify limit is never verified later (follow-up to #1123, v0.16.23)

Issue Description

On v0.16.23, when a second PushSubscription is created on the same account shortly after another subscription of that account received its PushVerification, the second subscription never receives a PushVerification. Waiting (more than 10 minutes, pushVerifyTimeout is the default 60 s) or new state changes on the account do not help. Updating the subscription (any PushSubscription/set update, for example “types”) or restarting the server makes the verification go out immediately. Subscriptions of other accounts are not affected.

This looks related to Stale push subscription can block verification of a new one (fixed in v0.16.14): the per-account verification limit now picks the newest unverified subscription, but a subscription that is skipped because of the limit is apparently never picked up again once the limit has expired.

Impact: mobile clients create a new subscription for every new device or reinstall and can stay without push indefinitely.

Expected Behavior

A subscription that was skipped because of the per-account limit receives its PushVerification once pushVerifyTimeout has passed.

Actual Behavior

It stays unverified until it is updated or the server is restarted.

Reproduction Steps

  1. Push endpoint that logs incoming POSTs (https, hostname URL).
  2. User A: PushSubscription/set create sub1 → PushVerification arrives within about 1 s.
  3. A few seconds later, user A: create sub2 → no PushVerification. At the same time, user B: create sub3 → PushVerification arrives normally.
  4. Wait 10 minutes, then deliver a new email to user A → still nothing for sub2.
  5. User A: PushSubscription/set update sub2 with {“types”: [“Email”]} → PushVerification for sub2 arrives immediately.

Measured on 2026-09-23: sub1 verified after about 1 s, sub3 (other account) after 15 s, sub2 not after 10.5 minutes and a new email, verified right after the update.

Relevant Log Output

Nothing is logged at INFO level for the skipped verification.

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

Additional Context

Web Push itself works well for us on v0.16.23: encrypted aes128gcm bodies with VAPID, and EmailPush with from, subject and preview (the fix for #1047 is effective).

Found and reproduced with the help of an AI assistant (Claude by Anthropic) while evaluating Stalwart; I reviewed this report before submitting.

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

This is confirmed by the source. In spawn_push_manager (state_manager/push.rs), the per-account “newest unverified” check and the last_verify throttle are only re-evaluated inside the Event::Update branch, which fires solely when a PushServerUpdate IPC event is sent, i.e. on a PushSubscription/set create/update/destroy. A plain state change (new mail, calendar update, etc.) goes through Event::Push, which only delivers notifications to already-verified subscriptions and never re-runs the “pick newest unverified subscription” logic. There is no timer/retry mechanism that revisits a subscription that was skipped because last_verify for the account hadn’t yet exceeded push_verify_timeout, so a skipped subscription simply sits unverified until something explicitly triggers another PushSubscription/set mutation (or the server restarts and reloads state from scratch). This matches your reproduction exactly: sub2 only got verified after you issued an update to it, not after 10+ minutes of waiting or a new email arriving.

This is a genuine defect (the per-account verify-limit fix only ensures the newest unverified subscription is chosen when an Event::Update happens to fire, but doesn’t guarantee that event ever fires again for skipped subscriptions). I don’t see any scheduled re-check of unverified subscriptions in the push manager loop to close this gap.

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

This has been fixed in v0.16.24 (yet to be released).

Thanks a lot for the quick fix.

I’ll test it on our test instance as soon as 0.16.24 is out.