Push notifications stop after presence transitions to offline (iOS PWA backgrounded)

(#723) Bug Needs triage mobile notifications self-hosting

Thread

Comment by @chowe99
RexSystem 1 vote originally by @chowe99 on GitHub
Same bug on a different client — it's gateway-side, not platform-specific Confirming this on a self-hosted instance (Docker Compose, gateway ghcr.io/fluxerapp/fluxer-gateway:v1, Postgres backend) with a GrapheneOS + Brave (Chromium) PWA — i.e. not iOS-specific. Same symptom exactly: web push works while the app is foregrounded/online, and stops entirely once presence transitions to offline, even though the subscription stays valid and registered. I isolated where it breaks. Short version: the client/delivery side is fully healthy; the gateway never sends the push for an offline recipient.

Ruled out (all verified working)

  • A raw VAPID Web Push sent directly to the stored subscription (via the web-push CLI, bypassing the gateway entirely) is delivered to the device instantly, even with the browser fully closed. So the VAPID keypair, the stored subscription, FCM transport, and OS-level delivery are all fine.
  • The gateway's only push-related config is VAPID (which is all standard Web Push needs, and is the same path the manual send used successfully).

Gateway-internal evidence

Gathered via the release's bounded rpc against the live node (bin/fluxer_gateway.real rpc <Mod> <Fun> <Args>, with FLUXER_ERLANG_NODE_NAME / FLUXER_ERLANG_COOKIE / FLUXER_ERLANG_DIST_PORT set):
  • Recipient confirmed offline: presence_manager:lookup(UserId) → {error, not_found}.
  • push:get_cache_stats() → #{push_subscriptions_size => 0, user_guild_settings_size => 4, badge_counts_size => 0, blocked_ids_size => 0} — the guild-settings sync path populates, but push subscriptions never do.
  • push_token_cache:get(UserId) → undefined.
  • push_dispatcher:stats() → #{queued => 0, inflight => 0} — even immediately after sending both a DM and a guild @mention to the offline recipient. Nothing is ever enqueued.

Where it breaks

A message to an offline recipient never reaches push_subscriptions:fetch_and_send_subscriptions/8 — the dispatcher stays empty. So the break is upstream of any subscription fetch/send, in the push:handle_message_create/1 → push_eligibility:is_eligible_for_push/8 path. Note the module surface: there are sync_user_guild_settings / sync_user_blocked_ids paths (which work — hence user_guild_settings_size => 4), but there is no load/sync for push subscriptions, only invalidate_user_subscriptions. That's consistent with subscriptions being fetched on-demand at send time — which means the eligibility/dispatch step simply never fires for offline recipients, so the fetch never happens. Happy to run more targeted rpc probes on the live node if a maintainer can point at which eligibility branch or dispatch call to instrument. (Investigation and diagnosis performed with Claude Code.)