Server-Side Mail Sorting with Dovecot Sieve: Filtering That Works Even When Your Client Doesn't
Client-side mail rules only work where they're defined. Dovecot's Pigeonhole Sieve plugin moves filtering to delivery time, so sorting works from every device — here's how to enable it safely with a minimal spam rule.
05 Aug 2025, 12:29 UTC

You set up a mail rule on your laptop: anything flagged as spam goes to Junk. It works — until you check mail from your phone, where the rule doesn't exist, and the junk sits in your inbox. Client-side filters only run where they're defined. If you want sorting that happens no matter which device or client reads the mail, the filtering has to happen on the server, at the moment the message is delivered.
That's exactly what the Pigeonhole Sieve plugin adds to Dovecot: rules evaluated during delivery, before the message lands in a mailbox. This post covers where Sieve fits in Dovecot's architecture, a minimal working rule, and the operational risks that make a broken filter look like lost mail.
Where Sieve actually runs
Dovecot is primarily an IMAP/POP3 server — it serves mail that's already stored. Filtering doesn't happen inside IMAP sessions. Instead, Sieve hooks into the delivery path: either Dovecot's Local Delivery Agent (LDA) or its LMTP service, which receive messages from your MTA (Postfix, Exim, etc.) and write them into mailboxes.
The practical consequence: enabling Sieve is an additive, delivery-stage change. Your authentication setup, mailbox layout, and TLS configuration stay untouched. You're only telling the delivery process to consult a script before filing each message. That also means Sieve only sees mail at delivery time — it won't retroactively sort messages already in the inbox.
A minimal working rule
A common pattern: your spam scanner adds an X-Spam-Flag: YES header, and you want those messages filed into Junk regardless of client. A per-user Sieve script might look like this:
require ["fileinto"];
if header :is "X-Spam-Flag" "YES" {
fileinto "Junk";
stop;
}Two things to note. First, fileinto is an extension, so the require line is mandatory — without it, the script fails. Second, the rule is entirely client-independent: any IMAP client that can see the Junk folder sees the result.
On the Dovecot side, the typical configuration enables the Sieve plugin for the delivery protocols and points at per-user script storage, often under the user's home directory:
protocol lmtp {
mail_plugins = $mail_plugins sieve
}
plugin {
sieve = file:~/sieve;active=~/sieve/active.sieve
}Treat this as a template, not gospel. Directive names, defaults, and packaging differ between Dovecot and Pigeonhole releases, and distribution packages sometimes pre-wire parts of this. Check your installed pair before copying anything.
Verify before and after
Because a broken script can affect every delivery for a user, verification matters more than the config itself. A reasonable sequence:
- Confirm what's installed:
dovecot --versionplus your package manager's output for Pigeonhole, thendovecot -nto see the configuration Dovecot actually loaded (run as root or a user with read access to Dovecot's config). - Syntax-check the script offline with
sievec script.sieveon a copy before activating it. This catches parse errors without touching live delivery. - Send a controlled test message through LMTP/LDA containing the trigger header, then confirm placement with
doveadm fetchor any IMAP client. Also send a non-matching message to prove unrelated mail is unaffected. - Watch Dovecot's logs during the test — Sieve compile and runtime errors surface there, not in the client.
The risks worth respecting
The biggest operational trap: Sieve failures look like missing mail, not like a filter error. A default script with a syntax error, or wrong ownership/permissions on the script directory, can break delivery for the affected user while everything else looks healthy. If users report vanished mail after enabling Sieve, check the script directory permissions and Dovecot logs first.
Second, Sieve runs before the message is safely stored. Actions like discard or broad redirect rules destroy auditability — a message that's discarded at delivery leaves no copy to investigate. For shared or compliance-sensitive mailboxes, prefer fileinto over destructive actions, and be conservative with catch-all conditions.
Third, ManageSieve — the protocol that lets users edit their own scripts over the network — is a separate listener with its own authentication and ACL surface. It's genuinely useful for multi-user systems, but don't open the port just because the plugin exists. Justify it, put it behind TLS, and verify that a disabled account can't upload or activate scripts.
Start small
Enable Sieve for LMTP/LDA delivery, deploy one boring, high-value rule (spam-to-Junk is the classic), and verify it end to end with a test message and dovecot -n. Once that path is proven, additional rules and per-user scripts are low-risk increments. The goal isn't a clever filtering setup — it's sorting you can trust to behave identically from every client, every time.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.