Server-Side Mail Filtering with Dovecot Sieve: Rules That Follow You
Client-side filters break the moment you switch devices. Dovecot's Sieve plugin moves mail filtering to delivery time on the server—here's how it works, a starter script, and how to verify it actually runs.
09 Apr 2026, 07:53 UTC

You set up a perfect set of filters in Thunderbird: mailing lists filed away, notifications sorted, junk discarded. Then you open webmail from another machine and your inbox is chaos again, because those filters live in the client and only run when that client is running. The fix is to move the rules to the server, and on Dovecot that means Sieve.
Sieve (RFC 5228) is a small, purpose-built language for filtering mail at delivery time. Dovecot implements it through the Pigeonhole project. Because scripts execute inside Dovecot's delivery path, every message is filtered the moment it arrives—before any client ever sees it. One set of rules, every device, always applied.
Where Sieve actually runs
This is the part people get wrong, so it's worth being precise: Sieve runs at final delivery, inside Dovecot's LDA or LMTP process. When your MTA (Postfix, Exim) hands a message to Dovecot for local delivery, the Sieve plugin evaluates the user's script and decides where the message lands.
Two consequences follow. First, if delivery bypasses LDA/LMTP—say the MTA writes directly to mailboxes, or another delivery agent is in the path—Sieve scripts silently never run. This is a common misconfiguration and it fails quietly: no errors, just unfiltered mail. Second, Sieve only sees mail at delivery. It does not reprocess existing messages, and it's not a replacement for spam scanning that should happen earlier in the pipeline (Rspamd, SpamAssassin). Sieve complements those tools; it doesn't replace them.
Enabling it
On most distributions the Pigeonhole plugin ships as a separate package (often named something like dovecot-sieve and dovecot-managesieved). After installing, you load the plugin in the LMTP configuration and tell it where user scripts live. A typical snippet in /etc/dovecot/conf.d/90-sieve.conf or 15-lda.conf:
protocol lmtp {
mail_plugins = $mail_plugins sieve
}
plugin {
sieve = file:~/sieve;active=~/.dovecot.sieve
sieve_dir = ~/sieve
}The sieve setting points at the active script; sieve_dir is where uploaded scripts are stored. If you use virtual users, make sure the home directory in these paths actually resolves for the mail user—scripts in nonexistent home directories are another silent failure.
After editing, run doveconf -n as root and confirm sieve appears in mail_plugins under protocol lmtp (or lda), then reload Dovecot. If the plugin isn't listed there, it isn't running, no matter what your config files say elsewhere.
A worked example
Here's a realistic starter script: file mailing-list traffic into its own folder, drop anything already tagged as spam, and let everything else fall through to the inbox.
require ["fileinto", "mailbox"];
if header :contains "List-Id" "dovecot" {
fileinto :create "Lists.Dovecot";
stop;
}
if header :contains "X-Spam-Flag" "YES" {
fileinto "Junk";
stop;
}A few things to notice. The require line declares which extensions the script uses; fileinto enables delivery to folders other than INBOX, and mailbox provides :create, which auto-creates the target folder if it doesn't exist. The stop command ends processing so later rules don't also fire.
Most importantly: if no rule matches, Sieve performs an implicit keep—the message goes to INBOX. This makes scripts safe to extend incrementally. A rule that never matches costs you nothing; a rule you haven't written yet doesn't lose mail.
One caveat: available extensions depend on how Pigeonhole was compiled for your distribution and version. Check require lines against your local install before deploying; an extension that exists upstream may not exist in your package.
Getting scripts onto the server
Users can edit ~/.dovecot.sieve directly, but the friendlier path is ManageSieve, Pigeonhole's management daemon. It listens on port 4190 and lets clients like Thunderbird (with a Sieve extension) or webmail (Roundcube ships a ManageSieve plugin) upload and activate scripts. Dovecot compiles scripts to a binary form and validates them at upload time, so a broken script is rejected before it can affect delivery.
Do not expose ManageSieve without TLS and authentication. It handles credentials, and an open 4190 on the internet is a credential-theft invitation. Keep it on localhost or behind TLS, the same discipline you'd apply to IMAP.
Verify before you trust it
Sieve's failure modes are quiet, so verification matters more than usual. Three practical checks:
- Compile-check the script. Run
sievec script.sieveas the mail user. It reports syntax errors without deploying anything. A clean compile is a prerequisite, not proof of correct logic. - Trace evaluation offline. Pigeonhole ships
sieve-test, which runs a script against a sample message file and shows exactly which rules matched and what action would be taken—without touching a live mailbox. Save a real message (headers included) to a file and test against it. - Send a live probe. Deliver a test message that should match your
fileintorule, then check the target mailbox anddoveadm log errors(run as root) for delivery traces. If the message lands in INBOX instead, the script either didn't match or never ran—doveconf -nwill tell you which.
The trade-off
Sieve is deliberately limited. It can't run arbitrary code, can't call external programs (unless you explicitly enable the extprograms extension), and can't modify messages—only file, redirect, reject, or discard them. That constraint is a feature: scripts are safe to let users write, and they execute fast. But if you need content rewriting, auto-replies with complex logic, or integration with external systems at delivery time, you'll hit the ceiling quickly and need to look at MTA-level filtering or extprograms instead.
Where to start
Install the Pigeonhole packages, enable the plugin for LMTP, and deploy the two-rule script above with sieve-test before going live. Once delivery filtering works, turn on ManageSieve over TLS so users can manage their own rules. You'll have filtering that works identically on every client—and one less class of "why is my inbox different on my phone" questions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.