Shared mailboxes, several accounts at once, and notifications for each #60

Merged
jcoffey-dev merged 5 commits from feature/shared-and-multi-account into main 2026-10-20 20:54:16 +00:00
5 Commits
Author SHA1 Message Date
jcoffey-dev aedcf74d3f Notifications with the app closed, for every signed-in account
ci / node (pull_request) Skipped
ci / version (pull_request) Skipped
ci / docker-build (pull_request) Skipped
ci / publish (pull_request) Skipped
github/ci (branch) GitHub Actions
ci / github (pull_request) Successful in 3m24s
Turning on "Notify me even when ihasmail is closed" now registers this
browser in every signed-in account, each through its own session (the
route from the previous change), and renewal keeps them all current.
GET /api/auth/accounts says which mail account each session is, so the
subscription can name it. The remembered endpoint is kept per account,
and survives the clean-up that follows switching accounts.

The service worker is told who the other accounts are. A push for one
of them is titled with that account's address, shown even while a tab
is focused (the tab only shows the front account's mail), and its Mark
read and Archive act through that account's session. Clicking it brings
that account to the front and opens the message. While push is on, the
tab's own polling of other accounts stops notifying, so nothing arrives
twice.

Signing out of one account removes this device's subscription there
only; signing out of all, or turning push off, removes every one.

Also fixes where every background notification opened: the worker
linked to /mail/inbox/<thread>, and the route takes a mailbox id there,
so a click landed on the inbox list with "That folder no longer exists".
The worker now gets each account's inbox id and links to the message.

No new strings.
2026-10-20 13:48:28 -07:00
jcoffey-dev b4167b2947 A narrow route to another signed-in account, for its push subscription
First of two for notifications with the app closed, for every
signed-in account. No behavior changes yet.

A JMAP push subscription belongs to whoever signs the request, so an
account that isn't in front can only have one registered, verified and
renewed through its own session. POST /api/auth/accounts/<sessionId>/jmap
forwards to Stalwart as that session, when it is one of this browser's
other accounts, and only for PushSubscription/get and /set, Mailbox/get,
and Email/set limited to keywords and mailboxIds updates (what a
notification's Archive and Mark read need). Anything else is 403; the
account in front, or a session this browser doesn't hold, is 404. The
browser already holds the session, so nothing new becomes reachable.

On the web app side the push helpers (list, create, extend, destroy,
verify) take a JMAP caller, defaulting to the account in front exactly
as before, and lib/notify/otherAccount gives the caller for another
account through the route.

Tests: the allowlist, the route end to end with two accounts, and the
caller. No new strings.
2026-10-20 13:47:16 -07:00
jcoffey-dev 58c933da10 New mail in the other signed-in accounts: counts, a dot, and a notification
With more than one account signed in, mail arriving in one that isn't
in front went unseen until someone switched to it.

- GET /api/auth/accounts/unread answers the Inbox unread count of each
  account not in front, asked through that account's own session, kept
  a minute per account.
- The web app asks every two minutes while another account is signed
  in. The account menu shows each one's count beside its name, and the
  avatar carries a dot when any of them has unread mail.
- When a count rises while the app is open and desktop notifications
  are on, a notification names the account ("New mail for
  [email protected]"); clicking it switches to that account. An
  account seen for the first time doesn't notify: its mail was already
  there.

New strings (3, translated in all ten catalogs): "New mail for {name}",
"Unread in the Inbox: {count}", "Account: new mail in another account".
2026-10-20 13:46:25 -07:00
jcoffey-dev 60fd2be5fb Account switcher: more than one account signed in at once
Someone who looks after several mailboxes of their own can now keep
them all signed in in one browser and move between them from the
account menu, without signing out.

The session cookie is unchanged: it is the account in front, and every
request is answered with it. The others ride in a second cookie,
<name>_more, as a list of their own session cookies. Each session stays
its own -- sealed credential, expiry and "this is my device" -- and
nothing about one is read through another. At most 5 in all, all on
the same Stalwart server.

- Add account (account menu) asks for the address and password in a
  small dialog. Refused, and the front stays, when the account is on
  another server or 5 are open. The same account again just comes to
  the front.
- Switching (POST /api/auth/accounts/<id>/front) swaps it into front;
  the web app clears what it cached for the previous account and
  reloads. A message being written blocks the switch.
- Sign out ends only the account in front, and the next one comes
  forward; Sign out of all accounts ends every one.
- GET /api/auth/accounts lists them, the front first, and says whether
  one more may be added.

The mock can sign in a second user (MOCK_SECOND_USER/PASS) for the
server tests; the client tests cover listing, switching and both
sign-outs.

New strings (8, translated in all ten catalogs): "Add account", "Add an
account", "Sign out of all accounts", "Both accounts stay signed in
here; switch between them from this menu.", "Working…", "That account
couldn't be added.", "You can't add more accounts here.", and the
other-server refusal.
2026-10-20 13:44:59 -07:00
jcoffey-dev 3576e8e3f8 Open a group's mailbox, or folders someone shared, from the account menu
A group's members, and anyone a folder was shared with, could reach that
mail over IMAP but not here: Mail read only the reader's own account.
Calendars, contacts and files already list other people's shares beside
the reader's own; Mail now offers them too.

Such an account is listed under "Mail to show" in the account menu and
opens in place of the reader's own mail. Only accounts whose Mailbox/get
answers with a mailbox are offered: Stalwart advertises every capability
on any shared account, so a colleague who shared one calendar would
otherwise appear with mail. That is what sank the old account switcher,
which moved the whole app; this switches Mail alone.

While one is in view:

- a bar in the palette's accent names it, with "Back to my mail", and
  the tab title names it;
- every folder in it shows (subscriptions are the reader's own);
- calendars, contacts, files, settings and push stay the reader's own;
- writing a message uses that account's identities;
- it is polled every minute, since push covers only the reader's own
  account, and new mail in the reader's own inbox is still announced.

Losing the account (removed from the group, share withdrawn) takes the
reader back to their own mail with a notice. A message being written
blocks the switch.

New strings: 6, in all ten catalogs.
2026-10-20 13:40:31 -07:00