Skip to content

feat(mail list): server-side unread/flagged filtering, envelope fields, and field selection - #16

Merged
bscott merged 1 commit into
bscott:masterfrom
kochj23:feat/mail-list-server-filter-fields-9
Aug 8, 2026
Merged

feat(mail list): server-side unread/flagged filtering, envelope fields, and field selection#16
bscott merged 1 commit into
bscott:masterfrom
kochj23:feat/mail-list-server-filter-fields-9

Conversation

@kochj23

@kochj23 kochj23 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Addresses the mail list limitations from the issue, in internal/imap
(client.go, types.go) and internal/cli (mail.go, cli.go):

  • Server-side filtering. --unread now issues IMAP SEARCH UNSEEN and a
    new --flagged issues SEARCH FLAGGED. Previously --unread fetched N
    messages and filtered client-side, so you got back fewer than the limit; the
    count now respects --limit.
  • Missing envelope fields. MessageSummary gains from_address, to,
    message_id, and in_reply_to. All four come straight from the ENVELOPE
    already fetched, so there is zero extra fetch cost. from_address lets you
    match on sender/domain without a per-message mail read; in_reply_to lets
    you reconstruct reply chains from list output.
  • Field selection. --fields uid,from_address,subject projects JSON output
    to the requested keys, and --compact emits a bare JSON array instead of the
    wrapper object. Both affect --json mode only; text output is unchanged.

ListMessages was refactored to take a ListOptions struct instead of
positional parameters (as suggested in the issue), and all call sites (drafts,
watch) were updated. README documents the new flags, fields, and JSON shape.

Tests

Added internal/imap/list_options_test.go (real in-memory IMAP server, no new
dependency) and internal/cli/mail_list_fields_test.go, covering all seven
categories:

  • Security – empty-mailbox filter returns cleanly (no error/panic);
    --fields rejects unknown/typo'd field names instead of silently ignoring
    them.
  • Performance – server-side unread listing is bounded by the limit;
    projection scales linearly over 5k messages.
  • Retry – repeated SEARCH+FETCH queries and repeated projections are stable
    and deterministic.
  • UnitpaginateSeqNums pagination math; splitFieldList; projection
    keeps only requested keys.
  • Integration – with the newest messages already read, SEARCH UNSEEN
    still returns the unread ones (the exact client-side-truncation bug); flagged
    filtering; projected output round-trips through JSON.
  • Functional – envelope fields (from_address/to/message_id/
    in_reply_to) populated from the ENVELOPE; a requested-but-empty field is
    emitted as null for a stable shape.
  • Frame – N/A (no new wire framing; envelope/search decoding and JSON
    encoding covered by the tests above); explicit skip placeholders retained.

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

Closes #9

🤖 Generated with Claude Code

…s, field selection

Reworks `mail list` per the issue:

- ListMessages now takes a ListOptions struct instead of positional args,
  making it cleaner to extend.
- --unread uses IMAP SEARCH UNSEEN and --flagged adds SEARCH FLAGGED, both
  server-side, so results respect the limit instead of fetching N and
  filtering client-side (which returned fewer than requested).
- MessageSummary gains from_address, to, message_id, and in_reply_to — all
  already present in the ENVELOPE, so there is no extra fetch cost. in_reply_to
  makes it possible to reconstruct reply chains from list output.
- --fields projects JSON output to a chosen set of keys and --compact emits a
  bare JSON array; both affect JSON mode only, text output is unchanged.

README documents the new flags, fields, and JSON shape.

Closes bscott#9

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bscott

bscott commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Reviewed and verified locally: gofmt/go vet/go test clean. This is the strongest of the three — the extraction of listSeqSet/paginateSeqNums/newMessageSummary is a real improvement over the inline version, and moving --unread to a server-side SEARCH fixes the actual bug in #9 (client-side filtering could return far fewer than --limit). Pulling the extra envelope fields costs no additional fetch, which is the right observation.

Merging. Two follow-ups I'm pushing right after:

  1. internal/cli/help.go wasn't updated. Per CLAUDE.md, new flags need to land in the --help-json schema — that output is the documented contract for agent integration, so --flagged, --fields, and --compact were invisible to it. I've added them (plus the pre-existing --offset/--page, which were also missing).
  2. Test signature collision with fix(imap): error instead of silent success when STORE/COPY/MOVE target UIDs aren't present #15. Both PRs merge cleanly in git but conflict semantically: fix(imap): error instead of silent success when STORE/COPY/MOVE target UIDs aren't present #15's client_affected_test.go calls the old ListMessages(mailbox, limit, offset, unreadOnly). Updated those three call sites to ListOptions.

Also noting for later, not blocking: Search() still builds MessageSummary inline and doesn't populate FromAddress/To/MessageID/InReplyTo, so mail list and mail search now return different shapes. Worth routing Search() through newMessageSummary too.

Closes #9.

@bscott
bscott merged commit 8685f3c into bscott:master Aug 8, 2026
bscott added a commit that referenced this pull request Aug 8, 2026
#15 and #16 merge cleanly in git but collide semantically: #15's
client_affected_test.go calls the pre-#16 ListMessages signature, so
master did not build. Ported those three call sites to ListOptions.

Also guards the no-match detection added in #15. CopyCommand.Wait()
returns &cmd.data unconditionally in go-imap v2, so the `copyData != nil`
half of the check was dead code and the result rested entirely on
COPYUID. COPYUID is only sent by servers advertising UIDPLUS (folded into
IMAP4rev2); without it an empty SourceUIDs/DestUIDs pair means the server
never reported, not that nothing was copied — which would have failed
every successful copy/label/move. Extracted copyMatchedNothing(), which
draws that conclusion only when the capability guarantees the data.

#15's fixture pins Caps to IMAP4rev2 and so could not cover this; added a
plain IMAP4rev1 fixture. On such a server COPY no-match detection is
necessarily weaker, but MoveMessages still catches it via the STORE
affected-count — pinned by TestMoveMissingUIDWithoutUIDPlusStillErrors.

Finally, adds #16's new mail list flags (--flagged, --fields, --compact)
to the --help-json schema per CLAUDE.md, along with --offset and --page,
which were already missing. That output is the documented agent contract.

Co-authored-by: exe.dev user <exedev@discovery-lotus.exe.xyz>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
bscott added a commit that referenced this pull request Aug 8, 2026
Carries forward the unique portion of #13. The env-var and mail-list
work in that PR landed independently via #14 and #16, so only the batch
command remains; this drops the duplicated implementations rather than
conflicting with what shipped.

mail batch runs multiple label/unlabel/archive/move/flag/delete
operations over a single IMAP session, avoiding a connect and auth
round-trip per operation. All operations are validated up front, so
malformed input fails before any connection is opened or any mailbox is
mutated. Names containing IMAP special characters or CRLF are rejected,
and input is capped at 10MB.

Also fixes two defects from #13:
  - The --help-json examples used \" inside raw string literals, which
    emitted literal backslashes into the agent-facing schema.
  - docs/commands.md listed `seq` as a --fields name; the implementation
    that shipped in #16 uses `seq_num`.

Adds a CHANGELOG covering everything in 0.2.6 and bumps the version.

Closes #10.

Co-authored-by: exe.dev user <exedev@discovery-lotus.exe.xyz>
Co-authored-by: Claude Opus 5 (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.

feat: server-side filtering, field selection, and additional envelope fields for mail list

2 participants