VayuPress Docs

ADR-0130: VayuMail retention — auto-delete read mail

  • Status: Accepted
  • Date: 2026-07-11
  • Relates to: ADR-0129 (device approval), the VayuMail Maildir store

Context

Mailboxes accumulate read mail indefinitely. The operator wants hygiene with teeth: mail that was read and never deliberately saved should be permanently deleted after a configurable window — 30/90/180/365 days — with no recovery, per mailbox, off by default.

Two design questions dominate: what counts as saved, and where the read time comes from.

Decision

Saved means intent

The sweep only ever touches Inbox, Junk, and Trash. Sent, Drafts, Archive, and Snoozed all encode deliberate intent (you wrote it, you're still writing it, you archived it, you scheduled it) and are exempt. Within the swept folders, a message survives when it is unread (still in new/, or without the Maildir S flag) or pinned (the F flag — the webmail's 📌 and IMAP's \Flagged). Everything else that has been read for longer than the window is removed with os.Remove — permanent, no Trash detour, exactly as specified.

Read time is the file mtime, stamped at the Seen transition

setMessageFlags is the single chokepoint through which every flag change passes — webmail mark-read, IMAP STORE, POP3-driven flag updates. Maildir renames preserve mtime, so a file's timestamp normally stays at delivery time. At the moment a message first gains S, the store now stamps the file's mtime to now; every later flag rename preserves that stamp. The retention sweep reads it back with os.Stat.

This gives a durable, restart-proof read time with zero new bookkeeping: no table, no dual-write, no reconciliation between the filesystem (the source of truth for mail) and SQLite. The trade-offs are acknowledged:

  • Mail read before this release keeps its delivery mtime, so for legacy mail the window counts from delivery rather than read time — retention can only fire earlier for such mail, never keep it longer. Operators enable retention after upgrading, so the first sweep's behaviour is predictable.
  • Filesystem tools that rewrite mtimes (backup restores without --times, touch) would reset the clock. Acceptable for a self-hosted single-VPS product; the alternative (a read-times table) buys precision at the cost of a second source of truth.

Configuration and audit

vayumail_accounts.retention_days (idempotent ALTER, default 0 = off) with RetentionDays/SetRetentionDays accessors. The admin Accounts page gains an "Auto-delete read" select per mailbox (Off/30/90/180/365) posting through the existing account-update handler (admin-only; audit vayumail.retention.set). The sweeper runs from Engine.Start — first pass two minutes after boot, then hourly, stopping with the engine's done channel — and every sweep that deletes anything writes one vayumail.retention.sweep audit entry per mailbox with the count, via a callback so the engine stays free of the database layer.

Consequences

  • Opted-in mailboxes stay lean without manual cleanup; the exemption set (pinned/Archive/Sent/Drafts/Snoozed/unread) makes "save this" a one-tap action in every client.
  • Deletion is genuinely unrecoverable, matching the operator's requirement — the UI copy says so explicitly.
  • IMAP clients see the deletion as an expunged message on their next sync; the UID store already tolerates disappearing files.
  • Tests pin the eligibility matrix (old-read deleted; fresh-read, unread, pinned, Archive, Sent survive), the off-is-noop guarantee, and the mtime stamping/preservation semantics.