Problem / Motivation
After #233 shipped, Background Notifications are fully functional —
self-hosters can configure the relay URL in Settings → Notifications,
click Enable, and Web Push works end-to-end (verification handshake,
service-worker delivery, click-to-message).
The remaining UX gap is discovery: a user who just logged in to a
fresh install has no in-app signal that push is available. They have to
know push exists, know it lives under Settings, and remember to enable
it manually. In practice that means most users never turn it on, even
though the relay is sitting there ready to deliver.
Example flow today:
- User opens Bulwark for the first time after a self-host upgrade to
v1.7.x.
- Mail loads, the UI looks complete, nothing hints that the browser
could be receiving background notifications.
- User keeps the tab open all day for foreground notifications; closes
it; misses new mail until next open.
- Months later: maybe stumbles onto Settings → Notifications.
Proposed Solution
Show a one-time onboarding affordance the first time the app loads on a
browser whose Notification.permission is default (never asked) and
where Web Push is supported ('serviceWorker' in navigator && 'PushManager' in window). Two reasonable shapes:
Option A — inline banner inside the mail view (preferred):
🔔 Get notified when new mail arrives, even when this tab is closed.
[ Enable ] [ Not now ] [ Don't ask again ]
- Enable → triggers the same code path as the Settings toggle
(Notification.requestPermission() → pushManager.subscribe() with
the VAPID public key → POST /api/push/register/web →
PushSubscription/set on JMAP). On success, also flips the
Background Notifications toggle in Settings so the in-app state is
consistent.
- Not now → suppressed for the rest of the session.
- Don't ask again → persistent localStorage flag.
Option B — guided callout from the existing notifications icon /
Settings entry, e.g. a small badge / tooltip on the bell icon that
points to the toggle.
Suppression rules:
- Skip if
Notification.permission !== 'default' (don't pester users
who already accepted, denied, or installed the PWA).
- Skip on
/login, /settings, and on the Bulwark setup flow.
- Skip when no push relay URL is configured (don't show what we can't
fulfil).
- Skip when the Email Notifications toggle is OFF (user explicitly
opted out of all mail notifications).
Why upstream rather than self-host injection
Self-hosters can absolutely paper over this with a nginx sub_filter
that injects a custom prompt — we did that briefly on our deployment —
but the result is awkward: the injected prompt runs outside Bulwark's
own subscription flow, so even when the user clicks Enable, the
in-app Background Notifications toggle reads "off" until they
revisit Settings. Doing this in Bulwark itself keeps a single source of
truth for the subscription state.
Alternatives Considered
- Browser-native permission API auto-call on every load — too
aggressive; Chromium quietly ignores promotional prompts and Firefox
shows a low-contrast UI bar most users miss.
- Documentation only — works in theory, but the gap between "feature
shipped" and "users actually use it" stays wide. Especially on
self-hosted instances where the operator may not be the same person
as the end user.
Feature Area
Notifications / PWA
Mockups / Examples
Reeva.me, a self-host that ships Bulwark, ran a quick nginx sub_filter version of Option A as a stopgap. Banner copy and button
layout in the snippet at
https://git.linux-hosting.co.il/shukivaknin/Riva/commit/18b14a0
(reverted in favour of this upstream request). The visual treatment is
arbitrary — what matters is the trigger condition and the hand-off
into Bulwark's own subscribe flow.
Additional Context
If the maintainers are open to it, happy to put together a PR that
adds the banner component, a usePushOnboardingPrompt hook that
encapsulates the suppression rules, and a small piece of state in the
notifications store so the existing toggle reflects the result of the
banner's Enable click. Wanted to align on the approach (Option A vs B,
suppression rules) before sending code.
Problem / Motivation
After #233 shipped, Background Notifications are fully functional —
self-hosters can configure the relay URL in Settings → Notifications,
click Enable, and Web Push works end-to-end (verification handshake,
service-worker delivery, click-to-message).
The remaining UX gap is discovery: a user who just logged in to a
fresh install has no in-app signal that push is available. They have to
know push exists, know it lives under Settings, and remember to enable
it manually. In practice that means most users never turn it on, even
though the relay is sitting there ready to deliver.
Example flow today:
v1.7.x.
could be receiving background notifications.
it; misses new mail until next open.
Proposed Solution
Show a one-time onboarding affordance the first time the app loads on a
browser whose
Notification.permissionisdefault(never asked) andwhere Web Push is supported (
'serviceWorker' in navigator && 'PushManager' in window). Two reasonable shapes:Option A — inline banner inside the mail view (preferred):
(
Notification.requestPermission()→pushManager.subscribe()withthe VAPID public key →
POST /api/push/register/web→PushSubscription/seton JMAP). On success, also flips theBackground Notifications toggle in Settings so the in-app state is
consistent.
Option B — guided callout from the existing notifications icon /
Settings entry, e.g. a small badge / tooltip on the bell icon that
points to the toggle.
Suppression rules:
Notification.permission !== 'default'(don't pester userswho already accepted, denied, or installed the PWA).
/login,/settings, and on the Bulwark setup flow.fulfil).
opted out of all mail notifications).
Why upstream rather than self-host injection
Self-hosters can absolutely paper over this with a
nginx sub_filterthat injects a custom prompt — we did that briefly on our deployment —
but the result is awkward: the injected prompt runs outside Bulwark's
own subscription flow, so even when the user clicks Enable, the
in-app Background Notifications toggle reads "off" until they
revisit Settings. Doing this in Bulwark itself keeps a single source of
truth for the subscription state.
Alternatives Considered
aggressive; Chromium quietly ignores promotional prompts and Firefox
shows a low-contrast UI bar most users miss.
shipped" and "users actually use it" stays wide. Especially on
self-hosted instances where the operator may not be the same person
as the end user.
Feature Area
Notifications / PWA
Mockups / Examples
Reeva.me, a self-host that ships Bulwark, ran a quick
nginx sub_filterversion of Option A as a stopgap. Banner copy and buttonlayout in the snippet at
https://git.linux-hosting.co.il/shukivaknin/Riva/commit/18b14a0
(reverted in favour of this upstream request). The visual treatment is
arbitrary — what matters is the trigger condition and the hand-off
into Bulwark's own subscribe flow.
Additional Context
If the maintainers are open to it, happy to put together a PR that
adds the banner component, a
usePushOnboardingPrompthook thatencapsulates the suppression rules, and a small piece of state in the
notifications store so the existing toggle reflects the result of the
banner's Enable click. Wanted to align on the approach (Option A vs B,
suppression rules) before sending code.