fix(email): defer source removal on cross-account move to Stalwart

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).
This commit is contained in:
Stefan Hildebrandt
2026-07-24 18:30:50 +02:00
parent 6248bb9825
commit b48b6e0871
2 changed files with 72 additions and 72 deletions
+5 -13
View File
@@ -6407,11 +6407,16 @@ export class JMAPClient implements IJMAPClient {
]);
const keywords = srcResp.methodResponses?.[0]?.[1]?.list?.[0]?.keywords ?? {};
// onSuccessDestroyOriginal is the spec-correct way to remove the source, but
// Stalwart currently destroys the copy's create-id instead of the source id,
// so the original is left behind — a duplicate on every cross-account move.
// Reported upstream (support.stalw.art #1150); this self-heals once fixed.
const response = await this.request([
["Email/copy", {
fromAccountId,
accountId: toAccountId,
create: { c: { id: emailId, mailboxIds: { [destMailboxId]: true }, keywords } },
onSuccessDestroyOriginal: true,
}, "0"],
]);
const res = response.methodResponses?.[0]?.[1];
@@ -6423,19 +6428,6 @@ export class JMAPClient implements IJMAPClient {
if (!id) {
throw new Error("Email/copy succeeded but no ID returned");
}
// onSuccessDestroyOriginal is unreliable on Stalwart (implicit destroy reports
// notFound and leaves the original behind), so remove the source explicitly.
const delResp = await this.request([
["Email/set", { accountId: fromAccountId, destroy: [emailId] }, "0"],
]);
const notDestroyed = delResp.methodResponses?.[0]?.[1]?.notDestroyed?.[emailId];
if (notDestroyed) {
throw new Error(
notDestroyed.description || notDestroyed.type ||
"Copied email but failed to remove the original from the source folder",
);
}
return id;
}