Commit Graph
40 Commits
Author SHA1 Message Date
Stefan Hildebrandt 242fe6aa2d fix(mail): unified mailbox missed accounts that had not connected yet
After a reload the unified mailbox showed only the active account's mail;
other accounts appeared only once visited. Intermittent (~1 in 14), and
not a timeout - nothing on this path uses setTimeout/AbortSignal/race.

checkAuth awaits only the active account and restores the rest in a
fire-and-forget background loop, registering each client only after
connect() resolves. buildUnifiedAccountClients drops accounts with no
live client (`if (!c) continue`). And isConnected was persisted, so it
rehydrated stale-true: the account passed the isConnected filter while
failing the client lookup, and connectedAccountsSignature already looked
complete at mount so nothing re-fired - and the effects keyed on it only
recompute counts, never the list.

Clear isConnected/hasError on rehydrate so both the filter and the
signature describe this session, and refetch the open unified/cross list
when an account joins the connected set (growth only, not on first
render, not while a search owns the list). Extracted
connectedAccountsGrew so the membership check is testable without
rendering MailApp.

Verified the reset cannot block reconnection: checkAuth reads the full
account list and never filters on isConnected.

Fixes #950
2026-09-02 22:15:50 +02:00
Shuki Vaknin 12f01409a6 feat(search): add "clear search when switching folders" setting
Switching folders currently keeps an active search applied and re-runs
it in the newly selected folder — deliberate behavior from #553, where
users wanted the query to follow them across folders. Others expect the
opposite: clicking Inbox/Sent should drop the search and just browse the
folder.

Add an opt-in setting (Settings → email behavior) that reconciles both.
Default is off, so #553's search-follows-you behavior is unchanged for
everyone. When on, any folder click clears the text query and advanced
filters and browses the folder instead of re-running the search.

Applied uniformly across the four folder-entry paths in MailApp — normal
mailboxes, per-account (Pro sidebar) folders, unified views, and cross
views — via one helper so no path diverges.
2026-09-01 21:26:08 +03:00
Linus Rath b11cf4b1a5 feat: offer deleting a non-empty folder with its messages 2026-08-29 18:23:09 +02:00
Linus Rath 03b588096d feat: filter advanced search by message size 2026-08-29 18:20:21 +02:00
Linus Rath cc90903f05 feat: request delivery notifications and REQUIRETLS when sending 2026-08-29 18:16:52 +02:00
Linus Rath e54d179f32 feat: surface CalendarEventNotification invitations and updates 2026-08-29 18:11:50 +02:00
Linus Rath 0b39e43a4d feat: surface ShareNotification changes as toasts 2026-08-29 17:59:04 +02:00
Linus Rath 6e5aca60a9 feat: share mail folders with other users via mail:share 2026-08-29 17:53:54 +02:00
Linus Rath a15af72296 fix: resolve shared-account identity and email on reschedule; isolate per-account scheduled-list failures 2026-08-25 20:26:48 +02:00
Linus Rath 1305d13a77 Merge pull request #874 from rotterp/fix/scheduled-send-shared-accounts
fix: route scheduled sends to the account that owns the submission
2026-08-25 20:23:58 +02:00
Linus Rath 99daa53c8c feat: search suggestions with recent searches and contact autocomplete #845 2026-08-25 20:09:18 +02:00
Linus Rath 55ef780342 fix: cap live SSE streams per tab so many logins can't starve JMAP requests #702 2026-08-25 20:03:29 +02:00
Linus Rath 987ed75aaf Merge pull request #861 from dealerweb/fix/fresh-compose-tab-title
fix: keep the fresh-compose tab title clear of the selected email subject
2026-08-25 19:26:54 +02:00
Linus Rath a29cf27ff8 fix: route multi-account email lookups and invitation parsing by source account #847 2026-08-25 18:50:50 +02:00
Linus Rath 6052f32129 Merge pull request #876 from dealerweb/fix/sent-draft-stale-viewer
fix: clear the viewer when sending destroys the displayed draft
2026-08-21 09:26:26 +02:00
Linus Rath 37d85e5447 feat: fullscreen email view in the standard interface 2026-08-20 16:19:12 +02:00
Linus Rath fc88fc6d3b feat: drag a mail to a new browser tab to open it fullscreen 2026-08-20 15:55:44 +02:00
Linus Rath 65e70a8088 feat: address bar reflects the focused Pro tab + live deep-link delivery to mounted surfaces 2026-08-20 15:35:41 +02:00
Linus Rath d83c83b0a9 fix: drop the folder sidebar from fullscreen email 2026-08-20 15:11:25 +02:00
Linus Rath 3e5ed4f3c4 feat: folder tabs via drag & drop + sidebar in fullscreen email tabs 2026-08-20 15:06:45 +02:00
Linus Rath 5d2b937ae3 fix: keep attachments when re-opening a draft and destroy old draft versions only after a successful create/send #849 2026-08-20 13:25:25 +02:00
dealerweb 0fd83203cf fix: clear the viewer when sending destroys the displayed draft
Sending an edited draft destroys it server-side, but the viewer kept
rendering the deleted message after the composer closed: the mailbox
list refreshed (row gone, counter down) while selectedEmail still
pointed at the destroyed draft. Clear the selection like a delete does
- and return to the list on mobile - when the email on display is the
draft the compose session was opened from or its latest autosaved
successor (autosave replaces the draft under a fresh id on every save,
so matching the sent draftId alone would miss).
2026-08-20 13:12:06 +02:00
Patrick Rotter 4461935a3c fix: route scheduled sends to the account that owns the submission
A delayed send composed from a shared/group identity is created in the
*shared* JMAP account, because sendEmail() passes the target account to
getSubmissionAccountId(). Every read/write on the scheduled surface,
however, asked the primary submission account only:

    getScheduledEmails / cancelEmailSubmission / rescheduleEmailSubmission
        -> getSubmissionAccountId()   // no argument = primary account

