fix: call imap_delete_mail BEFORE db.delete/folder_id change — IMAP sync was broken

This commit is contained in:
Agent Zero
2026-07-20 16:38:26 +02:00
parent 3a5520b46a
commit a2d68e1aca
+26 -8
View File
@@ -647,14 +647,17 @@ async def empty_folder(
for mail in mails: for mail in mails:
if is_trash or not trash_folder: if is_trash or not trash_folder:
# Permanent delete from DB + IMAP EXPUNGE # Permanent delete from DB + IMAP EXPUNGE
await db.delete(mail) # IMPORTANT: Call imap_delete_mail BEFORE db.delete, because imap_delete_mail
# reads the mail from DB to find its folder and imap_uid.
try: try:
await mail_services.imap_delete_mail(db, mail.id, tenant_id, permanent=True) await mail_services.imap_delete_mail(db, mail.id, tenant_id, permanent=True)
await db.delete(mail)
except Exception as exc: except Exception as exc:
import logging import logging
logging.getLogger(__name__).warning( logging.getLogger(__name__).warning(
"empty_folder: IMAP permanent delete failed for mail %s: %s, queuing for retry", mail.id, exc "empty_folder: IMAP permanent delete failed for mail %s: %s, queuing for retry", mail.id, exc
) )
await db.delete(mail)
queue_entry = MailSyncQueue( queue_entry = MailSyncQueue(
tenant_id=tenant_id, tenant_id=tenant_id,
mail_id=mail.id, mail_id=mail.id,
@@ -666,14 +669,17 @@ async def empty_folder(
db.add(queue_entry) db.add(queue_entry)
else: else:
# Move to Trash folder (standard mail program logic) # Move to Trash folder (standard mail program logic)
mail.folder_id = trash_folder.id # IMPORTANT: Call imap_delete_mail BEFORE changing folder_id,
# because imap_delete_mail reads mail.folder_id to find the IMAP folder.
try: try:
await mail_services.imap_delete_mail(db, mail.id, tenant_id, permanent=False) await mail_services.imap_delete_mail(db, mail.id, tenant_id, permanent=False)
mail.folder_id = trash_folder.id
except Exception as exc: except Exception as exc:
import logging import logging
logging.getLogger(__name__).warning( logging.getLogger(__name__).warning(
"empty_folder: IMAP move to Trash failed for mail %s: %s, queuing for retry", mail.id, exc "empty_folder: IMAP move to Trash failed for mail %s: %s, queuing for retry", mail.id, exc
) )
mail.folder_id = trash_folder.id
queue_entry = MailSyncQueue( queue_entry = MailSyncQueue(
tenant_id=tenant_id, tenant_id=tenant_id,
mail_id=mail.id, mail_id=mail.id,
@@ -1627,13 +1633,17 @@ async def delete_mail(
if trash_folder and mail.folder_id == trash_folder.id: if trash_folder and mail.folder_id == trash_folder.id:
# Mail is already in Trash → permanent delete from DB + IMAP EXPUNGE # Mail is already in Trash → permanent delete from DB + IMAP EXPUNGE
await db.delete(mail) # IMPORTANT: Call imap_delete_mail BEFORE db.delete, because imap_delete_mail
await db.flush() # reads the mail from DB to find its folder and imap_uid.
try: try:
await mail_services.imap_delete_mail(db, m_id, tenant_id, permanent=True) await mail_services.imap_delete_mail(db, m_id, tenant_id, permanent=True)
await db.delete(mail)
await db.flush()
except Exception as exc: except Exception as exc:
import logging import logging
logging.getLogger(__name__).warning("IMAP permanent delete failed for mail %s: %s, queuing for retry", m_id, exc) logging.getLogger(__name__).warning("IMAP permanent delete failed for mail %s: %s, queuing for retry", m_id, exc)
await db.delete(mail)
await db.flush()
queue_entry = MailSyncQueue( queue_entry = MailSyncQueue(
tenant_id=tenant_id, tenant_id=tenant_id,
mail_id=m_id, mail_id=m_id,
@@ -1646,13 +1656,17 @@ async def delete_mail(
await db.flush() await db.flush()
elif trash_folder: elif trash_folder:
# Mail is NOT in Trash → move to Trash folder (standard mail program logic) # Mail is NOT in Trash → move to Trash folder (standard mail program logic)
mail.folder_id = trash_folder.id # IMPORTANT: Call imap_delete_mail BEFORE changing folder_id,
await db.flush() # because imap_delete_mail reads mail.folder_id to find the IMAP folder.
try: try:
await mail_services.imap_delete_mail(db, m_id, tenant_id, permanent=False) await mail_services.imap_delete_mail(db, m_id, tenant_id, permanent=False)
mail.folder_id = trash_folder.id
await db.flush()
except Exception as exc: except Exception as exc:
import logging import logging
logging.getLogger(__name__).warning("IMAP move to Trash failed for mail %s: %s, queuing for retry", m_id, exc) logging.getLogger(__name__).warning("IMAP move to Trash failed for mail %s: %s, queuing for retry", m_id, exc)
mail.folder_id = trash_folder.id
await db.flush()
queue_entry = MailSyncQueue( queue_entry = MailSyncQueue(
tenant_id=tenant_id, tenant_id=tenant_id,
mail_id=m_id, mail_id=m_id,
@@ -1665,13 +1679,17 @@ async def delete_mail(
await db.flush() await db.flush()
else: else:
# No Trash folder found → permanent delete from DB + IMAP EXPUNGE # No Trash folder found → permanent delete from DB + IMAP EXPUNGE
await db.delete(mail) # IMPORTANT: Call imap_delete_mail BEFORE db.delete, because imap_delete_mail
await db.flush() # reads the mail from DB to find its folder and imap_uid.
try: try:
await mail_services.imap_delete_mail(db, m_id, tenant_id, permanent=True) await mail_services.imap_delete_mail(db, m_id, tenant_id, permanent=True)
await db.delete(mail)
await db.flush()
except Exception as exc: except Exception as exc:
import logging import logging
logging.getLogger(__name__).warning("IMAP delete failed for mail %s: %s, queuing for retry", m_id, exc) logging.getLogger(__name__).warning("IMAP delete failed for mail %s: %s, queuing for retry", m_id, exc)
await db.delete(mail)
await db.flush()
queue_entry = MailSyncQueue( queue_entry = MailSyncQueue(
tenant_id=tenant_id, tenant_id=tenant_id,
mail_id=m_id, mail_id=m_id,