From 51b8fd5708d23108d532be1cf38e4bb619eeb777 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sun, 6 Sep 2026 15:26:31 +0200 Subject: fix: keep one Message-ID across a draft's revisions Every autosave called MessageBuilder::build(), which generated a fresh Message-ID unconditionally, so each revision of a draft was a different message rather than a new version of one. The entry called this invisible while the file is replaced correctly, and that turned out to be wrong: mbsync uploads each revision to the drafts folder before the next save removes the local file, so the server keeps one message per revision and syncs them all back down. Measured on real mail as four independent messages for a single reply, all four carrying a ,U= infix, threading into the conversation and putting a draft tag on a Sent row. Deleting a local file does not retract an uploaded one, which is why the local cleanup, which is correct, could never fix it. The user chose a stable id while drafting, discarded at send: the sent copy is a different item from the draft, and the draft is deleted once the message goes out, which the code already did. Three links, none of which existed. OutgoingMessage::messageId is the field, where empty means generate, so the send path is unchanged by construction rather than by remembering to clear it. MessageBuilder::build() uses a supplied id when there is one. ComposeWindow::m_draftMessageId holds the identity between revisions, assigned from built.messageId so the first save adopts the id GMime just generated, and ComposeContext::draftMessageId carries it across a reopen, read in forDraft() from ParsedMessage::messageId, which the parser already provided and nothing had ever used. Five tests, because the property spans three objects and a test at any one of them passes while another link is broken. Two are the safety constraint rather than the feature: a field defaulting to a fixed value would satisfy the reuse test and make two sent messages share an id, which is far worse than the defect this fixes. One comment is corrected rather than left: the autosave's dirty check justified comparing the message rather than the built bytes with "GMime is given a fresh Date and Message-ID on every build". Half of that is no longer true. The Date still is, so the conclusion stands. Revisions already on the server are not touched by this; the four found on real mail were deleted by hand. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Jq9gXquUo9W4KXDagJXMmn --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'CHANGELOG.md') diff --git a/CHANGELOG.md b/CHANGELOG.md index 2749e0b..f024916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,16 @@ point at which they are stable. ### Fixed +- **A draft no longer becomes a new message every time it is saved.** Each + autosave built the draft under a fresh Message-ID, and because mbsync + uploads each revision to the drafts folder before the next save removes the + local file, the server ended up holding one message per revision. Four + revisions of a single reply were found that way on real mail, all four + syncing back down and threading into the conversation. A draft now keeps one + identity across its revisions, including across being closed and reopened, + so a save replaces the message rather than adding one. The sent copy still + gets an id of its own: it is a different item from the draft, and the draft + is deleted once the message goes out. - **The Sent and Drafts views show every message you sent in a conversation, not just the first.** Both views are lists of your own messages rather than of conversations, but a thread you had replied to twice produced a single -- cgit v1.2.3