IMAP ESEARCH returns only the min and max in ALL when MIN or MAX is also requested

Issue Description

When an IMAP SEARCH asks for MIN and/or MAX together with ALL, the ALL part of the ESEARCH response contains only the min and max message numbers instead of every match. COUNT is correct, so the response contradicts itself.

The cause is in map_search_results (crates/imap/src/op/search.rs, around line 734). When MIN or MAX is requested, only the min and max are collected, whether or not ALL was also requested. The saved search result (SAVE, $) is affected the same way.

Stalwart’s own test expects the wrong output: tests/src/imap/search.rs line 32 asserts COUNT 10 MIN 1 MAX 10 ALL 1,10 for a 10-message mailbox.

Real-world impact: Libredesk (helpdesk software) sends SEARCH RETURN (MIN MAX ALL COUNT) SINCE NOT DELETED and fetches what ALL lists. Since Libredesk commit 080318a, it imports only the oldest and newest message on each poll. Mail in between is delayed by up to two days.

A failing test is on my fork: IMAP: ESEARCH ALL with MIN/MAX returns only min and max - Pull Request #1 - mewejo/stalwart - GitHub (found and tested with AI assistance).

Expected Behavior

ALL lists every matching message, whatever other result options are requested (RFC 4731 §3.1).

With SAVE, $ holds only MIN/MAX when those are the only other options. When ALL or COUNT is also requested, $ holds every match (RFC 5182 §2.4).

For 6 matching messages:

  • ESEARCH (TAG “A1”) COUNT 6 MIN 1 MAX 6 ALL 1:6

Actual Behavior

  • ESEARCH (TAG “A1”) COUNT 6 MIN 1 MAX 6 ALL 1,6

COUNT says 6, but ALL lists only messages 1 and 6. Without MIN/MAX it’s correct:

  • ESEARCH (TAG “A2”) COUNT 6 ALL 1:6

Reproduction Steps

  1. Have a mailbox with 3 or more messages.
  2. openssl s_client -connect :993 -quiet
  3. A0 LOGIN
  4. A0 SELECT INBOX
  5. A1 SEARCH RETURN (MIN MAX COUNT ALL) ALL, which returns ALL with only the first and last message.
  6. A2 SEARCH RETURN (COUNT ALL) ALL, which returns every message.

Relevant Log Output

C: A1 SEARCH RETURN (MIN MAX COUNT ALL) SINCE 11-Oct-2026
S: * ESEARCH (TAG “A1”) COUNT 6 MIN 1 MAX 6 ALL 1,6
C: A2 SEARCH RETURN (ALL COUNT) SINCE 11-Oct-2026
S: * ESEARCH (TAG “A2”) COUNT 6 ALL 1:6
No errors are logged; the server returns a successful but incomplete response.

Stalwart Version

v0.16.x

Installation Method

Docker

Database Backend

PostgreSQL

Blob Storage

S3-compatible

Search Engine

Internal

Directory Backend

Internal

Additional Context

The bug is also on current main)

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

This confirms the report’s analysis. This looks like a genuine bug report about the forum, not a question I need to answer with docs—but I should respond helpfully acknowledging the confirmed bug location.

This is a well-documented bug report with source-level analysis that matches the code. I’ll confirm the finding and let the human follow-up happen, per the no-GitHub-issue-filing rule but acknowledging this is a server source issue for staff to triage.

Your analysis matches the code. In map_search_results (crates/imap/src/op/search.rs), the branch is a strict if/else: when find_min_or_max is true, matching document IDs are only tested against MIN/MAX and never pushed into imap_ids (or the saved-results list) unless they happen to be the chosen min/max. ALL (and SAVE/$) therefore collapses to just the min and max entries whenever MIN or MAX is also requested, while COUNT is tallied correctly via the separate *total += 1 counter outside that branch. This confirms the RFC 4731/5182 discrepancy you described, and the shipped test at tests/src/imap/search.rs:32 indeed encodes the current (incorrect) behavior rather than the RFC-compliant one.

This is a code-level defect rather than something resolvable via configuration, so there’s nothing to change on your end to work around it short of not combining MIN/MAX with ALL in the same SEARCH RETURN request. A staff or community member will need to review your fork’s PR and fix in the main source tree.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

Fixed in v0.16.27 (yet to be released).