Commit Graph
139 Commits
Author SHA1 Message Date
Linus Rath 2a952f4b72 Merge branch 'feat/enterprise-pack-c'
# Conflicts:
#	lib/sign-out-cleanup.ts
2026-11-01 07:39:30 +01:00
Linus Rath ebb33619a0 Merge branch 'feat/enterprise-pack-a' 2026-11-01 07:36:59 +01:00
Linus Rath 8d48f98261 fix: keep failed messages for the next import run and stop an import that can't go on
A message that failed for a passing reason (a dropped connection, a
server error, an expired token) used to count as done, so continuing
the import skipped it for good, and a slow upload followed by a cancel
imported the messages after it twice.

- The checkpoint keeps every message that is done, also past a gap,
  and the next run of the same import tries only the rest again.
- Ten failures in a row, a full mailbox (overQuota) or a refusal
  (forbidden) stop the import with a message instead of going through
  the whole file.
- An upload without an answer pauses the import instead of failing the
  message: 30 seconds, then until the upload window ends.
- Email/import, the export's Blob/get and folder listing pass the
  client's own 429 pause by, and a 429 on them doesn't pause the rest
  of the app.
- Signing out stops the job of that account only, writes no checkpoint
  and imports nothing more afterwards; another account's sign-out
  leaves the job alone.
- One tab imports a file at a time (Web Locks), and Import waits until
  the dialog knows whether an earlier run can be continued.

The import dialog tests now wait for the Import button to be enabled
before clicking it, since it stays off during that lookup.
2026-11-01 06:37:08 +01:00
Linus Rath 1c7f81bcc6 feat: pick a classification label in the composer and stamp it on sent mail 2026-11-01 05:33:12 +01:00
Linus Rath 15f0bd198c feat: export a folder or the whole account as mbox or ZIP 2026-11-01 05:28:58 +01:00
Linus Rath 877198a4bf feat: answer invitations in a calendar shared with you as its owner #634 2026-11-01 05:05:12 +01:00
Linus Rath d714957f98 feat: import mbox, EML and ZIP mail with progress, pause and resume 2026-11-01 04:58:55 +01:00
Linus Rath 50f95f4463 feat: report a message as phishing and move it to Junk 2026-11-01 04:58:01 +01:00
Linus Rath 73a7bb7bcd fix: send the headers plugins add to an outgoing message instead of dropping them 2026-10-31 17:40:20 +01:00
Linus Rath 96797597c5 Merge branch 'main' into feat/org-features 2026-10-28 15:14:34 +01:00
Linus Rath 1322f6c3f0 fix: switch between demo accounts without the app crashing 2026-10-28 09:48:49 +01:00
Linus Rath 5d90a850bd Merge branch 'feat/org-recovery' into feat/org-features
# Conflicts:
#	components/email/email-list.tsx
2026-10-28 09:17:56 +01:00
Linus Rath 4d846cfc91 fix: read the whole deleted items archive newest first and only for the account shown #1319
Stalwart's x:ArchivedItem/query without calculateTotal scans ids
ascending, stops at 5000 and only then reverses them, so the newest
items of a large archive never showed, and admin accounts got every
account's items. The review of the branch also found gaps around
account switches, restores and focus.

- Read the archive in pages of 500 with calculateTotal, newest first,
  stop at a whole page older than the days limit, and offer
  "Show older" after 2000 items; detection asks for one item.
- Send the accountId filter and fall back without it on
  unsupportedFilter; keep only items of the account, never one without
  an account id.
- Ask nothing until config and policy are loaded and the policy is the
  current account's; the later check after a refusal is forced.
- Show the Trash link only when the archive holds a message inside the
  days limit; the Settings page shows whenever the server has the
  feature and says that archiving must be on in Stalwart.
- Restore: notFound leaves the list with a note, forbidden hides the
  page, results for another account are dropped, the button keeps focus.
- Forget what servers said at a full sign-out.
2026-10-28 09:08:44 +01:00
Linus Rath 33ca8efb0f fix: keep shared tags safe when members edit at once or the server fails
The first version wrote the whole tag list on every change, guessed a
group when the server wouldn't say, and treated a failed read like a
server without metadata. Now:

- Definitions are a map by tag id under `tags`, and each change patches
  one tag (`metadata/org.bulwarkmail.tags/tags/<id>`), so two members
  adding tags at once both keep theirs. Fields and colors this version
  doesn't know survive. The `{ list }` and bare-array shapes are still
  read and converted by the first save.
- Shared tags appear only for confirmed group principals. A failed
  principal lookup keeps the user's own tags and asks again later.
- "No metadata advertised" (0.16) and "the read failed" are separate.
  A failed read keeps the last good tags; a failed save keeps the change,
  says so, and sends it with the next good read after the client's
  pause.
- Push reloads wait for 5 s of quiet and skip the echo of our own save;
  a returning tab reloads definitions only, and the scan for unnamed tags
  runs once per sign-in and then at most every 30 minutes.
