Enable ManageSieve: Let Users Edit Their Own Server-Side Mail Filters
Client-side mail rules only fire when the app is open, and every device rebuilds the same logic in isolation. ManageSieve turns filtering into one server-side ruleset that Roundcube, Thunderbird, and mobile all edit over port 4190 — and it runs at delivery, whether or not any client is online. Here is the full Dovecot + Pigeonhole enablement, from the service block to STARTTLS to safe user editing.
EvilMail TeamJuly 12, 202612 min read
A user asks why the newsletter they filed into a folder on their laptop is back in the inbox on their phone. The answer is that neither rule ever ran on the server. A Thunderbird message filter executes only when Thunderbird is open and actively polling; a rule built in webmail lives in a totally separate database; the phone knows about neither. All three fire *after* delivery, on whichever client happens to be awake — which is why a list-heavy inbox still buzzes every device before anything gets sorted.
Server-side Sieve fixes this, and on most Dovecot deployments the filtering engine is already installed and running — it's just admin-only or invisible. The piece that's usually missing is ManageSieve: the remote protocol that lets a user's own client upload and activate a script over the wire. Turn it on and "move newsletters to a folder" becomes one ruleset that fires at delivery time, server-side, exactly once, regardless of which client — if any — is running. This is the single highest-leverage deliverability-adjacent feature you can ship for a mail tenant, and the only real hazard is a rule that discards mail you meant to keep — something sieve-test and Sieve's implicit keep make almost impossible to trigger by accident.
Sieve vs. ManageSieve: two things people conflate
Keep the two concepts separate or the config won't make sense:
Enable ManageSieve on Dovecot: User-Editable Server-Side Mail Filters — EvilMail Blog
Sieve
is the filtering *language*, defined in RFC 5228. It's what actually decides
fileinto "Lists"
or
discard
. In Dovecot this is the
sieve
plugin, loaded into the LDA/LMTP delivery path.
ManageSieve is the *remote management protocol*, RFC 5804, on TCP port 4190. It's how a client fetches, edits, uploads, and activates scripts. In Dovecot this is the managesieve service.
Both come from Pigeonhole, the Sieve implementation for Dovecot. The overwhelmingly common situation is a stack that has the first (delivery-time filtering works if you drop a script in place) but not the second (users have no way to edit it). We're adding the second.
The delivery path: where a Sieve script actually fires
Trace one message end to end so the "why does this beat client rules" argument is concrete. Postfix accepts the mail and hands it to Dovecot over LMTP. Dovecot's LMTP/LDA process loads the recipient's active Sieve script and evaluates the rules top-down. Each fileinto, keep, discard, or redirect is an action; if no action files the message anywhere, Sieve's implicit keep drops it into INBOX. The message is filed once, and only then does any IMAP client ever see it.
Because Sieve runs before the message is filed, a fileinto "Lists" beats every client rule that would otherwise try to move it later. That's the whole pitch.
Turn on the managesieve service
Everything below lives under /etc/dovecot/conf.d/. First, add the sieve token to your protocols — that token is what makes Dovecot start the ManageSieve service at all:
protocols = imap lmtp sieve
The delivery plugin and its limits live in 90-sieve.conf. Set the anti-abuse knobs here — they matter on a multi-tenant host, because an unbounded redirect rule is an open spam relay:
You want to see LISTEN ... :4190 bound by a dovecot process. If the port isn't there, the sieve token is missing from protocols — that's the number-one cause.
Don't ship cleartext Sieve
ManageSieve authenticates with the same SASL credentials as IMAP — the user's actual mailbox password. An unencrypted 4190 leaks that password on every login. Require STARTTLS before AUTH:
disable_plaintext_auth = yes
managesieve-login reuses the same ssl_cert and ssl_key as imap-login, so if IMAP TLS already works you have nothing else to provision. Open 4190/tcp at the firewall — and only 4190. Do not touch port 2000: that was the old unofficial ManageSieve port, it's deprecated, and it collides with Cisco SCCP. IANA assigned 4190 for exactly this reason (RFC 5804).
Verify the handshake and capability banner before you wire a single client:
Look for the implementation string and the advertised extensions:
"IMPLEMENTATION" "Dovecot Pigeonhole"
"SIEVE" "fileinto reject envelope encoded-character vacation
subaddress comparator-i;ascii-numeric relational regex imap4flags
copy include variables body enotify environment mailbox date index
ihave duplicate mime foreverypart extracttext"
"STARTTLS"
If vacation, fileinto, imap4flags, and mailbox are in that list, the user-facing rules people actually want will all work.
Test a script before it can eat mail
The single operational rule that prevents disasters: never activate an untested script. Pigeonhole ships the tools to make this trivial. Here's a real ~/sieve/lists.sieve:
require ["fileinto", "mailbox", "imap4flags"];
# File any mailing-list traffic and stop
if header :contains "List-Id" "" {
fileinto :create "Lists";
stop;
}
# Flag anything from billing
if address :domain "from" "billing.example.com" {
setflag "\\Flagged";
}
The :contains "List-Id" "" test matches any message that carries a List-Id header at all — an empty substring is contained in every value — which is the standard "catch all list mail" idiom. Compile the script to catch syntax errors, then dry-run it against a saved message to see exactly which actions fire, without touching a real mailbox:
bash
sievec lists.sieve # syntax check only
sieve-test -e lists.sieve sample.eml # show actions that would run
sieve-test prints the resolved action list — fileinto "Lists"; stop; — so you can confirm the branch you expected actually matched. The safety net underneath all of this is implicit keep: if a script compiles but no action matches a given message, Dovecot files it to INBOX, so the message is never lost. The *only* actions that can genuinely destroy mail are discard and an unconditional reject; everything else degrades to keep.
The most-requested user rule is an auto-reply, and Sieve's vacation (RFC 5230) is the correct tool, because it de-duplicates and throttles per sender:
require ["vacation"];
vacation
:days 3
:subject "Out of office"
"Back Monday — for anything urgent, call the desk line.";
The :days 3 throttle means a given correspondent gets at most one auto-reply every three days, which is what stops the classic vacation-responder mail loop.
Wire the clients: one script, many editors
Roundcube is the webmail payoff. Enable the bundled managesieve plugin and point it at the local endpoint:
A Filters entry appears under Settings. Users build rules with dropdowns — "if List-Id contains … then move to Lists" — and Roundcube generates the Sieve script and uploads it over ManageSieve. They never see the syntax.
Thunderbird covers the desktop with the community "Sieve" add-on. Point it at the same host on 4190 with STARTTLS and it edits the *same active script* Roundcube writes. Make a rule in webmail, open Thunderbird, and it's already there. K-9 Mail / Thunderbird for Android speak the same protocol to the same endpoint. That's the single-source-of-truth model the second diagram shows: many editors, one ruleset, running server-side no matter which one you touched last.
Guardrails for a multi-tenant mailhost
Handing users an editing surface means handing them a small programming language, so cap what it can do:
Size and action caps — sieve_max_script_size, sieve_max_actions, and especially sieve_max_redirects = 4. A rule that redirects to many addresses is a relay-abuse vector; keep the ceiling low.
No external programs unless you fully trust the tenant. Do not enable vnd.dovecot.pipe or the filter/execute extensions on shared hosting — they run commands on your box.
A global "before" policy you control. sieve_before = /etc/dovecot/sieve/global-before.sieve runs ahead of every user script and can't be overridden — that's where tenant-wide rules (quarantine routing, forced spam foldering) belong.
Quota awareness — fileinto still consumes the user's quota; it moves mail, it doesn't exempt it. A "file everything into Archive" rule won't rescue a full mailbox.
Logging — Sieve actions land in Dovecot's mail log. When a user swears "my rule didn't fire," grep sieve /var/log/dovecot.log (or doveadm log find) shows the executed actions and any runtime errors, and sieve-filter lets an admin replay a script against an existing mailbox.
Ship-it checklist
protocols includes sieve; ss -ltnp | grep 4190 shows the listener.
disable_plaintext_auth = yes, and openssl s_client -starttls sieve completes the handshake.
sieve_max_script_size, sieve_max_actions, sieve_max_redirects are all set.
sieve_before global policy is in place for tenant-wide rules.
Every candidate script passes sievec and was dry-run with sieve-test before activation.
Roundcube Filters UI loads, and a rule made there appears in Thunderbird against the same script.
Run the openssl s_client -starttls sieve one-liner against your own host right now — if the "SIEVE" line lists fileinto and vacation, your users are one Roundcube login away from filtering that finally follows them across every device.