Installable app, push notifications about new mail, Install app entry

The webmail is now a PWA: web app manifest, icons (any + maskable, a status-bar
badge), theme-coloured title bar following the chosen theme, and an app-icon
badge with the unread Inbox count. The service worker is always installed;
offline access is on only when it is registered as /sw.js?offline=1, so
turning it on or off is an update that applies at once.

Notifications (Settings → Accounts → Notifications, per device) use Web Push.
Encryption (RFC 8291) and VAPID signing (RFC 8292) are implemented in
app/push/webpush.py — pywebpush needs a newer cryptography than we pin — and
checked against the RFC's worked example. The server makes its key pair on
first use (private half encrypted with MASTER_KEY). New mail found by the live
sync in an Inbox enqueues a job 5 s later that tells the user's devices about
messages still unread there, under 2 hours old and not from the user; more
than three become one "N new messages". A subscription belongs to the session
it was made under: signed out or revoked, nothing more is sent there. Endpoints
are limited to the known push services (no SSRF). Nothing shows while the app
is in front (except on Apple, which requires it). Tested with real pushes
through Google's service in Chromium and Opera.

The mobile drawer gets "Install app": Chrome's own install dialog where the
browser offers one, else short steps for iPhone/iPad, Opera and Firefox, with
"Don't show this again". docs/nas-install.md §5.3 covers installing,
notifications, Public URL and a later Android app (TWA).

Migration 0022: push_subscriptions, app_settings.vapid_*.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 18:52:26 +02:00
co-authored by Claude Opus 5.5
parent 0729dbb691
commit fda4cc6f16
37 changed files with 1765 additions and 42 deletions
+19
View File
@@ -597,6 +597,25 @@ export const en = {
"offline.settingSaving": "Saving recent mail…",
"offline.settingUnsupported":
"Not available here: browsers allow offline access only when the app is opened over HTTPS.",
"push.settingTitle": "Notifications",
"push.settingHelp":
"Get a notification on this device when new mail arrives in an Inbox, even when Ultimo isn't open. Shows the sender and subject. Turned off when you sign out.",
"push.needsInstall":
"On iPhone and iPad, add Ultimo to your Home Screen first (Share → Add to Home Screen), then turn this on from the app.",
"push.blocked":
"Notifications are blocked for this site in your browser. Allow them in the browser's site settings, then turn this on.",
"push.unsupported": "Not available here: this browser can't show notifications from a website, or the app isn't opened over HTTPS.",
"push.notAllowed": "Notifications weren't allowed.",
"push.failed": "Couldn't turn notifications on.",
"install.entry": "Install app",
"install.title": "Install Ultimo",
"install.stepsIos":
"1. Tap the Share button (the square with an arrow) in Safari's toolbar.\n2. Choose Add to Home Screen.\n3. Tap Add. Ultimo opens from your Home Screen like an app, and can show notifications about new mail.",
"install.stepsOpera":
"1. Open Opera's menu (⋮).\n2. Choose Add to → Home screen.\n\nOpera adds a shortcut to Ultimo on your Home Screen. To install Ultimo as a full app, open this page in Chrome and tap Install app there.",
"install.stepsFirefox": "1. Open Firefox's menu (⋮).\n2. Choose Add app to Home screen (or Install).",
"install.hide": "Don't show this again",
"install.gotIt": "Got it",
} as const;
export type MessageKey = keyof typeof en;