Progressive Web App
Bulwark ships as a Progressive Web App (PWA). Users can install it to their home screen on Android, iOS, and desktop, and the service worker keeps the app shell ready for fast subsequent loads.
What You Get
- Installable - Browsers offer "Add to Home Screen" / "Install app" once the manifest is detected.
- Standalone window - Launches without browser chrome on desktop and mobile.
- Splash screen - OS-rendered splash using your configured background color and icon.
- App-name everywhere - Browser tab title, install dialog, home screen label, and PWA manifest all use
APP_NAME(withAPP_SHORT_NAMEfor tight contexts). - Service worker - Registered automatically; caches the app shell.
- Install prompt - A friendly in-app prompt suggests installation. Users can dismiss it, and there is a "don't remind me again" option.
- Web push notifications - When the user grants permission, Bulwark subscribes to web push and surfaces new-inbox-mail notifications even when the tab is closed. Clicking the notification opens the message. New-mail notifications are scoped to genuine inbox deliveries (not flag changes).
- Update detection - The service worker detects the changed app shell on the next load and refreshes the cache. Bulwark additionally performs a server-side update check on startup and shows a non-dismissible in-app update notice when a new release is available.
Configuration
Every aspect of the PWA manifest is configured via runtime environment variables - you don't need to rebuild the image to rebrand the install experience.
APP_NAME=Acme Mail # Used in the manifest, browser tab, and SoftwareApplication metadata
APP_SHORT_NAME=Acme # Home screen label
APP_DESCRIPTION=Acme webmail # Description in the install dialog
PWA_ICON_URL=/branding/acme.svg # Source icon (auto-generates 192/512 PNG + maskable variants)
FAVICON_URL=/branding/acme.svg # Browser tab icon (PWA_ICON_URL falls back to this)
PWA_THEME_COLOR=#0f172a # Browser UI chrome (Android status bar)
PWA_BACKGROUND_COLOR=#ffffff # Splash screen background
Auto-generated icons
Bulwark generates the required PWA icon sizes from a single source:
icon-192x192.pngandicon-512x512.png- regularicon-maskable-light-192x192.png/icon-maskable-light-512x512.png- maskable for light backgroundsicon-maskable-dark-192x192.png/icon-maskable-dark-512x512.png- maskable for dark backgrounds
If PWA_ICON_URL is not set, the app falls back to FAVICON_URL, then to the bundled Bulwark icons.
Default colors
If you don't set PWA_THEME_COLOR and PWA_BACKGROUND_COLOR, both default to #ffffff. Match PWA_BACKGROUND_COLOR to your app's main background to avoid a visible color flash during launch.
Install Prompt UX
When the browser fires the beforeinstallprompt event, Bulwark shows an in-app prompt:
- Install - triggers the native install dialog
- Later - dismisses the prompt for this session
- Don't remind me again - persists the dismissal so the prompt won't reappear
The prompt also shows the app's name and logo so users see your branding (not "Bulwark") when you have customized it.
Service Worker
The service worker is served from /sw.js and registered on first load. It precaches the app shell and serves it on subsequent visits, which makes navigation between pages fast even on slow networks. Mail data itself is always fetched fresh from the JMAP server.
Web Push Notifications
When the user grants notification permission, Bulwark subscribes the browser to web push and uses the JMAP push verification handshake to receive real-time new-mail pings. Notifications:
- Fire only on genuine inbox deliveries, not on flag changes or moves
- Click to jump directly to the message
- Survive the tab being closed - the service worker handles delivery in the background
- Use the longer push verification timeout to avoid spurious unsubscriptions; leftover subscriptions are cleaned up automatically
If you self-host behind a reverse proxy, keep the /api/push/* and /sw.js paths reachable so the verification handshake can complete.
Reverse Proxy Notes
If Bulwark sits behind a reverse proxy, make sure these paths are forwarded as-is:
/manifest.webmanifest- dynamic manifest/sw.js- service worker (must NOT be cached aggressively at the proxy)/api/pwa-icon/*- auto-generated PWA icons/branding/*- branding assets
The service worker lives at the site root by design. If you serve Bulwark from a subpath, the service worker scope is automatically adjusted to that subpath.
Updates
When a new Bulwark version is deployed, the service worker detects the changed app shell and refreshes the cache on the next load. No manual cache busting is needed.
Bulwark additionally performs a server-side update check on startup that logs to stderr and surfaces an in-app, non-dismissible update notice when a newer release is available. The notice provides a one-click refresh that picks up the new service worker; in dev it dynamically reloads without a full page navigation.
Troubleshooting
"Install" option doesn't appear
- Confirm the site is served over HTTPS (the only exception is
http://localhost). - Confirm
/manifest.webmanifestreturns a 200 with valid JSON. - Confirm the service worker is registered (
/sw.jsreturns 200, scope is correct). - Some browsers (notably iOS Safari) only offer install via Share → Add to Home Screen.
Wrong icon or name shows up after install
PWA installs are cached by the OS. Uninstall the existing app and reinstall after changing APP_NAME, APP_SHORT_NAME, PWA_ICON_URL, or related variables.
Splash screen flashes the wrong color
Set PWA_BACKGROUND_COLOR to match your initial background and reinstall the PWA.