00 · Overview
This report documents a defect in the VOXMail offline mailbox sync pipeline. Under the current mbsync configuration, deletions performed on the remote IMAP server are not reconciled against the local Maildir, which leaves orphaned files behind and inflates unread counts. It describes the observed behavior, the intended behavior, and the minimal configuration and indexer changes required to resolve it.
01
Current Behavior
- 01
IMAP synchronization runs on
mbsyncwith Sync All, Remove None and Expunge None policies. - 02
When a message is moved or deleted on the remote IMAP server (for example Gmail), the remote UID mapping is pruned from
.mbsyncstate, but the local Maildir file stays on disk carrying a trashed flag (:2,T). - 03
Because mbsync no longer tracks that UID, it treats the local file as untracked rather than deleted, leaving ghost files in the local Inbox.
- 04
The mail indexer reads local Maildir flags; files flagged
T(trashed) without theS(Seen) flag are counted as unread, producing persistent phantom unread counts.
02
Desired Behavior
- 01
When a message is removed from the remote Inbox (archived, moved to Trash, or deleted), the local copy in
Inbox/curis expunged automatically on the next sync pass. - 02
Local Inbox unread counts reflect the remote inbox accurately, with no accumulation of ghost files.
- 03
Flag sync operates bidirectionally without preserving orphaned local files.
03
Changes Required to Fix
Inbox channel
Folders channel
Sync policy unit test
Indexer logic
Exclude messages flagged T (trashed) from the Inbox unread count calculation, so ghost files no longer surface as unread.
// unreadCount counts local messages missing the Seen (S) flag.
// Trashed (T) files are excluded - they are orphaned and never read.
func unreadCount(messages []*MaildirMessage) int {
n := 0
for _, m := range messages {
if m.flags.Has("S") || m.flags.Has("T") {
continue
}
n++
}
return n
}04 · Verification
How this is confirmed
- 01
Update the unit test assertion in
internal/mailconfig/mbsync_test.goto expect Expunge Near in place of Expunge None. - 02
Run the full test suite. All config generation tests must still pass against the updated assertion.
- 03
Expected result: with both channel channels set to Remove Near and Expunge Near, mbsync expunges local Inbox files whose remote UID has been removed, and unread counts no longer include orphaned trashed files.