So a message scheduled from a shared address is accepted and stays
pending on the server — it *will* be delivered at sendAt — while the UI
cannot list it, cancel it or reschedule it. Cancelling appears to work:
EmailSubmission/set against the primary account answers `notFound` in
notUpdated, which the client never surfaced, and the mail goes out
anyway. Silent unrecallable mail is worse than a failed send.

Stalwart advertises urn:ietf:params:jmap:submission (with FUTURERELEASE
and a non-zero maxDelayedSend) on group accounts as well as personal
ones, so scheduling from a shared address is legitimate.

Data layer:
- getSubmissionAccountIds() enumerates every submission-capable account;
  getScheduledEmails() queries each, tags every result with the account
  that owns it, and fetches each submission's Email from that same
  account (it lives beside the submission, not in the primary account).
  It also returns per-account totals computed over all pending
  submissions, so a badge stays correct past the first page.
- resolveSubmissionAccountId() locates a submission's owning account so
  cancel/reschedule (and the sendAt/envelope helpers) act on the right
  one. Callers that know the account pass it and skip the lookup.
- sendEmail() reports the submission's account in SendEmailResult, and
  the pending-undo record carries it, so the "Undo send" / "Send now"
  toast actions — which know only a submission id — address the right
  account directly rather than searching for it inside the undo window.
- The delayed-send gate considers any reachable submission account
  rather than only the primary one.

UI:
- Each shared account gets its own "Scheduled" sidebar row, scoped to
  that account; the own row keeps showing the combined view. The scope
  travels beside the virtual mailbox id, so the store's single
  __scheduled__ id is unchanged, and it also drives which row renders as
  selected and whether the scoped list has more to load.

