diff --git a/internal/imap/scheduler.go b/internal/imap/scheduler.go index 86ecf65..062e6bd 100644 --- a/internal/imap/scheduler.go +++ b/internal/imap/scheduler.go @@ -398,7 +398,17 @@ func (s *Scheduler) syncFolder( if err != nil { return 0, 0, fmt.Errorf("imap scheduler: uid search incremental %q: %w", folder, err) } - uids = searchData.AllUIDs() + // Defensive re-filter: some IMAP servers mishandle open-ended "N:*" + // UID ranges and return UIDs below N anyway (observed against + // q.sysops.de — "UID SEARCH UID 22:*" on a 21-message mailbox + // returned [21] instead of an empty set). Trusting that blindly + // makes the sync loop rediscover the same top message as "new" + // forever, every cycle. Never trust the server's range filtering. + for _, uid := range searchData.AllUIDs() { + if uid >= minUID { + uids = append(uids, uid) + } + } } else { // First sync or post-UIDVALIDITY-reset: fetch everything. searchData, err := c.UIDSearch(&imapv2.SearchCriteria{}, nil).Wait()