Apple Mail iOS renders empty inbox despite IMAP SELECT INBOX returning EXISTS; Outlook desktop works on same account

Issue Description

Apple Mail iOS renders empty inbox despite IMAP SELECT INBOX returning * N EXISTS; Outlook desktop works on same account

Summary

Apple Mail on iOS (and macOS Mail) connects, authenticates, and renders the inbox as empty (“nothing left to read, enjoy your empty inbox”) for accounts on Stalwart v0.16.4. The server confirms ~90 messages in INBOX via raw IMAP SELECT; UID FETCH returns real messages with valid headers. Outlook desktop on the same account works correctly.

Reproduced across:

  • Two end users on different home networks, two distinct Apple-Mail-on-iPhone setups
  • Both iPhone-only and iPhone + macOS (macOS Mail also affected for one user)
  • Two different accounts on the same server, after the standard Mozilla-style autoconfig setup flow

Outlook desktop, Thunderbird, and direct IMAP CLI all see the 90 messages correctly.

Environment

  • Server: Stalwart v0.16.4, Ubuntu 24.04 aarch64
  • Listeners: imaps on [::]:993 (TLS implicit), Let’s Encrypt cert chain
  • Client: Apple Mail iOS (iOS 18.x), Apple Mail macOS (current)
  • Setup path: Mozilla-style autoconfig served at /mail/config-v1.1.xml
  • Capability advertisement (post-AUTH):
    IMAP4rev2 IMAP4rev1 ENABLE SASL-IR LITERAL+ ID UTF8=ACCEPT JMAPACCESS
    IDLE NAMESPACE CHILDREN MULTIAPPEND BINARY UNSELECT ACL UIDPLUS ESEARCH
    WITHIN SEARCHRES SORT THREAD=REFERENCES LIST-EXTENDED LIST-STATUS ESORT
    SORT=DISPLAY SPECIAL-USE CREATE-SPECIAL-USE MOVE CONDSTORE QRESYNC
    UNAUTHENTICATE STATUS=SIZE OBJECTID PREVIEW RIGHTS=texk QUOTA QUOTA=RES-STORAGE
    

Server returns the data correctly

Direct IMAP from localhost via openssl s_client -connect 127.0.0.1:993:

* OK [CAPABILITY ...] Stalwart IMAP4rev2 at your service.
a01 LOGIN [email protected] <password>
a01 OK Authentication successful

a02 LIST "" "*"
* LIST (\Trash) "/" "Deleted Items"
* LIST (\Drafts) "/" "Drafts"
* LIST () "/" "INBOX"
* LIST (\Junk) "/" "Junk Mail"
* LIST (\Sent) "/" "Sent Items"
a02 OK LIST completed

a03 SELECT INBOX
* 90 EXISTS
* 0 RECENT
* OK [UIDVALIDITY 4078036059] UIDs valid
* OK [UIDNEXT 103] Next predicted UID
* OK [MAILBOXID (qaaaaaa)] Unique Mailbox ID
a03 OK [READ-WRITE] SELECT completed

a04 UID FETCH 1:3 (UID FLAGS BODY[HEADER.FIELDS (SUBJECT FROM DATE)])
* 1 FETCH (UID 1 FLAGS () INTERNALDATE "04-May-2026 16:32:14 +0000" ...)
* 2 FETCH (UID 2 FLAGS () INTERNALDATE "04-May-2026 18:15:32 +0000" ...)
* 3 FETCH (UID 3 FLAGS (\Seen) INTERNALDATE "04-May-2026 18:30:59 +0000" ...)
a04 OK FETCH completed

