Skip to content

fix(imap): error instead of silent success when STORE/COPY/MOVE target UIDs aren't present#15

Open
kochj23 wants to merge 1 commit into
bscott:masterfrom
kochj23:fix/silent-success-store-copy-move-11
Open

fix(imap): error instead of silent success when STORE/COPY/MOVE target UIDs aren't present#15
kochj23 wants to merge 1 commit into
bscott:masterfrom
kochj23:fix/silent-success-store-copy-move-11

Conversation

@kochj23

@kochj23 kochj23 commented Jul 1, 2026

Copy link
Copy Markdown

Summary

DeleteMessages, CopyMessages, and MoveMessages in
internal/imap/client.go reported success even when the target UIDs were not
present in the selected mailbox. Removing a label an email didn't have (a COPY
to the wrong Labels/* folder, or a STORE on an absent UID) printed e.g.
Label removed from 1 message(s) while nothing actually changed.

Root cause, as described in the issue:

  • STORE against absent UIDs is a valid no-op per RFC 3501 and returns no
    error; the streamed FETCH responses (one per modified message) were never
    counted.
  • COPY of a UID set that matches nothing is accepted without error by many
    servers (Proton Bridge included); the CopyData (SourceUIDs/DestUIDs)
    was discarded.
  • MoveMessages reimplements COPY + STORE inline and ignored both.

Fix

  • DeleteMessages: drain storeCmd.Next() and error when the affected count
    is zero.
  • CopyMessages: error when CopyData.SourceUIDs and DestUIDs are both
    empty.
  • MoveMessages: apply both checks before expunging the source.

No extra IMAP round-trips — the affected count and COPYUID data are already in
the response streams. SetFlagsMultiple is intentionally left unchanged (per
the issue): a server returns zero affected items both for an absent UID and for
a flag that is already set, so distinguishing them needs a different approach.

Tests

Added internal/imap/client_affected_test.go, which drives a real in-memory
IMAP server (go-imap's imapmemserver, no new module dependency) end-to-end,
organized into all seven categories:

  • Security – a failed (no-match) delete must not partially mutate the
    mailbox.
  • Performance – present-UID delete completes promptly, documenting that the
    affected count adds no extra/blocking round-trip.
  • Retry – a no-match delete keeps failing deterministically, never leaking
    a false success.
  • Unit – missing UID → error; present UID → success (STORE affected count).
  • Integration – MOVE actually relocates the message from INBOX to Archive.
  • Functional – COPY and MOVE on a missing UID surface an error rather than
    silent success (the memserver rejects an empty COPY at the protocol level;
    Proton Bridge returns an empty COPYUID set — both paths are handled).
  • Frame – N/A (no new wire framing; response parsing covered by the
    integration path); explicit skip placeholder retained.

go build ./... && go vet ./... && go test ./... all green.

Closes #11

🤖 Generated with Claude Code

… nothing

DeleteMessages, CopyMessages, and MoveMessages reported success even when the
target UIDs were not present in the selected mailbox. A STORE against absent
UIDs is a valid no-op per RFC 3501 (no error), and many servers (Proton Bridge
included) accept a COPY of a non-matching UID set without error. The code
discarded both responses, so e.g. removing a label the message didn't have
printed "Label removed from 1 message(s)" while changing nothing.

- DeleteMessages: drain the FETCH responses the server streams from STORE and
  error when zero messages were modified.
- CopyMessages: inspect the COPYUID data and error when both SourceUIDs and
  DestUIDs are empty.
- MoveMessages: apply both checks to its inline COPY + STORE.

No extra IMAP round-trips: the affected count and COPYUID data are already in
the response streams. SetFlagsMultiple is intentionally left unchanged — the
server returns zero affected items both for absent UIDs and for a flag that is
already set, so that case needs a different approach.

Closes bscott#11

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: silent success when STORE/COPY targets UIDs not present in selected mailbox

1 participant