fix: remove all deleted_at/soft-delete from mails — standard mail program logic only

This commit is contained in:
Agent Zero
2026-07-20 16:08:05 +02:00
parent fb92acb52b
commit 3a5520b46a
4 changed files with 71 additions and 91 deletions
+11 -37
View File
@@ -588,7 +588,7 @@ async def delete_folder(
await db.delete(folder)
# ─── Empty Folder (soft-delete all mails in folder) ───
# ─── Empty Folder (move to Trash or permanent delete) ───
@router.post("/folders/{folder_id}/empty")
@@ -599,7 +599,7 @@ async def empty_folder(
):
"""Empty a folder: move all mails to Trash, or permanent delete if already in Trash.
If the folder IS the Trash folder, mails are permanently deleted (deleted_at + IMAP EXPUNGE).
If the folder IS the Trash folder, mails are permanently deleted (DELETE + IMAP EXPUNGE).
If the folder is NOT Trash, mails are moved to Trash (folder_id changed + IMAP MOVE).
Returns the number of mails that were emptied.
"""
@@ -634,22 +634,20 @@ async def empty_folder(
trash_folder = f
break
# Get all non-deleted mails in this folder
# Get all mails in this folder
result = await db.execute(
select(Mail).where(
and_(
Mail.folder_id == f_id,
Mail.tenant_id == tenant_id,
Mail.deleted_at.is_(None),
)
)
)
mails = result.scalars().all()
now = datetime.now(UTC)
for mail in mails:
if is_trash or not trash_folder:
# Permanent delete
mail.deleted_at = now
# Permanent delete from DB + IMAP EXPUNGE
await db.delete(mail)
try:
await mail_services.imap_delete_mail(db, mail.id, tenant_id, permanent=True)
except Exception as exc:
@@ -667,9 +665,8 @@ async def empty_folder(
)
db.add(queue_entry)
else:
# Move to Trash folder (keep visible in Trash)
# Move to Trash folder (standard mail program logic)
mail.folder_id = trash_folder.id
mail.deleted_at = now
try:
await mail_services.imap_delete_mail(db, mail.id, tenant_id, permanent=False)
except Exception as exc:
@@ -1629,8 +1626,8 @@ async def delete_mail(
break
if trash_folder and mail.folder_id == trash_folder.id:
# Mail is already in Trash → permanent delete
mail.deleted_at = datetime.now(UTC)
# Mail is already in Trash → permanent delete from DB + IMAP EXPUNGE
await db.delete(mail)
await db.flush()
try:
await mail_services.imap_delete_mail(db, m_id, tenant_id, permanent=True)
@@ -1648,12 +1645,8 @@ async def delete_mail(
db.add(queue_entry)
await db.flush()
elif trash_folder:
# Mail is NOT in Trash → move to Trash folder
# Set deleted_at so the mail disappears from the source folder
# and the sync doesn't move it back. list_mails shows deleted_at
# mails when viewing the Trash folder.
# Mail is NOT in Trash → move to Trash folder (standard mail program logic)
mail.folder_id = trash_folder.id
mail.deleted_at = datetime.now(UTC)
await db.flush()
try:
await mail_services.imap_delete_mail(db, m_id, tenant_id, permanent=False)
@@ -1671,8 +1664,8 @@ async def delete_mail(
db.add(queue_entry)
await db.flush()
else:
# No Trash folder found → permanent delete
mail.deleted_at = datetime.now(UTC)
# No Trash folder found → permanent delete from DB + IMAP EXPUNGE
await db.delete(mail)
await db.flush()
try:
await mail_services.imap_delete_mail(db, m_id, tenant_id, permanent=True)
@@ -1762,25 +1755,6 @@ async def list_mails(
if folder_id:
f_id = _parse_uuid(folder_id, "folder_id")
stmt = stmt.where(Mail.folder_id == f_id)
# Check if this folder is a Trash folder — if so, show soft-deleted mails too
folder = (
await db.execute(
select(MailFolder).where(
and_(MailFolder.id == f_id, MailFolder.tenant_id == tenant_id)
)
)
).scalar_one_or_none()
is_trash = folder and (
'trash' in (folder.imap_name or '').lower()
or 'papierkorb' in (folder.name or '').lower()
or 'trash' in (folder.name or '').lower()
)
if not is_trash:
# Exclude soft-deleted mails in non-Trash folders
stmt = stmt.where(Mail.deleted_at.is_(None))
else:
# No folder specified — exclude soft-deleted mails
stmt = stmt.where(Mail.deleted_at.is_(None))
if account_id:
a_id = _parse_uuid(account_id, "account_id")
stmt = stmt.where(Mail.account_id == a_id)