What we ruled out

  • Folder topology / INBOX/Notes subfolder confusing Apple’s name resolver. Flattened the tree, moved INBOX/Notes to root, deleted a duplicate Sent Messages folder. iOS Mail behavior unchanged.
  • Missing IMAP extensions per Apple’s TN3191. We advertise all of the extensions called out as required/recommended there: SASL-IR, CONDSTORE, ESEARCH, ID, IDLE, LIST-STATUS, LITERAL+, MOVE, SPECIAL-USE, UIDPLUS. Missing only optional optimizations (UIDONLY/RFC 9586, APPENDLIMIT, COMPRESS, PARTIAL) — none should produce empty rendering per TN3191.
  • \HasChildren / \HasNoChildren on INBOX. After folder flattening, INBOX has no children; crates/imap/src/op/list.rs:243-250 correctly emits \HasNoChildren when LIST-EXTENDED with CHILDREN return option is requested.
  • The IDLE-pipelining bug from #765. Commit 51c8f7b2 is in our tree (post-v0.10.0). And per your comment on #747, “Apple clients do not use IDLE” — confirmed by the connection pattern (one fresh connection per poll, ~every 2 minutes).
  • fail2ban / firewall. auth.success events fire ~30 times/hour from the iPhone’s IP for the affected account.
  • Credentials. Same creds work in Outlook desktop, Thunderbird, and direct IMAP CLI on the same iOS device’s network.

What we observed (and where we got stuck)

After each auth.success event from the iPhone’s IP, the server log shows no subsequent IMAP-related events on that connection — no imap.select, imap.fetch, imap.list, imap.logout. Either:

  • (a) iOS Mail authenticates and disconnects without issuing commands (would suggest client-side bail after auth), or
  • (b) the IMAP-command events fire but aren’t captured in our release-build log even at Tracer.level = "trace". Specifically, the trc::event!(Imap(trc::ImapEvent::Fetch), ...) calls at crates/imap/src/op/fetch.rs:237, op/select.rs:109, op/list.rs:296, etc. don’t produce log entries at trace level on a release build — the same is true for my local self-test from inside the box, which performs a full LOGIN -> SELECT INBOX -> UID FETCH -> LOGOUT and produces only one auth.success INFO entry, no Select/Fetch/Logout entries.

If (b), there may be a separate logging-level issue worth fixing — trc::event! for Imap subcommands appears to be filtered out of release builds, which blocks server-side wire-level debugging without rebuild.

Why filing here

