fix: bidirectional sync — check sync queue before restoring deleted mails
- If mail has deleted_at and is still on IMAP server: - Check sync queue for pending delete operation - If pending delete exists: our IMAP delete failed, keep deleted, retry via queue - If no pending delete: another program restored the mail, restore in DB - This enables true bidirectional sync like Thunderbird/Outlook
This commit is contained in:
@@ -665,12 +665,33 @@ async def imap_sync_folder(
|
||||
).scalar_one_or_none()
|
||||
|
||||
if existing_mail:
|
||||
# If mail has deleted_at, it was deleted in LeoCRM.
|
||||
# DO NOT restore it — keep it deleted and let the sync queue
|
||||
# retry the IMAP delete/move. This prevents deleted mails from
|
||||
# reappearing in the inbox.
|
||||
# Bidirectional sync logic:
|
||||
# - Mail has deleted_at AND is still on IMAP server
|
||||
# - If pending delete in sync queue → our IMAP delete failed, DON'T restore
|
||||
# - If no pending delete → another program restored it, DO restore
|
||||
# - Mail has no deleted_at → normal update
|
||||
if existing_mail.deleted_at is not None:
|
||||
continue
|
||||
# Check if there's a pending delete in the sync queue
|
||||
from app.plugins.builtins.mail.models import MailSyncQueue
|
||||
pending_delete = (
|
||||
await db.execute(
|
||||
select(MailSyncQueue).where(
|
||||
and_(
|
||||
MailSyncQueue.mail_id == existing_mail.id,
|
||||
MailSyncQueue.operation == "delete",
|
||||
MailSyncQueue.status == "pending",
|
||||
)
|
||||
)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if pending_delete:
|
||||
# Our IMAP delete failed and is retrying — don't restore
|
||||
logger.info("imap_sync_folder: skipping mail %s (deleted, pending IMAP delete in queue)", existing_mail.id)
|
||||
continue
|
||||
else:
|
||||
# No pending delete → another program restored the mail → restore in DB
|
||||
existing_mail.deleted_at = None
|
||||
logger.info("imap_sync_folder: restored mail %s (no pending delete, another program restored it)", existing_mail.id)
|
||||
# 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
|
||||
@@ -1164,11 +1185,30 @@ async def imap_sync_account(
|
||||
).scalar_one_or_none()
|
||||
|
||||
if existing_mail:
|
||||
# If mail has deleted_at, it was deleted in LeoCRM.
|
||||
# DO NOT restore it — keep it deleted and let the sync queue
|
||||
# retry the IMAP delete/move.
|
||||
# Bidirectional sync logic:
|
||||
# - Mail has deleted_at AND is still on IMAP server
|
||||
# - If pending delete in sync queue → our IMAP delete failed, DON'T restore
|
||||
# - If no pending delete → another program restored it, DO restore
|
||||
# - Mail has no deleted_at → normal update
|
||||
if existing_mail.deleted_at is not None:
|
||||
continue
|
||||
from app.plugins.builtins.mail.models import MailSyncQueue
|
||||
pending_delete = (
|
||||
await db.execute(
|
||||
select(MailSyncQueue).where(
|
||||
and_(
|
||||
MailSyncQueue.mail_id == existing_mail.id,
|
||||
MailSyncQueue.operation == "delete",
|
||||
MailSyncQueue.status == "pending",
|
||||
)
|
||||
)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if pending_delete:
|
||||
logger.info("imap_sync_account: skipping mail %s (deleted, pending IMAP delete in queue)", existing_mail.id)
|
||||
continue
|
||||
else:
|
||||
existing_mail.deleted_at = None
|
||||
logger.info("imap_sync_account: restored mail %s (no pending delete, another program restored it)", existing_mail.id)
|
||||
# Track folder moves: update folder_id and imap_uid if the
|
||||
# mail now appears in a different IMAP folder.
|
||||
if existing_mail.folder_id != folder.id:
|
||||
|
||||
Reference in New Issue
Block a user