Commit Graph
35 Commits
Author SHA1 Message Date
Linus Rath 7533fc516b fix: renew push for every connected account, not only the active one #1054
The background resync only touched the active client, so accounts
turned on from the all-accounts panel lapsed when the server's push
subscription expired after seven days. The resync now walks every
connected login in turn, active one first, and runs again once the
background logins finish connecting. Single-account behaviour is
unchanged.

The panel also lists which accounts failed to enable and why, instead
of only a count.
2026-10-10 19:24:43 +02:00
Linus Rath 02923fc1f1 Merge pull request #1054 from lucamzanon/feat/multi-account-push
feat: enable push notifications for several accounts at once
2026-10-10 19:24:10 +02:00
lucamzanon 9560a8b74f feat: optional encrypted backup of the account list
Users with several accounts can save the list of accounts in their browser
(address, server, label, colour) on the webmail server and bring it back on
another device. The list is encrypted in the browser with a password the user
chooses (PBKDF2-SHA-256, 600,000 iterations, AES-256-GCM, owner bound as
additional data); the server stores only the envelope, one file per owner
under SETTINGS_DATA_DIR/account-archives. No mailbox passwords are included.

Off by default behind the admin feature gate accountArchiveEnabled, pinned
off in Lite. Every method of /api/account-archive needs a session cookie of
the owner, matched without regard to case, whose credential is checked
against the mail server on each call; listing included.

Restored accounts this browser lacks are listed as awaiting sign-in: they
survive a reload, and opening one leads to the sign-in form with the address
filled in. Accounts already present get their archived label and colour.
2026-10-09 10:54:38 +02:00
Linus Rath 7f78d4837a fix: don't offer the auto-reply audience on a shared account #1152 2026-10-06 23:31:03 +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 6e5c5390df Merge pull request #1162 from dealerweb/fix/focus-trap-rerender
fix: keep the focus in dialogs while the page behind renders again
2026-10-06 23:28:51 +02:00
Linus Rath 1e10aa9bd6 feat: move several folders at once from the folder settings #1173 2026-10-06 15:49:53 +02:00
dealerweb 0f7e6d0c16 fix: keep an open filter dialog through a reload in the background
A change to the Sieve script from another device or tab reloads the
filters, and the page showed its loading or error state instead of its
content. That took an open rule dialog or the Sieve editor with it,
together with whatever was typed there. While a dialog is open, the
page keeps what it has.
2026-10-02 12:42:40 +02:00
dealerweb 0c376dd0e4 fix: keep the focus in a dialog's field while the page behind renders again
useFocusTrap started over whenever its onEscape changed, and callers pass
a new function on every render. Each start moved the focus back to the
opener and then to the dialog's first element, so a field lost the focus
whenever the page behind rendered again: every 30 seconds through the
connection check, or when new mail updated the folder counts. The trap
now reads the latest onEscape from a ref and starts once per opening.

A view change in a dialog (a list giving way to a form) can remove the
element that had the focus. The restart used to pull it back by
accident; the trap now does that on purpose, and only when the focused
element was removed.
2026-10-02 12:42:40 +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 d8f10f4fd6 Merge pull request #1148 from lucamzanon/fix/push-settings-refresh
fix(settings): check push again when the user comes back to the tab
2026-10-02 10:30:43 +02:00
Linus Rath 15839afec2 Merge pull request #1154 from dealerweb/feat/filter-all-messages
feat: a filter condition that matches every message
2026-10-02 10:30:42 +02:00
dealerweb 6da2bf9a43 fix: count the forwards one message can collect against the redirect limit
The rule editor added up the forwards of all enabled rules against the
server's redirect limit, which applies per message. Rules run in order,
and a rule that stops ends the script for its messages, so a rule that
forwards and stops locked forwarding in every other rule, although no
message can reach both.

Count the most forwards one message can collect instead, and warn above
the rule list when the order, a switch or a drag lets one message
collect more than the limit.

