Proinet
← Case studies
Own infrastructure — mail & open source

Mail that expires itself — without ever leaving the inbox

Some mail only matters for a few days. Instead of filing it into a "review later" folder — where it never actually gets read again — we tag it in Sieve with a retention header and let it expire automatically, straight out of the inbox, once it's genuinely overdue.

dependencies beyond Python and doveadm
0
generic script handles every new retention rule
1
license — published as open source
MIT

Context

Moving a message out of the inbox is sometimes the fastest way to forget it for good. Folders created "for later" work like a drawer: a message disappears from view the moment it's filed, not the moment it actually stops mattering.

On our own mail server, some mail — backup reports, admin notifications, mailing lists — only mattered for a few days. Our Sieve rules used to file it into folders whose name already encoded a retention policy ("admin, 7 days"), with a separate cron job cleaning those folders out by age. The cleanup side always worked fine — the problem was earlier: mail was filed away the moment it arrived, so nobody read it again, no matter how quickly it was cleaned up afterwards.

How it went

We checked what already existed. Sieve itself can't express "older than N days" — its date extensions only compare against a value written permanently into the script, and the script itself runs once, at delivery, when a message's age is always zero. Dovecot's built-in age mechanisms (autoexpunge, the expire plugin) cover a whole mailbox, with no way to match by sender or subject. No Roundcube plugin exists for rule-based cleanup of mail already sitting in a mailbox — it comes up repeatedly on Roundcube's forums and mailing list, always with the same answer: it has to happen server-side.

We split policy from enforcement. Sieve has an editheader extension (RFC 5293) — compiled into Pigeonhole, but off by default because it modifies message content. Once enabled, a Sieve rule can simply tag a message with an X-Retention-After: 7 header and leave it exactly where it would have landed anyway — normally the inbox, fully visible. All the knowledge of what's temporary stays in Sieve, exactly as before.

sql
1require ["editheader"];
2if header :contains "from" "newsletter@example.com"
3{
4 addheader "X-Retention-After" "7";
5}

We built one generic enforcer. A separate script knows nothing about categories: it reads the header and the message's date, and expunges whatever's overdue — age is computed entirely by doveadm search ... sentbefore, no date arithmetic of our own. A new retention rule is one line of Sieve, zero changes to the script. Thanks to the same extension, Roundcube's own filter editor automatically gained an "Add header to message" action — no plugin install required.

The result

The solution runs in production on our mail infrastructure. We published it as open source: sieve-retention on GitHub — zero dependencies beyond Python and doveadm, MIT licensed.

This wasn't client work — it came out of running our own infrastructure and hitting a problem nobody had already solved. That's how we work on mail, DNS and hosting for our clients too: when the standard tools fall short, we build the missing piece — and give it back if it might help someone else.

Mail retention without leaving the inbox | Proinet