diff --git a/app/plugins/builtins/mail/routes.py b/app/plugins/builtins/mail/routes.py index d069931..59b86a9 100644 --- a/app/plugins/builtins/mail/routes.py +++ b/app/plugins/builtins/mail/routes.py @@ -647,14 +647,17 @@ async def empty_folder( for mail in mails: if is_trash or not trash_folder: # 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: await mail_services.imap_delete_mail(db, mail.id, tenant_id, permanent=True) + await db.delete(mail) except Exception as exc: import logging logging.getLogger(__name__).warning( "empty_folder: IMAP permanent delete failed for mail %s: %s, queuing for retry", mail.id, exc ) + await db.delete(mail) queue_entry = MailSyncQueue( tenant_id=tenant_id, mail_id=mail.id, @@ -666,14 +669,17 @@ async def empty_folder( db.add(queue_entry) else: # 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: await mail_services.imap_delete_mail(db, mail.id, tenant_id, permanent=False) + mail.folder_id = trash_folder.id except Exception as exc: import logging logging.getLogger(__name__).warning( "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( tenant_id=tenant_id, mail_id=mail.id, @@ -1627,13 +1633,17 @@ async def delete_mail( if trash_folder and mail.folder_id == trash_folder.id: # Mail is already in Trash → permanent delete from DB + IMAP EXPUNGE - await db.delete(mail) - await db.flush() + # 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: await mail_services.imap_delete_mail(db, m_id, tenant_id, permanent=True) + await db.delete(mail) + await db.flush() except Exception as exc: import logging 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( tenant_id=tenant_id, mail_id=m_id, @@ -1646,13 +1656,17 @@ async def delete_mail( await db.flush() elif trash_folder: # Mail is NOT in Trash → move to Trash folder (standard mail program logic) - mail.folder_id = trash_folder.id - await db.flush() + # IMPORTANT: Call imap_delete_mail BEFORE changing folder_id, + # because imap_delete_mail reads mail.folder_id to find the IMAP folder. try: 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: import logging 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( tenant_id=tenant_id, mail_id=m_id, @@ -1665,13 +1679,17 @@ async def delete_mail( await db.flush() else: # No Trash folder found → permanent delete from DB + IMAP EXPUNGE - await db.delete(mail) - await db.flush() + # 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: await mail_services.imap_delete_mail(db, m_id, tenant_id, permanent=True) + await db.delete(mail) + await db.flush() except Exception as exc: import logging 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( tenant_id=tenant_id, mail_id=m_id,