- The editor names its buttons after the tag, shows the delete warning
  and 44 px buttons on phones, returns focus to the edit button, and
  asks for Inbox rights only for confirmed groups.
2026-10-28 08:58:11 +01:00
Linus Rath 42b694fb2c feat: save attachments of shared and group mailboxes to Files by copying the blob on the server 2026-10-28 08:16:47 +01:00
Linus Rath 7f06ed7637 Merge branch 'feat/org-features' into feat/org-tags
# Conflicts:
#	lib/admin/restriction-keys.ts
2026-10-28 08:14:39 +01:00
Linus Rath ba6e6af014 feat: recover deleted items from Stalwart's archive #1319 2026-10-28 07:27:47 +01:00
Linus Rath b049b6f470 feat: share tag names and colours in group mailboxes #1310 2026-10-28 06:43:46 +01:00
Linus Rath 5ce0f96f26 fix: read delegate sharing only for the apps that are on, with the file tree alongside 2026-10-28 06:27:07 +01:00
Linus Rath bcf4f3a7a6 feat: give a delegate access to mail, calendars and contacts in one step 2026-10-28 05:11:23 +01:00
Linus Rath 6c5aead20b feat: undo a delete to Trash, archive, move, spam report or remove from folder from its toast 2026-10-28 03:11:43 +01:00
Linus Rath 7ea0a7d58d feat: read and write account metadata for shared Bulwark data 2026-10-28 01:14:30 +01:00
Linus Rath 4b69f1307f feat: let admins scope the user policy per domain or group 2026-10-28 00:29:02 +01:00
VasilisNtovolos ac99c68ef3 fix: reconcile confirmed permanent bulk deletions 2026-10-27 14:51:38 +02:00
Linus Rath cf2c27157d Merge branch 'main' into feat/stalwart-1.0
# Conflicts:
#	components/contacts/contacts-app.tsx
#	components/email/__tests__/composer-discard.test.tsx
#	components/email/calendar-invitation-banner.tsx
#	components/email/email-composer.tsx
#	components/mail/mail-app.tsx
#	components/pro/pro-compose-tab-body.tsx
#	components/pro/pro-email-tab-body.tsx
#	components/ui/__tests__/avatar.test.tsx
#	components/ui/avatar.tsx
#	hooks/use-mailbox-drop.ts
#	lib/__tests__/calendar-alerts.test.ts
#	lib/__tests__/jmap-calendar-tasks.test.ts
#	lib/__tests__/jmap-send-threading.test.ts
#	lib/demo/demo-client.ts
#	lib/demo/fixtures/calendars.ts
#	lib/jmap/client-interface.ts
#	lib/jmap/client.ts
#	lib/web-push.ts
#	stores/__tests__/contact-store.test.ts
#	stores/__tests__/email-store-thread-key.test.ts
#	stores/calendar-store.ts
#	stores/contact-store.ts
#	stores/email-store.ts
2026-10-26 10:14:12 +01:00
Stefan Hildebrandt 04efcfc1f3 fix: save a group identity's drafts in the group account's Drafts (#1090)
Drafts were always created in the primary account, so a message composed as
a group identity sat in the member's own Drafts while its Sent copy (since the
previous commit) went to the group.

- createDraft takes { accountId, previousDraftAccountId }: the draft goes to
  the account that owns the From identity (resolveSendAccountId, now public
  and briefly caching the identity lists for autosave), and the replaced
  version is destroyed in the account it lives in.
- The composer tracks draftAccountId next to draftId (save, re-resolve of
  attachment blobs, send, discard, stashed state), since draft ids are only
  unique within one account.
- sendEmail destroys the sent draft in options.draftAccountId; discarding
  (inline and Pro tab) deletes it there, through the identity's login.
- Re-opening a draft from a group/shared Drafts folder (or a stamped
  aggregate row) keeps its account.
- resolveSendAccountId is optional on IJMAPClient; without it drafts stay in
  the primary account.
- Tests: client unit tests for draft placement / replacement / send cleanup,
  E2E spec 18 draft cases (autosave + send, discard).
