Description
When the server does not advertise MOVE (RFC 6851), MailBox.move falls back to COPY + +FLAGS \Deleted + EXPUNGE. The unqualified EXPUNGE removes every message in the mailbox currently flagged \Deleted, including messages the caller marked for deletion earlier and was not yet ready to commit. This is irreversible data loss.
Location
email_profile/clients/imap/mailbox.py line ~190
Current Behavior
def move(self, target: UIDLike, destination: str) -> None:
Status.state(self._client.select(_quote(self.name)))
uid = _uid_of(target)
try:
Status.state(self._client.uid("MOVE", uid, destination))
except Exception:
Status.state(self._client.uid("COPY", uid, destination))
Status.state(self._client.uid("STORE", uid, "+FLAGS", "\\Deleted"))
Status.state(self._client.expunge())
self._client.expunge() is an unqualified EXPUNGE — it removes ALL \Deleted messages in the mailbox, not just uid.
Expected Behavior
Only the moved UID should be permanently removed. Other messages already flagged \Deleted should remain until the user calls expunge() explicitly.
Suggested Fix
Use UID EXPUNGE (RFC 4315 / UIDPLUS) which targets a specific UID set:
except Exception:
Status.state(self._client.uid("COPY", uid, destination))
Status.state(self._client.uid("STORE", uid, "+FLAGS", "\\Deleted"))
try:
Status.state(self._client.uid("EXPUNGE", uid))
except Exception:
# server lacks UIDPLUS — leave the \Deleted flag in place and
# raise so the caller can decide whether to expunge globally.
raise IMAPError(
"Server lacks MOVE and UIDPLUS; message was copied and "
"flagged \\Deleted but not expunged."
)
Document the behavior change in the README.
Impact
Any code path that uses move() on a server without MOVE extension may silently delete unrelated messages that the user previously flagged for deletion. Once expunged, recovery is impossible.
Priority
High
Description
When the server does not advertise
MOVE(RFC 6851),MailBox.movefalls back toCOPY++FLAGS \Deleted+EXPUNGE. The unqualifiedEXPUNGEremoves every message in the mailbox currently flagged\Deleted, including messages the caller marked for deletion earlier and was not yet ready to commit. This is irreversible data loss.Location
email_profile/clients/imap/mailbox.pyline ~190Current Behavior
self._client.expunge()is an unqualifiedEXPUNGE— it removes ALL\Deletedmessages in the mailbox, not justuid.Expected Behavior
Only the moved UID should be permanently removed. Other messages already flagged
\Deletedshould remain until the user callsexpunge()explicitly.Suggested Fix
Use
UID EXPUNGE(RFC 4315 / UIDPLUS) which targets a specific UID set:Document the behavior change in the README.
Impact
Any code path that uses
move()on a server withoutMOVEextension may silently delete unrelated messages that the user previously flagged for deletion. Once expunged, recovery is impossible.Priority
High