Server — Mail — Sieve
The Dovecot article configured Sieve as a plugin and dropped a minimal default script that sorts spam to a Junk folder. This article covers what Sieve actually is, how the script hierarchy works, what the language looks like, and how to build the more interesting part: using IMAPSieve to feed Rspamd training data when users move messages between folders.
What Sieve is
Sieve is a mail filtering language defined in RFC 5228. It runs server-side, at delivery time, before mail reaches the user’s inbox. The key point is when it runs: not when the user’s mail client connects to retrieve mail, but at the moment Dovecot receives the message from Postfix via LMTP. A Sieve rule that sorts spam to Junk runs before the message ever appears in the inbox.
This is different from client-side filtering rules in Thunderbird or Apple Mail, which run when the client connects and downloads mail. Server-side Sieve filtering works regardless of which client is used, works on messages received while no client is connected, and cannot be accidentally disabled by switching clients.
The script hierarchy
Dovecot processes Sieve scripts in a defined order. From the configuration in the Dovecot article:
sieve_before = /etc/dovecot/sieve/before.d/
sieve = file:~/sieve;active=~/.dovecot.sieve
sieve_after = /etc/dovecot/sieve/after.d/
sieve_default = /etc/dovecot/sieve/default.sieve
The processing order is:
sieve_beforescripts — global scripts that run before anything else. Use these for rules that must apply to all users regardless of their personal settings.- Per-user scripts — scripts managed by individual users via ManageSieve. Users can write their own filtering rules here without touching global configuration.
sieve_afterscripts — global scripts that run after the user’s scripts. Use these for rules that catch what the user’s scripts did not handle.sieve_default— the fallback script that runs if the user has no active personal script.
For February’s setup, the spam sorting rule belongs in sieve_before so it always runs first, before any user rules. A user cannot accidentally configure their personal scripts to bypass spam sorting.
The Sieve language
A Sieve script is a sequence of conditions and actions. The syntax is straightforward.
Requiring extensions:
require ["fileinto", "mailbox", "imap4flags"];
Extensions must be declared before use. fileinto enables delivering to a named folder rather than the inbox. mailbox enables creating the folder if it does not exist. imap4flags enables setting IMAP flags on delivered messages.
Matching a header:
if header :contains "Subject" "Invoice" {
fileinto "Finance";
stop;
}
header matches against message headers. :contains is a substring match. :is is an exact match. :matches supports wildcard patterns. The stop command halts processing after the action so subsequent rules do not also run.
Matching multiple values:
if header :contains "From" ["newsletter@", "updates@", "noreply@"] {
fileinto "Newsletters";
stop;
}
Sieve checks each value in the list and matches if any one of them satisfies the condition.
Combining conditions:
if allof (
header :contains "From" "bank.example.com",
not header :contains "Subject" "statement"
) {
fileinto "Junk";
stop;
}
allof requires all conditions to be true. anyof requires any one to be true. not negates a condition.
The spam sorting script
Create the before-delivery directory and the spam script:
sudo mkdir -p /etc/dovecot/sieve/before.d
sudo nano /etc/dovecot/sieve/before.d/10-spam.sieve
require ["fileinto", "mailbox"];
# Sort spam flagged by Rspamd into Junk
if header :is "X-Spam-Flag" "YES" {
fileinto :create "Junk";
stop;
}
Rspamd adds the X-Spam-Flag: YES header to messages it classifies as spam above the configured threshold. The :create flag tells Dovecot to create the Junk folder if it does not exist for this user.
Compile the script:
sudo sievec /etc/dovecot/sieve/before.d/10-spam.sieve
The compiled .svbin file is what Dovecot actually executes. The uncompiled .sieve file is the human-readable source. Both must be present; Dovecot checks modification times and recompiles if the source is newer than the binary.
Update the Dovecot Sieve configuration to use the before.d directory. Edit /etc/dovecot/conf.d/90-sieve.conf:
plugin {
sieve = file:~/sieve;active=~/.dovecot.sieve
sieve_before = /etc/dovecot/sieve/before.d/
sieve_default = /etc/dovecot/sieve/default.sieve
}
Remove the sieve_default reference if you are moving spam sorting to sieve_before, or keep both if the default script contains other rules. The spam sorting should be in before.d rather than the default script so it always runs.
IMAPSieve: teaching Rspamd from folder moves
Rspamd’s Bayesian filter learns from examples. Every time a user moves a message from Junk back to their inbox, they are saying: this was not spam, and Rspamd should learn from it. Every time they move a message into Junk, they are saying: this is spam. Without a feedback mechanism, those signals never reach Rspamd.
IMAPSieve is a Dovecot plugin that fires Sieve scripts when users interact with IMAP folders. It can be configured to run a script when a message is moved into or out of the Junk folder, and that script can pipe the message to Rspamd for training.
Install the IMAPSieve plugin
sudo apt install dovecot-sieve
This is already installed from the Dovecot article. The IMAPSieve plugin is part of the dovecot-sieve package.
Configure IMAPSieve in Dovecot
Add to /etc/dovecot/conf.d/90-sieve.conf:
plugin {
sieve = file:~/sieve;active=~/.dovecot.sieve
sieve_before = /etc/dovecot/sieve/before.d/
sieve_default = /etc/dovecot/sieve/default.sieve
# IMAPSieve for spam training
sieve_plugins = sieve_imapsieve sieve_extprograms
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
# When a message is moved INTO Junk: learn as spam
imapsieve_mailbox1_name = Junk
imapsieve_mailbox1_causes = COPY
imapsieve_mailbox1_before = file:/etc/dovecot/sieve/learn-spam.sieve
# When a message is moved OUT OF Junk: learn as ham
imapsieve_mailbox2_name = *
imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:/etc/dovecot/sieve/learn-ham.sieve
}
Create the training scripts
Create /etc/dovecot/sieve/learn-spam.sieve:
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
pipe :copy "rspamd-learn-spam.sh";
Create /etc/dovecot/sieve/learn-ham.sieve:
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
pipe :copy "rspamd-learn-ham.sh";
Compile both:
sudo sievec /etc/dovecot/sieve/learn-spam.sieve
sudo sievec /etc/dovecot/sieve/learn-ham.sieve
Create the shell scripts
Create /usr/lib/dovecot/sieve-pipe/rspamd-learn-spam.sh:
#!/bin/bash
exec /usr/bin/rspamc learn_spam
Create /usr/lib/dovecot/sieve-pipe/rspamd-learn-ham.sh:
#!/bin/bash
exec /usr/bin/rspamc learn_ham
Make both executable:
sudo chmod +x /usr/lib/dovecot/sieve-pipe/rspamd-learn-spam.sh
sudo chmod +x /usr/lib/dovecot/sieve-pipe/rspamd-learn-ham.sh
The scripts live in /usr/lib/dovecot/sieve-pipe/ which is the default directory Dovecot looks in for pipe targets. The rspamc binary communicates with the Rspamd controller to submit training data.
Configure the sieve-pipe directory
Add to /etc/dovecot/conf.d/90-plugin.conf or the relevant plugin config:
plugin {
sieve_pipe_bin_dir = /usr/lib/dovecot/sieve-pipe
}
Restarting and testing
Restart Dovecot to apply all the configuration changes:
sudo systemctl restart dovecot
sudo journalctl -u dovecot -n 30
Test the spam sorting by sending a test message with the X-Spam-Flag: YES header manually and confirming it arrives in the Junk folder rather than the inbox:
echo "Subject: Test spam
X-Spam-Flag: YES
From: test@test.invalid
To: you@yourdomain.com
This is a test." | sudo -u vmail sendmail you@yourdomain.com
Check the Dovecot delivery log to confirm the Sieve script fired:
sudo journalctl -u dovecot | grep -i sieve
A successful delivery via Sieve shows lines like:
dovecot: lmtp(you@yourdomain.com): sieve: msgid=<...>: fileinto action: stored mail into mailbox 'Junk'
Common personal filter patterns
Once the global scripts are in place, users can write their own personal scripts via ManageSieve. A few patterns that come up most often:
Mailing list to a folder:
require ["fileinto", "mailbox"];
if header :contains "List-Id" "debian-announce" {
fileinto :create "Lists/Debian";
stop;
}
Sender to a folder:
require ["fileinto", "mailbox"];
if address :is "from" "someone@example.com" {
fileinto :create "Important";
stop;
}
Flag a message:
require ["imap4flags"];
if header :contains "Subject" "URGENT" {
setflag "\Flagged";
}
Vacation auto-reply:
require ["vacation"];
vacation
:days 7
:subject "Out of office"
"I am away until 20 May. I will reply on my return.";
The vacation extension sends one auto-reply per sender per seven days, preventing reply storms when someone sends multiple messages during the absence period.
Maintaining scripts
After editing any .sieve file, recompile it:
sudo sievec /path/to/script.sieve
And restart Dovecot if the script is a global one. User scripts managed via ManageSieve are compiled automatically when the user saves them.
To check the syntax of a script without compiling it:
sudo sievec -c /path/to/script.sieve
This is useful before deploying a new global script to confirm it is syntactically valid.