2026-10-19 16:33:27 +02:00
Linus Rath 1c8e7025e6 Merge pull request #1178 from VasilisNtovolos/fix/mixed-account-bulk-archive
fix: route bulk archive by owning account
2026-10-18 14:33:41 +02:00
Linus Rath a11a89d689 fix: skip vacation filter rules on shared accounts without sieve #1263 2026-10-18 13:46:47 +02:00
Linus Rath 107d731af7 feat: remove a message from just the open folder when it is in several #615 2026-10-17 19:18:25 +02:00
Linus Rath 1ad1e31781 feat: set message priority in the composer #970 2026-10-17 19:18:23 +02:00
Linus Rath f8ef54dbea fix: enable push notifications when another user shares a folder with you #1050 2026-10-17 19:17:59 +02:00
Linus Rath 964626794b feat: browse the company directory in contacts and the composer #1253 2026-10-16 20:46:27 +02:00
Linus Rath 41a0716c50 Merge branch 'main' into feat/stalwart-1.0 2026-10-16 01:41:48 +02:00
Linus Rath 6ef0d97900 fix: count storage quota against softLimit when the server sets one #1242 2026-10-15 14:31:09 +02:00
VasilisNtovolos 69519bf344 fix: respect account permissions when creating Archive 2026-10-15 14:24:44 +03:00
Linus Rath db2c0aff3d perf: load conversation sizes with the folder page instead of in a second request 2026-10-15 00:14:57 +02:00
Linus Rath 2d67442a2b perf: list search results without waiting for the highlighted excerpts 2026-10-14 23:51:17 +02:00
Linus Rath c633185cda Merge branch 'main' into feat/stalwart-1.0
# Conflicts:
#	CONTRIBUTING.md
#	LICENSE
#	README.md
#	app/api/calendar-agenda/route.ts
#	app/api/wopi/files/[fileId]/contents/route.ts
#	app/api/wopi/launch/route.ts
#	components/calendar/calendar-app.tsx
#	components/calendar/calendar-day-view.tsx
#	components/calendar/calendar-month-view.tsx
#	components/calendar/calendar-week-view.tsx
#	components/calendar/event-modal.tsx
#	components/contacts/contact-detail.tsx
#	components/email/email-composer.tsx
#	components/email/email-list.tsx
#	components/files/file-browser.tsx
#	components/files/files-app.tsx
#	components/files/folder-tree-sidebar.tsx
#	components/filters/filter-rule-modal.tsx
#	components/mail/mail-app.tsx
#	hooks/use-mailbox-drop.ts
#	integration/run-tests.sh
#	lib/__tests__/calendar-agenda-route.test.ts
#	lib/__tests__/jmap-draft-lifecycle.test.ts
#	lib/__tests__/web-push.test.ts
#	lib/account-state-manager.ts
#	lib/jmap/client-interface.ts
#	lib/jmap/client.ts
#	lib/jmap/types.ts
#	lib/message-list-order.ts
#	lib/sieve/generator.ts
#	lib/sieve/parser.ts
#	lib/telemetry/payload.ts
#	lib/thread-utils.ts
#	lib/unified-mailbox.ts
#	lib/web-push.ts
#	lib/wopi/token.ts
#	locales/ar/common.json
#	locales/ca/common.json
#	locales/cs/common.json
#	locales/da/common.json
#	locales/de/common.json
#	locales/en/common.json
#	locales/es/common.json
#	locales/fa/common.json
#	locales/fr/common.json
#	locales/he/common.json
#	locales/hu/common.json
#	locales/it/common.json
#	locales/ja/common.json
#	locales/ko/common.json
#	locales/lv/common.json
#	locales/mn/common.json
#	locales/nb/common.json
#	locales/nl/common.json
#	locales/pl/common.json
#	locales/pt/common.json
#	locales/ro/common.json
#	locales/ru/common.json
#	locales/sk/common.json
#	locales/tr/common.json
#	locales/uk/common.json
#	locales/zh-TW/common.json
#	locales/zh/common.json
#	scripts/lite/lib.mjs
#	stores/__tests__/calendar-store-synthetic-ids.test.ts
#	stores/__tests__/email-store-shared-folder-keywords.test.ts
#	stores/__tests__/email-store-unified-actions.test.ts
#	stores/__tests__/file-store.test.ts
#	stores/email-store.ts
#	stores/file-store.ts
#	stores/message-list-tabs-store.ts
2026-10-12 08:35:08 +02:00
Linus Rath 37440e02b0 fix: let tasks move into and live in calendars shared with you #1204 2026-10-12 07:36:46 +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 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 a4e313f73e fix: leave trash and spam out of tag views and their counts #1156 2026-10-02 23:15:26 +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 1531dae7eb feat: create filter rules from the message right-click menu 2026-09-30 13:52:41 +02:00
Linus Rath 040cea30c5 fix: keep calendars visible after a failed calendar fetch 2026-09-29 23:59:03 +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 efdc6a1dc3 fix: keep a reply draft in its thread when it is re-opened or undone
Drafts were saved without In-Reply-To and References, and a draft
re-opens in compose mode, where the composer has no original message to
rebuild them from. A reply that was saved and re-opened, or sent and
brought back with Undo, therefore went out as a new conversation.

`createDraft` now stores the threading headers, the composer saves them
with every draft of a reply, and re-opening a draft carries them into
the composer for later saves and the send.
2026-09-26 16:06:28 +02:00
Linus Rath 9f11210b6a fix: undo and edit a group-identity send in the group's own account
A message sent or scheduled from a shared or group identity lives in
that account. Undo send, "Edit" on a scheduled message and cancelling
one cancelled the submission in the right account (#874), but then
restored the message to Drafts, fetched and deleted it through the
login's primary account. That hit whichever primary message had the
same id - pulling an unrelated mail into Drafts or deleting it - and
left the unsent message sitting in the group's Sent.

`restoreEmailToDraft` now takes the account, and the store passes the
submission's account to restore, fetch and delete, using that
account's Drafts and Sent.
2026-09-26 15:18:20 +02:00