Fix/cross account move#680
Merged
rathlinus merged 3 commits intoJul 24, 2026
Merged
Conversation
hildebrandttk
force-pushed
the
fix/cross-account-move
branch
from
July 23, 2026 17:47
289331c to
800fdd3
Compare
Moving a message to a folder in another account (own ↔ delegated/shared) via the "Move to" context menu was a no-op — the handlers always issued a single-account Email/set, which can't move between JMAP accounts. Drag-and-drop already routed these correctly; the context menu never did. Add moveToMailboxCrossAware: it detects a cross-account destination (own and shared mailboxes both carry accountId) and routes through the drag-and-drop crossAccountMoveEmails pipeline, else falls back to the single-account move. Fix the pipeline for delegated folders too: a client can't stage a blob in a delegated account (Blob/upload → blobNotFound), so importing into a shared folder failed. When one client reaches both accounts, use a server-side JMAP Email/copy (+ destroy original) instead of blob copy+import; the blob path is kept only for separate cross-server login accounts. Adds client.copyEmailAcrossAccounts. Unit tests for the dispatch; the two 08-shared-moves specs are un-pinned. Full docker integration suite green (37 passed).
Moving a message across the account boundary (own ↔ shared folder, or between two owners' shared folders) left the original in the source folder and showed the moved copy as unread. Same-account moves were fine. Cause (verified against a live Stalwart): - Email/copy drops keywords unless the create sets them, so the copy lost $seen and arrived unread. - onSuccessDestroyOriginal is unreliable — the implicit destroy reports notFound and leaves the original behind (flaky), so the move duplicated. Fix (copyEmailAcrossAccounts): read the source keywords and carry them into the Email/copy create, then destroy the original with an explicit Email/set on the source account instead of onSuccessDestroyOriginal. Tests: 08-shared-moves now asserts the source is gone and the read state survives on every cross-account case, and adds a cross-owner shared → shared move (alice's folder → bob's folder). Confirmed red on the old code (3 cross-account cases fail), green with the fix.
The explicit Email/set destroy workaround for the duplicate-on-move bug is removed now that the root cause is filed upstream (support.stalw.art #1150: onSuccessDestroyOriginal destroys the copy's create-id instead of the source id). copyEmailAcrossAccounts keeps requesting onSuccessDestroyOriginal, so the move self-heals once Stalwart ships the fix. Kept: the keyword-preservation fix (carry the source keywords into Email/copy) so the moved message keeps its read state. Tests: 08-shared-moves still asserts delivery + read-state on every cross-account case; the source-removal checks are re-pinned test.fail, scoped to a nested describe, until #1150 is fixed. Suite green (5 pass, 3 expected-fail).
hildebrandttk
force-pushed
the
fix/cross-account-move
branch
from
July 24, 2026 16:31
800fdd3 to
b48b6e0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
"Move to" a folder in another account (own ↔ delegated/shared) was a no-op — the
message stayed put. Drag-and-drop already handled this; the context menu didn't.
Changes
moveToMailboxCrossAware(stores/email-store.ts): detect a cross-accountdestination (own and shared mailboxes both carry accountId) and route through
the drag-and-drop
crossAccountMoveEmailspipeline; else single-account move.server-side JMAP Email/copy (
copyEmailAcrossAccounts) instead of blobcopy+import. The blob path stays only for separate cross-server login accounts.
copyEmailAcrossAccountscarries the source keywords into the copy (Email/copydrops them otherwise → arrived unread) and requests
onSuccessDestroyOriginalto remove the source. Note: Stalwart currently destroys the copy's create-id
instead of the source id, so the original is left behind until that is fixed
upstream (reported: support.stalw.art Support #1150). Our code self-heals once fixed.
Related Issues
Type of Change
Checklist
npm run typecheck && npm run lintand there are no errorsnpm run build)locales/) if my changes affect user-facing textScreenshots / Demo
Notes for Reviewers