Builds on the stop fix: without it, "Stop processing" after "Delete
silently" or "Reject" writes no stop.
2026-10-01 16:33:51 +02:00
dealerweb c55b9f4929 feat: a filter condition that matches every message
The rule editor had no way to say "every message that arrives"; the
workaround was a condition like "From contains @", which only works if
you know it. "All messages" is now a condition of its own, at the end of
the field list. It takes no comparator and no value, writes Sieve's
`true` test, and combines with other conditions as any condition does.
The rule list shows it on its own. A rule with it is never applied to
the messages already in a folder: it would act on all of them.
2026-10-01 14:58:00 +02:00
lucamzanon ce3b12aa21 fix(settings): check push again when the user comes back to the tab
The notification settings read the push state once, when they opened.
Push turned off meanwhile - in the browser's site settings, or from
another tab - still showed as on until the page was reloaded. The state
is now read again when the window regains focus or the tab becomes
visible, unless an enable or disable is in progress or has just failed.
A failed check reads as off instead of leaving the panel unchanged.
2026-10-01 12:43:40 +02:00
Linus Rath 410d6b3532 Merge branch 'main' of https://github.com/bulwarkmail/webmail 2026-10-01 11:52:29 +02:00
Linus Rath 4f48af6307 fix: spam siege restyle 2026-10-01 10:29:20 +02:00
lucamzanon 405c0e656e fix(push): show each login's real push state in the all-accounts panel
Push state is stored under the JMAP account id the server assigned
(enableWebPush keys it on client.getAccountId()), but the all-accounts
panel looked it up under the login's own id, so every account read
"notifications off" even right after enabling them. Read it back
under the JMAP account id.
2026-10-01 07:36:44 +02:00
dealerweb 9130ec7e14 fix: wrap a filter condition's values in the expanded rule view
The values of a multi-value condition were glued together as one
unbreakable run - the space around each "or" was a margin, not a
character the line could break at - so a rule with several addresses ran
out of its card. Each value is now an item of its own: they wrap between
each other under the first value, with "or" leading the next line and
still inside the condition's chip, apart from the "and"/"or" between
conditions. A single value too long for any line, in a condition or an
action, breaks inside itself instead of overflowing.
2026-09-30 23:40:13 +02:00
Linus Rath e4ef2fa3d6 fix: keep an abandoned pairing re-auth from breaking the next sign-in
The Link Mobile App step-up marked the tab before asking the server to
start the provider round trip. When that request failed, or the user
backed out at the provider, the mark stayed, and the next sign-in's
callback in that tab went to the pairing route and failed. The mark is
now set only once the round trip starts, holds that round trip's state,
and the callback drops one that does not match.
2026-09-27 04:56:48 +02:00
Linus Rath 377c283d86 fix: say up front when the mobile app cannot use an http mail server
The app refuses a JMAP server over plain http (loopback aside), yet the
desktop issued a code, reported "Signed in on your phone" once it was
redeemed, and the phone failed. Linking now stops before the step-up
with an explanation.
2026-09-27 04:53:49 +02:00
Linus Rath 96e0e6b036 fix: keep the pairing QR on screen while the Security tab checks the server 2026-09-27 04:18:09 +02:00
Linus Rath 1446fd39dc fix: link the mobile app from every account with a sign-in of its own
"Link Mobile App" only appeared for OAuth sessions, its step-up could
only go through the identity provider (which failed without an OAuth
client and bounced password+TOTP users to the provider's login page),
and it handed the phone the desktop's own refresh token. Revoking that
token on sign-out, or a rotating provider, signed the phone out too; a
confidential client stopped the phone at its first token expiry; and an
identity provider on another host was refused by the app.

The step-up now mints the phone's own grant:

- Password and password+TOTP accounts confirm the current password (and
  code) in the dialog. The server uses it once to get an OAuth grant
  from Stalwart's structured login for the app's own client, retries
  with the webmail's client when the server insists on a registered one,
  and falls back to an app password ("Bulwark mobile (paired <date>)")
  or, on servers that offer neither, the verified password. Failed
  attempts are limited per account so the webmail's address cannot be
  banned by guessing through it.
- SSO accounts keep the provider round trip, now with `offline_access`
  where offered; its fresh tokens become the phone's grant instead of
  being thrown away, and must belong to the account being paired.
- The account and server always come from the slot's verified session
  context, never from the request.

Grants live 5 minutes server-side, codes 2 minutes, both single use; the
store is pinned on `globalThis`. Redeem answers `invalid_code`,
`expired_code` or `used_code`, and the desktop polls a separate status
id to replace the QR with "Signed in on your phone". A phone whose
client is confidential, or whose provider is on another host, renews
through `/api/auth/pair/token` with a sealed refresh token, so current
app builds work unchanged. Signing out discards a pending grant but no
longer affects a paired phone.

The dialog shows for every account (not in Lite), with a copyable
sign-in link, a TOTP field when needed, and the app's own "Scan a
sign-in code" wording.
2026-09-27 04:16:18 +02:00
Linus Rath 8c746fb7af fix: keep inbox-only push working for accounts shared without an inbox #983 2026-09-23 05:56:28 +02:00
Aaron Guise 4078a7494a fix(notifications): don't let the inbox-only re-sync hang on "Working…" 🐛
The re-sync effect depended on pushStatus.kind, so its own
setPushStatus({ kind: 'busy' }) re-fired the effect, whose cleanup
cancelled the in-flight enableWebPush - the push row then sat on
"Working…" forever and never updated the filter. Depend only on the
setting flip; read client/relay/status live from the recreated closure.
2026-09-23 11:07:58 +12:00
Aaron Guise c89fdcbf0c feat(notifications): apply inbox-only push filter immediately on toggle ✨
Flipping the setting only wrote to the store; the server-side JMAP push
filter stayed stale until the next app launch ran resyncWebPush. When push
is already enabled on the device, re-run enableWebPush on change so the
toggle takes effect without a restart.
2026-09-23 11:07:58 +12:00
Linus Rath 8e4296ae5b fix: add the missing scheduled folder role label #495 2026-09-07 22:34:47 +02:00
Linus Rath f4d2fd950c fix: sort folders in the role assignment dropdown #984 2026-09-07 22:34:47 +02:00
Linus Rath 3ef2b45fb4 feat: configurable message-list ordering #718 2026-08-25 19:20:48 +02:00
Linus Rath 69c8c041f9 feat: detect and import unrecognized JMAP keywords as tags #658 2026-08-06 18:44:14 +02:00
Linus Rath e2da05c130 feat: deep links for mail, calendar, contacts, files, settings and improve screen reader support
#733 #722 #721  #720  #719
2026-08-06 16:47:20 +02:00
Mathy Vanvoorden 56d11b5759 fix: remove the reset-to-defaults button from tag settings
If you carefully crafted your tags and then click this button by accident, all
your hard work is gone. A confirmation message would be the other solution but
since I have difficulty to grasp when you would need such a button, I propose
to just remove it.
2026-07-29 17:18:42 +02:00
Mathy Vanvoorden 0c1e238223 feat: allow hidden tags, either permanent or when there are no unread messages 2026-07-29 17:18:42 +02:00
Mathy Vanvoorden ca0ba818b7 feat: Add nesting of tags in a tree
- levels are joined by forward slashes in the keywords
- behaviour is opt-in for now
- long paths are shortened if there is not enough display room

Closes #687.
2026-07-29 17:18:38 +02:00
Linus Rath 454d51e283 feat: refactor email tagging system to use labels instead of colors
- Updated EmailContextMenu to replace color tag functionality with label tags.
- Modified EmailListItem to display label badges for emails based on keywords.
- Enhanced EmailViewer to include a tag picker for emails.
- Adjusted ThreadListItem to show label badges for email subjects.
- Added tests for email list item and keyword settings to ensure proper functionality of the new tagging system.
- Updated localization files to reflect changes from color tags to labels in multiple languages.
2026-03-11 18:56:43 +01:00