A good addition to the spam detection mechanisms of rspamd is to have spam and ham learned when users are copying messages in and out of their spam folder when using their IMAP mail clients. When a message is moved into the spam folder, it is learned as spam, when it is moved out, it is learned as ham.
For this purpose there is a dovecot plugin, imap_sieve, that applies sieve filtering during imap actions like copying messages.
If you look around on the web you can find quite some well written blog posts describing how to do this. However, as this is not a completely new topic, a lot of the instructions you can find on the web do not take into account that Dovecot had massive changes in it’s configuration syntax when version 2.4.0 was released. So most of the dovecot configuration steps in the tutorials are not valid anymore.
Fortunatelly, the dovecot documentation itself has a quite helpful chapter on Spam Reporting (https://doc.dovecot.org/latest/core/config/spam_reporting.html). The section “Local Reporting” contains the needed dovecot configuration and scripts for spam/ham learning.
So I will document here, how I have set up the ham and spam learning using the latest Dovecot version available with Ubuntu/Debian.
First of all, both plugins imap_sieve and sieve_imapsieve (https://doc.dovecot.org/2.4.2/core/plugins/imap_sieve.html) need to be activated. The fist plugin adds the IMAPSIEVE capability to the IMAP service which allows the configuration of Sieve scripts triggered via IMAP events. The latter plugin adds the specific imapsieve language extension to the Sieve engine. This extension can be activated by the loading it with require [“imapsieve”]; within Sieve scripts.
In Ubuntu/Debian the configuration directory for dovecot is /etc/dovecot/conf.d/. The conf-files in this directory already include most of the configuration parameters that need to be set. Most of the time they simply need to be commented in and their values might have to be adjusted.
imap_sieve is activated in 20-imap.conf. The mail_plugins section of “protocol imap” needs to get an “imap_sieve = yes” entry like below:
protocol imap {
mail_plugins {
imap_sieve = yes
}
}
The imap_sieve plugin only activates sieve filtering triggered by IMAP operations. It is independent of the the sieve plugin for LMTP and LDA so there would be no need to activate sieve for these protocols in case you don’t want to use sieve filtering upon mail reception.
The sieve_imapsieve plugin is activated in 90-sieve.conf by adding it to the sieve_plugins. While at it, the sieve_extprograms plugin has to be added as well, it is needed for the pipe extension and also brings some extensions to the standard environment extension:
sieve_plugins {
sieve_imapsieve = yes
sieve_extprograms = yes
}
The pipe extension will be used in the Sieve scripts to pipe messages to a shell script that trains rspamd. As already mentioned above, the dovecot specific environment extensions are enabled as well. The extensions needs to be activated implicitly and should be activated only within global scripts stictly limited for administrator use:
sieve_global_extensions {
vnd.dovecot.pipe = yes
vnd.dovecot.environment = yes
}
Now the actions for moving a message into the spam folder or moving a message out of the spam-folder have to be defined. For this to work a few things have to be checked. First of all you need to find out the name of your Spam/Junk folder. This is not always the name that is displayed in your email client. A good way to find out is to search the dovecot configuration files for the term “special_use = \Junk” which identifies the mailbox name used for spam mail. In my case this is a mailbox with the name “Junk” as defined in 15-mailboxes.conf.
mailbox Junk {
# From elsewhere to Spam folder
sieve_script report-spam {
type = before
cause = copy
path = /etc/dovecot/sieve/report-spam.sieve
}
}
# From Spam folder to elsewhere
imapsieve_from Junk {
sieve_script report-ham {
type = before
cause = copy
path = /etc/dovecot/sieve/report-ham.sieve
}
}
The fist block of above section defines that whenever a message is copied into the mailbox Junk, the report-spam.sieve script is called. The second one defines that whenever a message is copied out of the mailbox Junk to any other mailbox, the report-ham.sieve script is called. Both scripts are executed before any user scripts are executed.
One last thing and the configuration for dovecot is done. For the pipe extension it is necessary to define the directory from where shell scripts are allowed to be executed. This is defined in 90-sieve-extprograms.conf:
sieve_pipe_bin_dir = /etc/dovecot/sieve
Now to the Sieve scripts. The report-spam.sieve script looks as follows:
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
# rspamd does not differentiate users when learning spam and ham
# but for other filters the username might be comming in handy
if environment :matches "imap.user" "*" {
set "username" "${1}";
}
# "learn-spam.sh" MUST live in sieve_pipe_bin_dir directory (in my case /etc/dovecot/sieve)
pipe :copy "learn-spam.sh"
As mentioned in the Sieve script’s comment, rspamd does not need the current user’s name but other spam filters might train on a per user basis by default.
The report-ham.sieve script looks as follows:
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
# which mailbox are we copying to?
if environment :matches "imap.mailbox" "*" {
set "mailbox" "${1}";
}
# if it is the Trash mailbox, we don't consider the message worth to
# be learned as ham.
if string "${mailbox}" "Trash" {
stop;
}
# rspamd does not differentiate users when learning spam and ham
# but for other spam filters the username might be coming in handy
if environment :matches "imap.user" "*" {
set "username" "${1}";
}
# "learn-ham.sh" MUST live in sieve_pipe_bin_dir directory (in my case /etc/dovecot/sieve)
pipe :copy "learn-ham.sh";
The only differenct to the process of learning spam is that it would be probably counterproductive to learn a message as ham when it is copied from the Spam mailbox to the Trash mailbox, so the script is stopped at this point.
The shell scripts learn-spam.sh and learn-ham.sh are then simple one-liners.
learn-spam.sh:
#!/bin/sh
exec /usr/bin/rspamc learn_spam
learn-ham.sh:
#!/bin/sh
exec /usr/bin/rspamc learn_ham
In my case rspamd is on the same host as dovecot and the localhost IP is trusted so I can have an unauthorized connection. If rspamd is not on the same host as dovecot rspamc needs to know the host address (option -h) and a password (option -P). Also don’t forget to make both shell scripts executable.
Now it’s time to activate the dovecot configuration. I did not use a reload as I read somewhere that this is not enough, for whatever reason.
# systemctl stop dovecot
# systemctl start dovecot
If dovecot does not start as expected, systemctl status dovecot will help you investigate the reason why.
Once the new configuration is in place it is now possible to compile the new sieve scripts:
# sievec report-spam.sieve
# sievec report-ham.sieve
That’s it. rspamd should now learn spam and ham once messages are copied back and forth between the Spam folder and all other folders in your mail account.
Rspamd creates log entries for each message learned so you can verify that your setup is working correctly.
Here is an entry for learning ham:
2026-07-01 23:07:58 #1419(controller) <c37d1f>; csession; rspamd_controller_learn_fin_task: <127.0.0.1> learned message as spam: <here is the message ID>
And this is an entry for learning spam:
2026-07-01 23:23:56 #1419(controller) <474b1e>; csession; rspamd_controller_learn_fin_task: <127.0.0.1> learned message as spam: <here is the message ID>