Tested against a real Stalwart (integration/tests/11-shared-scheduled-send):
two server-truth tests pin the mechanism (the submission is invisible to
the primary account, and cancelling through it is a silent no-op) and two
UI tests cover the fix. All three UI-level assertions — the combined
list, the scoped row, and which row is selected — fail without this
change.
2026-08-20 11:23:23 +02:00
dealerweb d6ae84f5e8 fix: keep the fresh-compose tab title clear of the selected email subject
The Pro-mode bridge that turns an inline compose intent into a compose
tab derived its working title from the draft subject or, failing that,
the selected email's subject - regardless of mode. For reply/forward the
selected email is the message being acted on; for a fresh compose it is
whatever happens to be highlighted in the list, so "New Email" opened
with an unrelated subject in the tab strip (#856). Extract the rule into
buildComposeTabTitle: a fresh compose only ever uses its own draft
subject (mailto, resumed draft) and otherwise starts on the "New
message" fallback. Typing a subject already renames the tab live via
handleSaveState, so nothing else changes.
2026-08-18 15:48:05 +02:00
Linus Rath 58ddd01cf7 Merge pull request #792 from dev-hive-kazniisa/fix/shared-mailbox-folder-management
fix(email-store): route shared-folder management to the owner account
2026-08-17 12:18:24 +02:00
Linus Rath c78b964c69 perf: halve time-to-mail-list (lazy locale catalogs, code-split viewer/composer, shorter auth waterfall, boot snapshot) 2026-08-17 01:50:10 +02:00
Linus Rath 66be55f6a7 feat: show a pull-to-refresh indicator when dragging down #826 2026-08-16 22:50:51 +02:00
Linus Rath 644a9a0793 fix: search folder filter defaults to all folders and persists #788 2026-08-16 22:32:14 +02:00
Linus Rath ffff165a4b Merge pull request #800 from rotterp/fix/shared-folder-account-context
fix: a directly-selected shared folder has no account context (tags, compose identity)
2026-08-16 21:27:18 +02:00
Linus Rath 527872cc13 Merge pull request #773 from dealerweb/fix/mailto-unsaved-draft-guard
fix: route mailto composer replacement through the close guard
2026-08-16 03:08:44 +02:00
Linus Rath 47cf0ed90d feat: rework pro split-screen shell, per-pane tab strips, drag & drop, pane-scoped overlays 2026-08-16 03:03:40 +02:00
Linus Rath 8e3b4e71a1 fix: open right conversations in shared mailbox #814 2026-08-16 02:19:04 +02:00
dealerweb 9ff14f5781 fix: route mailto composer replacement through the close guard
Opening a mailto: link while the composer held unsaved text used to
replace the session outright and drop the text on the floor. The mailto
entry point now hands a follow-up to the composer's dirty-aware close
handler, so the "Save or discard draft?" dialog runs first: save and
discard continue into the mailto draft, cancel leaves the current draft
untouched. When a failed save hands the text back to the continue-draft
slot (#702), that rescue wins and the follow-up is dropped.
2026-08-13 11:00:13 +02:00
Patrick RotterandClaude Opus 5 a971d5a119 fix: preselect the shared folder identity for new messages
Composing a new message while a shared/group folder was selected started
it as the reaching login's primary address instead of the shared one,
even when a matching send-as identity existed.

`composeFromAccountEmail` was resolved from
`getAccountById(viewingAccountId ?? activeAccountId)`. A delegated
mailbox has no separate login and no AccountEntry, and
`setViewingAccount` is only ever called with `null`, so selecting a
shared folder leaves both ids pointing at the primary account:
`findComposeIdentityId` was handed the wrong address and correctly
matched the primary identity.

Resolve the address from the selected mailbox instead, falling back to
the account lookup for own folders. `accountName` is the JMAP
`Account.name`, which RFC 8620 describes as "e.g., the email address of
the account"; a server that puts a human label there produces no
identity match and the composer keeps the primary identity, which is the
previous behaviour. Replies are unaffected - `composeFromAccountEmail`
is only read when `mode === 'compose'`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 10:42:55 +02:00
Patrick RotterandClaude Opus 5 3560cf258b fix: route keyword writes to the selected shared account
Tags, pins and $answered/$forwarded/$mdnsent set on a message in a
shared/group mailbox opened directly from the "Shared" sidebar section
did not persist: they showed until the next reload and were then gone.

The call sites resolved the target account as
`isUnifiedView ? email.sourceAccountId : undefined`. Outside the unified
view that is always `undefined`, so the write went to the reaching
client's own account, which the server answers with `notUpdated` /
`updated: null` and no error - the same silent no-op that
`email-store-shared-folder-actions.test.ts` already covers for the batch
actions. Emails fetched from a shared folder this way are undecorated
(no `sourceAccountId`); the owner is only recoverable from the selected
mailbox.

`toggleStar` never had the bug because it routes through
`resolveEmailActionContext`, which resolves the owner from the email's
source in an aggregate view and from the selected mailbox otherwise.
That resolver was reimplemented inline at each keyword call site, and
the inline copies only ever covered the aggregate half - so starring a
shared message persisted while tagging the same message did not.

Move the two keyword writes into store actions built on that resolver
and route every call site through them, including the read-receipt and
Pro-compose paths that previously passed no accountId at all.

The store actions resolve only for an email the store actually knows.
A Pro tab fetches its own email and never enters `emails` or
`selectedEmail`; for those the selected mailbox says nothing about the
message's owner, so guessing from it would send an own-account write to
a shared account whenever a shared folder happened to be open. An
unknown email keeps the previous behaviour instead.

Behaviour on own-account and unified-view messages is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 10:42:06 +02:00
Lucas Gaitzsch 17f18e378e fix: polish scheduled send follow-ups 2026-08-12 17:46:51 +02:00
KazNIISA IT c516ba98d0 fix(email-store): route shared-folder management to the owner account
Folder management performed on a shared/group account was sent to the
logged-in user's primary JMAP account instead of the mailbox owner's
account. Subfolder creation therefore failed with invalidProperties for
parentId, root folders could be created silently in the personal account,
and rename, delete, role, and reorder targeted the wrong mailbox tree.

Shared mailboxes use namespaced store ids to avoid cross-account
collisions, while Mailbox/set requires the owner's accountId and the bare
server mailbox id. Resolve that mutation context centrally: recover the
mailbox originalId and owner account, use a directly connected owner
client when one exists, otherwise reach delegated/group accounts through
the active client with an explicit accountId.

Thread the account target through create, update, and delete JMAP methods
and all folder-management actions. Scope role replacement to the same
owner account, and expose explicit root-folder creation from shared
account headers without making personal root creation depend on the
currently selected folder.

Add unit coverage for JMAP request scoping, shared store routing, role
isolation, direct-client handling, and reorder. Add a Stalwart integration
scenario that creates root and nested group folders, renames them, and
deletes them through the UI.
2026-08-12 18:11:43 +05:00
Paulhenry Saux 604316c663 feat(plugins): onBeforeComposeOpenToReply, etc to allow plugins to edit emails before they are used to populate fields of composer 2026-08-09 14:20:26 +02:00
Linus Rath 706c6c23fd fix: send via the identity's own account client so DKIM matches the From domain #461 2026-08-06 18:55:08 +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