Commit Graph
517 Commits
Author SHA1 Message Date
Linus Rath 7bf5e0d802 fix: keep a shared-folder message's thread key in line with its row #1188 2026-10-07 19:34:20 +02:00
VasilisNtovolos bb3ef6d9fc fix: route shared-account blob downloads by message owner 2026-10-07 15:57:45 +03:00
Linus Rath ccf6bf7706 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	locales/cs/common.json
2026-10-07 01:03:34 +02:00
Linus Rath cbfcdf3b69 Merge pull request #1152 from dealerweb/feat/rule-periods-out-of-office
feat: out of office forwarding and recipients, filter rule periods
2026-10-06 23:28:58 +02:00
Linus Rath d8b7a6919c Merge pull request #1176 from cybersmurf/fix/archive-auto-create
fix: create the Archive folder on first use when the account has none
2026-10-06 23:28:55 +02:00
Linus Rath 1829bd8ee4 fix: stop a previous account's settings leaking into the next sign-in #1185 2026-10-06 23:26:27 +02:00
Linus Rath 9738231c2a fix: stop stale tabs and devices from dropping synced templates 2026-10-06 22:14:29 +02:00
Linus Rath 1e10aa9bd6 feat: move several folders at once from the folder settings #1173 2026-10-06 15:49:53 +02:00
sudandClaude Fable 5.1 45c7e2c360 fix: create the Archive folder on first use when the account has none
Stalwart's default folder set has no archive-role mailbox, so on a fresh
account Archive did nothing but raise the not-found toast (#578). Both
archive paths now go through ensureArchiveMailbox(), which creates
"Archive" with the archive role in the account the action targets (own,
the selected shared folder's owner, or the pinned account in unified
view), reloads the mailbox list and continues with the store's own
object. createMailbox() takes an optional role for this. A refused
creation or a folder that does not come back still surfaces as an error.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 06:55:02 +02:00
Linus Rath b51de3baaa feat: add or remove a label on all selected mails at once #1077 2026-10-04 20:54:32 +02:00
Linus Rath a4e313f73e fix: leave trash and spam out of tag views and their counts #1156 2026-10-02 23:15:26 +02:00
Linus Rath 0b4d0b0f3c feat: limit the day and week views to working hours and days #1164 2026-10-02 22:56:16 +02:00
Linus Rath 1c1bfb0ce8 fix: explain a refused token exchange instead of asking for Basic auth on a TOTP login
When Stalwart accepted the password and code but refused to issue
tokens (in Lite: `invalid_client` for an OAuth client with a secret),
login retried `password$totp` over Basic. Stalwart 0.16+ rejects that, so
the browser showed its own Basic-auth dialog and the login page then
reported a wrong code. The login now stops with a message pointing at
the OAuth client settings. Password-only logins keep the Basic fallback.
2026-10-02 16:32:15 +02:00
Linus Rath 64b39c504a fix: open the inbox instead of the last folder when the app starts
The boot snapshot restored the last plain folder that was open, and
unified views are never written to it. A session that touched Trash and
then moved to the unified inbox therefore opened Trash on every visit to
`/`. A plain visit now starts in the account's own inbox and drops rows
cached for another folder; reloading a `/mail/folder/...` link still
continues in that folder.
2026-10-02 16:31:44 +02:00
dealerweb f5fb7f9db9 feat: out of office forwarding and recipients, filter rule periods
Filter rules can be limited to a period: the rule dialog takes a start
and an end on the user's clock, the rule list shows whether a rule is
planned, active or expired, and the script tests the moment of
delivery with RFC 5260 currentdate in UTC. A rule is never written
without its period.

The vacation card becomes an out of office page: the period on top,
then the automatic reply and forwarding, each with its own switch.
Forwarding runs with or without the auto-reply, within the period,
never for spam, and keeps a copy here or not. Against the server's
redirect limit it counts as a rule ahead of all others, one that stops
the message unless it keeps a copy. The auto-reply can go to
internal or external senders only, told apart by the envelope sender
against the account's own domains. Both run from the filters script
that includes Stalwart's vacation script, which is put back in charge
when the server's own script takes over.

A script that uses any of this says metadata version 2, which older
builds leave alone instead of writing it back without these fields.
2026-10-02 11:29:32 +02:00
Linus Rath 71a1fad77b fix: keep a copied message's original date in the other account #1150
A copy between logins goes through `Email/import`, which sent no
`receivedAt`, so the server stamped the import time and every copied
message landed at the top of the destination folder as today's mail.
`importRawEmail` now takes the original `receivedAt` (the same change
#1151 carries), and the cross-account copy and move pass it.
2026-10-02 10:35:09 +02:00
lucamzanon f02dbf3a40 feat: copy messages to a folder of another connected account
With several accounts connected, mail could only be moved between
them (drag to another account's folder). There was no way to file a
copy in another account and keep the original where it is.

crossAccountMoveEmails and copyEmailAcrossAccounts take an optional
`{ keepOriginal: true }`. With it the source message is not destroyed,
and the store leaves the list, the open message and the selection as
they are; only the destination's folders are refreshed. Without it
nothing changes.

The message menu gets "Copy to account..." when more than one account
is connected. It lists the writable folders of the other accounts
(their folder lists are fetched when the menu opens) and copies the
selection, or the message the menu was opened on, through the new
copyEmailsToAccount action. That groups the messages by the login and
JMAP account they live in and refuses a copy into the account a
message is already in.
2026-10-01 12:53:14 +02:00
Linus Rath 26cbca73cb feat: let @ recipient mentions be turned off and announce them to screen readers #1134
Mentions change what Enter and Tab do right after `@word` and open a
list for everyone, so they get a setting next to the empty subject
warning (`recipientMentionsEnabled`, on by default, translated in
every locale). Turned off, the composer offers no candidates and the
editor keeps its keys.

While the list is open, the editor now carries `aria-autocomplete`,
`aria-controls` and `aria-activedescendant`, as the To/Cc
autocomplete does, so screen readers hear the list and the highlighted
name. Each row now leads with the `@name` that will be inserted,
followed by the full name and the address: for a surname-first name
such as "Nagy János" the inserted word is not what the name alone
suggests.
2026-10-01 11:10:04 +02:00
lucamzanon 717f18fc46 feat(unified): say how many accounts the unified mailbox holds while logins reconnect
After a load the logins other than the one on screen reconnect in the
background, and until they are all back the unified counts cover only
some of them. With many logins that lasts seconds, and a partial total
looks exactly like the whole one.

While that restore runs, the "All accounts" header shows how many logins
are already in the scope, e.g. "4/10", with a translated label for screen
readers. It goes away when the scope is complete or the restore is over -
a login that failed to reconnect does not keep it up.

`restoringAccounts` in the auth store marks the background restore; the
count itself is a pure function with its own tests.
2026-10-01 04:38:04 +02:00
Linus Rath 29c74c3b77 feat: sign in with an access token 2026-10-01 00:24:47 +02:00
Linus Rath 770fb2c84a fix: resolve trash and archive in a background login's own folders #1132
The folder-list cache from #1132 published a reused list only when it
was freshly fetched. When the Pro folder tree had loaded a login first,
the unified build reused that list and never stored it under the
login's JMAP id, so trash and archive for that login's mail were
resolved against the active login's folders until it pushed a change.
A reused list now fills any key nothing has published yet, without
overwriting a live one.

The Pro folder tree also marked a login as loaded before its first
fetch finished and never retried a failure, which left that login's
tree empty until an account switch. A failed load is now retried on
the next run, as the identities hook already does.
2026-09-30 20:35:13 +02:00
Linus Rath 2d2b0753be Merge pull request #1132 from lucamzanon/perf/multi-account-startup
perf: stop the startup request storm with many logins
2026-09-30 20:33:32 +02:00
Linus Rath efe44e09cc Merge pull request #1131 from lucamzanon/fix/deep-link-late-login
fix: let a notification link wait for a login that is still reconnecting
2026-09-30 20:32:44 +02:00
Linus Rath cd14807133 Merge pull request #1112 from theLucius7/fix/config-load-recovery-20260927
fix: recover from transient application configuration failures
2026-09-30 20:32:44 +02:00
Linus Rath bfc8e0e52e fix: show only that folder's unread mail when its unread count is clicked
Clicking a folder's unread count turns on the unread filter and runs
the search. Since the search scope stopped following the open folder
(#788) its default is "all folders", and since #1082 that fans out over
every shared account too, so the Inbox badge listed unread mail from
every folder of every account the login reaches.

The badge now scopes the search to its folder. That scope moves with
the open folder, the way the search itself carries across folders
(#553); picking a scope in the dropdown or clearing the filters stops
it. Clicking the badge from a tag view or the Scheduled list now also
leaves that view, as clicking the folder does.
2026-09-30 19:49:13 +02:00
Linus Rath 4db88255e8 fix: stop a sixth sso account from overwriting the first account's sign-in
`/api/auth/sso/complete` only accepted cookie slots 0-4 and quietly
used slot 0 for anything higher. Since the account limit rose to
`MAX_ACCOUNT_SLOTS` (50) on HTTP/2, adding a sixth account through
server-side SSO wrote its refresh token over the first account's cookie,
while the new account was registered on slot 5 with no cookie behind it.

The route and the OAuth callback's pending-slot check now use the same
bound as every other auth route.
2026-09-30 19:31:03 +02:00
lucamzanon 3ec9cd7201 perf(auth): restore the other logins a few at a time
After the login on screen, the rest were restored one after the other, so
the last of them - and with it a complete unified inbox - waited on every
other login's round trips: with ten logins it arrived about five seconds
after the first. Restore them four at a time. Each restore already handles
its own failures, so a slow or broken login holds up only its own slot.
2026-09-30 19:04:49 +02:00
lucamzanon 612dcc81ee perf: stop reloading every login's folders and identities on each connection
Two effects keyed on the account list reloaded every connected login each
time the list changed - and while a browser restores its logins it changes
once per connection, so the cost was 1 + 2 + … + N:

- useProMultiAccountMailboxes, which the Pro shell runs for its per-account
  folder tree, reloaded every login's folders: 55 Mailbox/get with ten logins.
  It now loads each login once per client, through the same cache as the
  unified scope, and reloads all of them once on an account switch.
- useProMultiAccountIdentities reread every other login's identities: 45
  Identity/get. Each mount now reads each account once; what is already
  known shows at once, and a read that outlives the run that started it
  still reaches the dropdown.

Both are keyed on the set of connected logins instead of `accounts`, which
also changes for display-name and login-time updates.
2026-09-30 19:04:49 +02:00
lucamzanon a578150c2d perf(unified): fetch each login's folders once, all logins at once
The unified scope is rebuilt on every connection while a browser restores
its logins, on every background push, and on every unified browse,
load-more and search - and each build asked every login for its folders
again, one login after the other. With ten logins that was ten full
Mailbox/get per build and well over a hundred in the first seconds, and the
slowest build waited on the sum of every login's round trip.

Fetch the logins in parallel, and fetch each login's list once: a build
shares a fetch that is already in flight and reuses a finished one until
that login reports a change - the push handlers invalidate the login they
hear from, so a background push now costs one Mailbox/get instead of one
per login. A 60 s limit backstops a change whose push was lost.

A reused list is never written back into `accountMailboxes`: optimistic
updates land there directly, and publishing an older list over them would
snap a just-changed counter back.
2026-09-30 19:03:34 +02:00
lucamzanon 1c5dbbcd0b fix: let a link wait for a mailbox that is still reconnecting
A notification tapped with the app closed reopens it on a link that names
its mailbox (`?slot=`). The browser reconnects its logins one at a time,
so that mailbox is often not connected yet when the link is read: with ten
logins, one on the Gmail bridge came back at 3.3 s and the link was judged
at 1.8 s. It was declared unavailable - "sign in to that account to open
this link" - and the app stayed on whichever mailbox was active.

Wait for the login instead, and give up only when it is not one of this
browser's logins, its restore fails, or 20 s go by.
2026-09-30 19:01:14 +02:00
Linus Rath 1531dae7eb feat: create filter rules from the message right-click menu 2026-09-30 13:52:41 +02:00
Linus Rath 62e07608fd fix: stop the mail list hiding dividers between some rows 2026-09-30 00:21:21 +02:00
Linus Rath 040cea30c5 fix: keep calendars visible after a failed calendar fetch 2026-09-29 23:59:03 +02:00
Linus Rath 9d140fa762 Merge pull request #1052 from lucamzanon/feat/account-row-colours
feat: colour unified rows by account and give every tag a hue of its own
2026-09-29 23:30:24 +02:00
Linus Rath ddc0b03055 fix: reuse cached access token on lite session restore #552 2026-09-29 21:25:02 +02:00
Linus Rath be0494cb5c fix: move mail to trash when emptying an ordinary folder 2026-09-29 20:35:09 +02:00
Linus Rath 324fb48312 fix: keep an opened mail in place in unread-first lists until another is opened 2026-09-29 20:28:39 +02:00
Linus Rath 3904af2cbb feat: search spam and trash themselves while they are open 2026-09-29 20:09:26 +02:00
Linus Rath 763732fdc9 feat: leave spam and trash out of search by default 2026-09-29 19:52:39 +02:00
Linus Rath 3ed484df35 feat: offer the code from sign-in mail as a copy chip in the list and reader 2026-09-27 11:19:48 +02:00
Lucius7nya d923b92034 fix: recover from transient application configuration failures
Retry interrupted or invalid runtime configuration requests with bounded backoff and per-attempt timeouts. Validate the complete response before caching it, and prevent stale HTTP-cached configuration.

Cover request and body timeouts, shared hook consumers, retry bounds, the server response contract, and both Lite configuration paths.
2026-09-27 15:41:21 +08:00
Linus Rath 5ce8c89c51 fix: open and save files shared with you in the office editor #1094 2026-09-26 22:28:34 +02:00
Linus Rath 5c3c3bea99 fix: keep next's metadata modules in the standalone build so the server starts 2026-09-26 22:15:53 +02:00
Linus Rath a920e91cae fix: keep mail deleted during a list refresh from reappearing #966 2026-09-26 19:11:30 +02:00
Linus Rath d155cead41 fix: revoke Lite refresh tokens on sign-out and keep no failed-login password
Signing out of Lite only deleted the refresh token from web storage, so
a copy taken from the browser kept working until it expired. Sign-out
now revokes it at the endpoint the mail server (or, for an external
provider, its discovery document) advertises, once the token is gone
from storage.

A password login whose token exchange failed for any reason fell back to
Basic auth and kept the password in sessionStorage. The password is now
only kept for the tab when the server has no token login at all (or
cannot be reached for it); otherwise the Basic session lives in memory
and a reload asks to sign in again.
2026-09-26 17:15:55 +02:00
Linus Rath 05fe7f8780 fix: stop handing instance details to visitors who are not signed in
Several endpoints answered anyone with what fingerprints a deployment:
`/api/health?detailed=true` (versions, memory), the update status
(exact version and the text of a pending security advisory, even with
`loginShowVersion` off), `/api/plugins` (installed plugins and the hosts
they may reach), the admin policy (push relays, sidebar app URLs,
plugin lists) and `/api/config` (every domain each server serves).

Visitors without a session now get:

- the plain liveness answer from `/api/health`;
- the update status only where the login page shows the version, and
  never the advisory text;
- a 401 from `/api/plugins`, which the client only syncs after signing
  in;
- the policy without plugin lists, push relays and sidebar apps, marked
  `X-Bulwark-Policy-Scope: public`; the client fetches the full policy
  once it signs in;
- server domain lists only when servers are picked by domain.

A mailbox session, a remembered session cookie or the admin session
counts as signed in. Deployments without a session secret cannot tell
sessions apart and keep the old answers.
2026-09-26 17:03:24 +02:00
Linus Rath 47342e9e08 fix: clear the accounts' leftovers when everyone signs out
A full sign-out emptied the stores but left the accounts' traces in the
browser for the next person at the computer: search history, open tabs
with message subjects, Files favourites and recent files, calendar
alert acknowledgements, and attachment bytes a plugin had staged for an
upload that never started. Other open tabs kept showing the signed-out
accounts' mail, and the boot snapshot of the mailbox list was restored
for whichever account happened to be active next.

- A full sign-out now removes those keys and the staged files, and
  tells the other tabs through a `storage` event so they leave for the
  login page.
- The boot snapshot names its account and is only restored for it.
- The login form only remembers the username for suggestions when the
  user did not decline "remember me".

Device preferences and `template-storage` stay: with settings sync off
they exist only on this browser.
2026-09-26 16:55:34 +02:00
Linus Rath 0040e5daca fix: keep plugin storage per account and delete it on sign-out
`api.storage` kept one `plugin:<id>:` namespace for everyone signed in
to the browser and left it behind after signing out, so a plugin read
what it had stored for the previous account, such as quick-notes'
notes on someone else's mail.

Keys now carry the account (`plugin:<id>:@<account>:<key>`). Signing an
account out or removing it deletes its keys, and a full sign-out
deletes all plugin storage. Keys written before this change are handed
to the first account that uses the plugin. Uninstalling a plugin still
removes everything under its prefix.
2026-09-26 16:47:29 +02:00
Linus Rath aa0075a8e1 fix: write only the changed keywords when tagging or pinning a message
Tagging and pinning replaced the message's whole keyword set with a
copy built from the list row. When the row was stale - the mail had
been read or answered on another device since - the write cleared
`$seen` or `$answered` again, and removed tags were written as `false`,
which a JMAP keyword map may not hold.

Keyword writes now go out as `keywords/<name>` patches for just the
keywords that change, removals as `null`, through the call that
reports a refused update. The local row gets the same patch.
2026-09-26 16:16:13 +02:00
Linus Rath 40edc41def fix: turn settings sync back on after signing out of one of several accounts
Signing out switches sync off for the signed-out account. When another
account stayed signed in, the app switched to it without turning sync
back on, so every later settings change stayed local and was replaced
by the server copy on the next load. Sign-out now resumes sync for the
account it switches to, the same way an account switch does.
2026-09-26 16:08:22 +02:00