The IMAP server is RFC-conformant — every non-Apple client works. The bug is specific to Apple Mail iOS/macOS, and given your IETF 123 contact with the Apple Mail team (per #747) you may already have visibility into a known iOS Mail / IMAP server behavior that produces this symptom against Stalwart specifically. The general shape (“Apple Mail decides INBOX is empty while a parallel client correctly sees N messages”) matches old reports against Dovecot and Cyrus (e.g., Apple Discussions thread 7904861 re: NAMESPACE mismatches in iOS 10.3) but those don’t have a published server-side fix recipe.

Asks

  1. Has anyone reported “Apple Mail iOS shows empty inbox while SELECT INBOX returns valid * N EXISTS” against current Stalwart v0.16.x?
  2. Is the absence of imap.select / imap.fetch / imap.list events in trace-level logs intentional (release-build filtering), or a regression worth fixing separately?
  3. Open to a reproducer PR (tests crate, two-connection setup with Apple-Mail-style command sequence) if that would help triage?

Happy to capture an mitmproxy TLS-decrypted wire trace from a live iPhone connection if you can suggest the specific commands to look for. We can also share TCP-level packet timing if useful.

Expected Behavior

Apple Mail on iOS (and macOS Mail), after a standard account add via Mozilla-style autoconfig against a Stalwart IMAP server, should display the inbox contents — the same messages that SELECT INBOX reports via * N EXISTS and that UID FETCH returns from the server.

Apple’s TN3191 — IMAP extensions supported by Mail lists the extensions Mail requires/recommends; Stalwart advertises all of them. Per RFC 3501, a folder named INBOX is the canonical inbox and should be displayed by any compliant IMAP client.

Actual Behavior

Apple Mail on iOS connects, authenticates successfully, and then renders the inbox view as empty — the iOS placeholder text “nothing left to read, enjoy your empty inbox” is shown. macOS Mail behaves identically for the same user/account on the same network.

Server-side, SELECT INBOX returns * 91 EXISTS for the same account at the same moment, and UID FETCH 1:3 (UID FLAGS BODY[HEADER.FIELDS (SUBJECT FROM DATE)]) returns real messages with valid headers (verified via raw openssl s_client -connect 127.0.0.1:993). Outlook desktop, Thunderbird, and direct IMAP CLI all see the messages correctly when pointed at the same account.

Deviation: the inbox is not empty (91 messages exist); Apple Mail iOS and macOS Mail uniquely fail to display them. Outlook desktop on the same account works correctly, so the protocol/server side cannot be entirely broken.

The bug reproduces across:

  • Two end users on different home networks, two distinct Apple-Mail-on-iPhone setups
  • Both iPhone-only and iPhone + macOS (macOS Mail also affected for one user)
  • Two different accounts on the same server, after the standard autoconfig setup flow

Reproduction Steps

  1. Install Stalwart v0.16.4 on Ubuntu 24.04 (aarch64) with the IMAP listener bound to [::]:993 with tlsImplicit = true and a publicly-trusted (Let’s Encrypt) cert.
  2. Create a user account (e.g. [email protected]) and populate the inbox with ~90 messages via SMTP delivery.
  3. On an iPhone running iOS 18.x, open Settings → Mail → Add Account → Other → Add Mail Account, enter the account credentials, accept the autoconfig that resolves via the Mozilla-style /mail/config-v1.1.xml endpoint (IMAP host port 993 SSL, SMTP host port 465 SSL, full email as username, password-cleartext).
  4. Wait 1–2 minutes for the initial sync.
  5. Open Mail.app, navigate to the account’s Inbox view.
  6. Observe: empty inbox placeholder rather than ~90 messages.
  7. From inside the server, run openssl s_client -connect 127.0.0.1:993 -servername <host> with LOGIN [email protected] <pw> then SELECT INBOX — observe * 91 EXISTS (the messages exist; Apple Mail’s empty render is not consistent with server state).
  8. Set up the same account in Outlook desktop or Thunderbird on a separate machine — observe all 91 messages display correctly. We also tried (all without effect on the iOS empty-inbox symptom):
  • Flattened the mailbox tree so INBOX has no children. Deleted a duplicate Sent Messages folder leaving only the \Sent-flagged Sent Items.
  • Verified our advertised IMAP capabilities cover every required/recommended extension in TN3191 (SASL-IR, CONDSTORE, ESEARCH, ID, IDLE, LIST-STATUS, LITERAL+, MOVE, SPECIAL-USE, UIDPLUS are all advertised; only optional optimisations UIDONLY/RFC 9586, APPENDLIMIT, COMPRESS, PARTIAL are missing).
  • Verified \HasChildren / \HasNoChildren are emitted correctly on LIST-EXTENDED with CHILDREN return option (crates/imap/src/op/list.rs:243-250).
  • Confirmed the IDLE-pipelining fix (commit 51c8f7b2, shipped in v0.10.0) is present.
  • Confirmed credentials are correct (same creds work in Outlook desktop, Thunderbird, and direct IMAP CLI on the same iOS device’s network).
  • Confirmed no fail2ban block — auth succeeds ~30 times/hour from the iPhone’s IP without error.
  • Deleted the account from the iPhone, deleted saved entries from Settings → Passwords, full power-cycle, re-added fresh via autoconfig. Same empty result.
  • Cherry-picked upstream commit 77042480ae (“Fix IMAP: UID FETCH N:* could miss messages moved into a SELECTed mailbox by another connection”) on the speculation that Apple Mail’s per-folder parallel connections + aggressive MOVEs could be hitting this race. Rebuilt and deployed; iPhone re-tested against the patched binary; symptom persists. Falsifies that specific hypothesis.

Relevant Log Output

Server runs with Tracer.level = "info" by default. Per-connection IMAP auth events appear cleanly:

2026-05-14T18:49:04Z INFO Authentication successful (auth.success) listenerId = "imaps", localPort = 993, remoteIp = <iPhone-public-IP>, remotePort = 49319, accountName = "[email protected]", accountId = 4
2026-05-14T18:49:04Z INFO Authentication successful (auth.success) listenerId = "imaps", localPort = 993, remoteIp = <iPhone-public-IP>, remotePort = 49320, accountName = "<other-user>@example.com", accountId = 3
2026-05-14T18:49:15Z INFO SMTP EHLO command (smtp.ehlo) listenerId = "submissions", localPort = 465, remoteIp = <iPhone-public-IP>, remotePort = 49321, domain = "localhost"

After each auth.success from the iPhone’s IP, no subsequent IMAP-related events appear on that connection — no imap.select, imap.fetch, imap.list, imap.logout. The two interpretations are:

  • (a) iOS Mail authenticates and disconnects without issuing commands.
  • (b) The IMAP-command events fire in source but aren’t captured in our release-build log even at Tracer.level = "trace".

To test (b): I bumped the tracer level to trace, then ran a full local LOGIN → SELECT INBOX → UID FETCH 1:3 → LOGOUT sequence via openssl s_client from inside the box. Only the single auth.success INFO entry was produced — no imap.select, imap.fetch, or imap.logout log entries despite the trace-level setting. The relevant trc::event!(Imap(trc::ImapEvent::Fetch), ...) calls at crates/imap/src/op/fetch.rs:237, op/select.rs:109, op/list.rs:296, and op/logout.rs did not produce log output at trace level on a release build. If this is intentional (release-only event filtering), it blocks server-side wire-level debugging of Apple Mail issues without a debug rebuild.

Raw IMAP exchange from openssl s_client (server side is healthy):

* OK [CAPABILITY IMAP4rev2 IMAP4rev1 ENABLE SASL-IR LITERAL+ ID UTF8=ACCEPT JMAPACCESS AUTH=PLAIN AUTH=OAUTHBEARER AUTH=XOAUTH2] Stalwart IMAP4rev2 at your service.

a01 LOGIN [email protected] <password>
a01 OK [CAPABILITY IMAP4rev2 IMAP4rev1 ENABLE SASL-IR LITERAL+ ID UTF8=ACCEPT JMAPACCESS IDLE NAMESPACE CHILDREN MULTIAPPEND BINARY UNSELECT ACL UIDPLUS ESEARCH WITHIN SEARCHRES SORT THREAD=REFERENCES LIST-EXTENDED LIST-STATUS ESORT SORT=DISPLAY SPECIAL-USE CREATE-SPECIAL-USE MOVE CONDSTORE QRESYNC UNAUTHENTICATE STATUS=SIZE OBJECTID PREVIEW RIGHTS=texk QUOTA QUOTA=RES-STORAGE] Authentication successful

a02 LIST "" "*"
* LIST (\Trash) "/" "Deleted Items"
* LIST (\Drafts) "/" "Drafts"
* LIST () "/" "INBOX"
* LIST (\Junk) "/" "Junk Mail"
* LIST (\Sent) "/" "Sent Items"
a02 OK LIST completed

a03 SELECT INBOX
* 91 EXISTS
* 0 RECENT
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
* OK [PERMANENTFLAGS (\Deleted \Seen \Answered \Flagged \Draft \*)] All allowed
* OK [UIDVALIDITY 4078036059] UIDs valid
* OK [UIDNEXT 104] Next predicted UID
* OK [MAILBOXID (qaaaaaa)] Unique Mailbox ID
a03 OK [READ-WRITE] SELECT completed

a04 UID FETCH 1:3 (UID FLAGS BODY[HEADER.FIELDS (SUBJECT FROM DATE)])
* 1 FETCH (UID 1 FLAGS () INTERNALDATE "04-May-2026 16:32:14 +0000" ...)
* 2 FETCH (UID 2 FLAGS () INTERNALDATE "04-May-2026 18:15:32 +0000" ...)
* 3 FETCH (UID 3 FLAGS (\Seen) INTERNALDATE "04-May-2026 18:30:59 +0000" ...)
a04 OK FETCH completed

a05 LOGOUT
* BYE Stalwart IMAP4rev2 bids you farewell.
a05 OK LOGOUT completed

Stalwart Version

v0.16.x

Installation Method

Built from source

Database Backend

RocksDB

Blob Storage

Filesystem

Search Engine

Internal

Directory Backend

Internal

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

Let me research this thoroughly before responding.

This is a long but highly informative source read. Here’s what I found from all the code reviewed:

On the logging question (your Ask #2): The IMAP events imap.select, imap.fetch, imap.list, imap.logout etc. all have a default level of debug (they fall through to the _ => Level::Debug catch-all in the level() implementation in crates/trc/src/event/enums_impl.rs). Only imap.raw-input and imap.raw-output are trace level. This means setting Tracer.level = "trace" is not the issue – you need Tracer.level = "debug" (or lower) to see imap.select, imap.fetch, etc. Since your default is info, these events are filtered out. This is intentional design, not a release-build regression.

On Ask #1 (Apple Mail empty inbox): Looking at the select.rs source, the SELECT response does not include an UNSEEN unilateral * N UNSEEN response (notice unseen_seq: 0 is hard-coded in the response struct). Apple Mail on IMAP4rev2 may rely on a specific sequence to initialize its view. The server source doesn’t indicate any known Apple Mail-specific workaround, and the docs don’t document any such bug or fix at this time.


To answer your three asks directly:

Ask 1: No known Stalwart-specific report of this exact symptom is documented or visible in the current source or docs. The wire-trace approach you’re proposing (mitmproxy on the iPhone) is the right next step.

Ask 2: The absence of imap.select, imap.fetch, imap.list, imap.logout events at trace level is because those events are logged at debug level by design (confirmed in crates/trc/src/event/enums_impl.rs). Set your tracer’s level to debug (not trace) and they will appear. Only raw wire I/O (imap.raw-input, imap.raw-output) requires trace.

Ask 3: One thing worth checking in the wire trace: the SELECT response in select.rs hard-codes unseen_seq: 0, meaning Stalwart never sends * N UNSEEN in the SELECT response. Some Apple Mail versions use that response to decide whether to fetch. Whether this is the cause is speculative without a wire trace, but it is a protocol detail that differs from servers like Dovecot that do emit * UNSEEN.

Set the tracer to debug level to capture the IMAP command flow, and enable imap.raw-input/imap.raw-output at trace level to see the full wire exchange from the server’s perspective alongside your mitmproxy capture.

Reference: Stalwart Events docs for event level configuration.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong. If you found it useful, mention @helpbot in any reply to ask follow-up questions and the bot will respond again.

Without the actual wire bytes from Apple Mail it’s hard to land on a hypothesis. Easiest path is to capture the raw I/O instead of the per-command events: with Tracer.level = "trace", RawInput and RawOutput should show every byte both ways.

On the IMAP4rev2 question: the greeting just advertises the banner string. Sessions stay in Rev1 until the client sends ENABLE IMAP4rev2, and Apple Mail doesn’t, so Rev2 differences aren’t a likely cause here.

The thing I’d specifically want from the raw trace: does Apple Mail send CAPABILITY after LOGIN, then LIST "" "*" / LIST "" "%" or NAMESPACE before disconnecting? The “silent empty inbox” pattern on other servers usually traces to one of those returning something Apple Mail decides is fatal. Could you also send the output of LIST "" "*" from your openssl s_client session (which works), so I can compare?

I also tried to connect iOS mail to Stalwart but without success. Thunderbird connects fine.

I had the same issue. Creating an app password for Apple Mail. solved it for me.