From 5ded6f7d0b5156192c206b46a80bd5cc97fdbf9f Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Mon, 20 Jul 2026 12:59:19 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20dedup=20by=20message=5Fid=20in=20imap=5F?= =?UTF-8?q?sync=5Ffolder=20=E2=80=94=20stop=20deleted=20mails=20reappearin?= =?UTF-8?q?g=20from=20Trash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - imap_sync_folder now checks message_id in addition to (folder_id, imap_uid) - If mail was moved to Trash by IMAP server (new UID, same message_id), it is recognized - Soft-deleted mails with matching message_id are skipped, not re-created - Prevents deleted mails from reappearing when IMAP server moves them to Trash --- app/plugins/builtins/mail/services.py | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/app/plugins/builtins/mail/services.py b/app/plugins/builtins/mail/services.py index 06ae2ac..3a9098c 100644 --- a/app/plugins/builtins/mail/services.py +++ b/app/plugins/builtins/mail/services.py @@ -635,10 +635,41 @@ async def imap_sync_folder( ) ).scalar_one_or_none() + # Also check by message_id — mail may have been moved to Trash by IMAP server + if not existing_mail: + # Fetch headers first to get Message-ID + fetch_hdr = await client.uid('fetch', uid_str, '(BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)])') + hdr_raw = None + for line in (fetch_hdr.lines if hasattr(fetch_hdr, 'lines') else fetch_hdr): + if isinstance(line, bytearray): + hdr_raw = bytes(line) + break + if hdr_raw: + hdr_msg = message_from_bytes(hdr_raw) + peek_msg_id = hdr_msg.get("Message-ID", "") + if peek_msg_id: + existing_mail = ( + await db.execute( + select(Mail).where( + and_( + Mail.account_id == account.id, + Mail.message_id == peek_msg_id, + Mail.tenant_id == tenant_id, + ) + ) + ) + ).scalar_one_or_none() + if existing_mail: # Don't touch soft-deleted mails — they're being deleted via sync queue if existing_mail.deleted_at is not None: continue + # Update folder_id and imap_uid if mail moved to a different folder + if existing_mail.folder_id != folder.id: + existing_mail.folder_id = folder.id + existing_mail.imap_uid = uid_str + elif not existing_mail.imap_uid: + existing_mail.imap_uid = uid_str continue # Fetch and parse the email