Troubleshoot Invisible Mail (Stale uidlist Lock)¶
Symptom¶
A user reports receiving no new mail, but the server looks healthy:
Postfix/LMTP logs show successful deliveries:
stored mail into mailbox 'INBOX'The user’s IMAP client logs in without errors
Other users receive mail normally
Diagnosis¶
Compare what the logs claim with what the index knows:
# LMTP says mail was stored today...
kubectl logs -n mailu deploy/<dovecot-deployment> --since=24h | grep "lmtp(<user>)"
# ...but the index finds nothing recent (empty = broken)
kubectl exec -n mailu deploy/<dovecot-deployment> -- \
doveadm search -u <user> mailbox INBOX SAVEDSINCE <yesterday>
Then check the maildir on disk:
kubectl exec -n mailu deploy/<dovecot-deployment> -- sh -c \
'ls -lt /mail/<user>/new/ | head; ls -lt /mail/<user>/cur/ | head -3'
Broken state: recent files pile up in new/, the newest file in cur/
is days old, and dovecot-uidlist has a stale modification time.
Confirm the root cause — a leftover dotlock:
kubectl exec -n mailu deploy/<dovecot-deployment> -- \
ls -la /mail/<user>/dovecot-uidlist.lock
The lock file contains <pid>:<hostname>. Because a container restart keeps
the pod hostname while PIDs restart from 1, Dovecot can mistake a dead
writer’s lock for a live one (PID reuse) and never breaks it. New mail then
never gets UIDs assigned — delivered to disk, invisible to IMAP. IMAP
session log lines show long in locks times for the affected user.
Fix¶
# 1. Remove the stale lock (safe once the owning PID is gone)
kubectl exec -n mailu deploy/<dovecot-deployment> -- \
rm /mail/<user>/dovecot-uidlist.lock
# 2. Rebuild the index from the maildir (non-destructive)
kubectl exec -n mailu deploy/<dovecot-deployment> -- \
doveadm force-resync -u <user> INBOX
# 3. Verify the message count increased and recent mail is indexed
kubectl exec -n mailu deploy/<dovecot-deployment> -- \
doveadm mailbox status -u <user> "messages unseen" INBOX
kubectl exec -n mailu deploy/<dovecot-deployment> -- \
doveadm search -u <user> mailbox INBOX SAVEDSINCE <incident-date> | wc -l
The user’s client shows the backlog on its next sync.
Check the Whole Fleet¶
After any dovecot crash or forced restart, scan for further stale locks:
kubectl exec -n mailu deploy/<dovecot-deployment> -- \
find /mail -maxdepth 2 -name "dovecot-uidlist.lock" -mmin +60 -exec ls -la {} \;
Mail sitting in new/ alone is not proof of breakage — mailboxes that
no client ever opens keep mail in new/ legitimately. The stale .lock
file plus a frozen dovecot-uidlist mtime is the signal.
Incident Reference¶
2026-08-24: a dovecot container restart left dovecot-uidlist.lock behind
for one user; 53 delivered mails were invisible for two days while LMTP
kept logging successful stores. Removing the lock and running
force-resync restored visibility within seconds.