Would https://doc.dovecot.org/latest/core/config/spam_reporting.html be of any help?
Aki
On 09/08/2026 12:32 EEST sjh_dovecot_org--- via dovecot <dovecot@dovecot.org> wrote:
Hi Dovecot experts...
I've recently started what I expected to be a tiny project to migrate from a legacy ad-hoc procmail by embracing Sieve scripting.
I have two different frustrations - depending upon which approach I adopt to trigger spam-training actions.
*Option 1*: Trigger actions when messages are moved to/from Spam folder.
This mostly works... but has has a niggle I can't see how to overcome.
I don't have only one 'Spam' folder - I have a hierarchy of folders - with names like "Spam.2026.July" and "Spam.Unsubscribed.2023". My 'learn ham' sieve script uses "imap.mailbox" from environment to determine if the email is being moved out of the Spam hierarchy. This works well. Unfortunately, when triggering 'learn spam' - the value of imap.mailbox is always the destination mailbox 'Spam' - not the source mailbox from which the spammy email has been moved. I can't see any way to trigger a spam reporting action, within Sieve scripts, if (and only if) a message is moved from a folder that does not have a name matching 'Spam.*' into the folder 'Spam'. This restriction is required so that adjustments to filed spam messages do not result in a single email being wrongly presented multiple times for learning as a spam example. This multiple reporting might otherwise happen if, for example, a message was moved between 'Spam' and 'Spam.2025.July" ; "Spam.2025.August" and "Spam.Fo rR eview.2025" several times.
Have I overlooked a reliable way to determine the source folder, in a Sieve script, when a message is moved (copied) to the Spam folder?
*Option 2*: Trigger actions when the Thunderbird 'Junk' and 'Not Junk' buttons are pressed
I accept it would be preferable to avoid depending upon client-specific flags... but I do expect Thunderbird to be the mail client... and marking messages as Junk/Spam in Thunderbird does promise a neat user interface experience.
I've read RFC 6785 (also RFC 5228 and RFC 5232). I understand that the "imap.changedflags" environment value is set both when flags have been added and when removed... and that it is expected that hasflag should be used to determine if the imap.changedflag flag was added or removed. This makes perfect sense, in principle... but the actual behaviour I see is peculiar. With a sieve script /var/dovecot-sieve/scripts/flags_changed.sieve (compiled with sievec) like this:
require ["environment", "imap4flags", "imapsieve", "variables", "vnd.dovecot.debug"]; if header :matches "subject" "*" { debug_log "FLAG-EMAIL Subject: ${1}"; } if environment :matches "imap.changedflags" "*" { debug_log "FLAG-EMAIL changedflags : ${1}"; } if hasflag :matches "*" { debug_log "FLAG-EMAIL hasflag : ${1}"; }and configuration in /etc/dovecot/conf.d/90-sieve.conf like this (N.B. user sieve script does not interact with flags... type=after and type=before behave similarly):
sieve_script flags_changed { type = after cause = flag path = /var/dovecot-sieve/scripts/flags_changed.sieve }I experimented with the "Junk" and "Not Junk" buttons for an email with a distinctive subject. The following nested bullet-list summarises the values I found for various scenarios. My experiment followed this order of scenarios - there should have been no flag changes, for the email in question, between each of the scenarios in the test sequence.
- Scenario 1 - Fresh, not-deterimed-to-be-spam email arrives in Inbox...
- Sieve script invocation 1
- imap.changedflags => NonJunk
- hasflag => NonJunk
- Notes: As expected.
- Scenario 2 - Same email is marked as read by Thunderbird after it has been displayed onscreen for a while...
- Sieve script invocation 2
- imap.changedflags => \Seen
- hasflag => \seen
- Notes: Flags are case insensitive - but odd lower case distinction. What happened to NonJunk flag from Scenario 1?
- Scenario 3 - Junk button pressed to manually mark this email as spam...
- Sieve script invocation 3
- imap.changedflags => Junk
- hasflag => \seen
- Sieve script invocation 4
- imap.changedflags => NonJunk
- hasflag => \seen
- Notes: Why neither Junk nor NonJunk flags set for hasflag?
- Scenario 4 - "Not Junk" button is pressed to manually mark this email as ham...
- Sieve script invocation 5
- imap.changedflags => NonJunk
- hasflag => \seen
- Sieve script invocation 6
- imap.changedflags => Junk
- hasflag => \seen
- Sieve script invocation 7
- imap.changedflags => \Seen
- hasflag => NonJunk
Perhaps it is relevant... where I have captured the value from hasflag... that only one token is ever reported - even where I'd expect multiple flags present. I would have expected at least to see both 'Junk' and 'NotJunk' alongside '\seen' for hasflag at appropriate stages in the sequence of scenarios.
Is there a problem with how I've used the :match idiom (with "*") intending to capture all the flags associated with the message? (If so, what approach should I have used instead?)
Is there a bug which prevents using the hasflag feature to disambiguate between a flag reported for imap.changedflags having been added versus having been removed? If not, where have I gone wrong?
Versions...
Server-side: Debian Trixie: dovecot-core/stable,stable-security,now 1:2.4.1+dfsg1-6+deb13u6 amd64 [installed] dovecot-imapd/stable,stable-security,now 1:2.4.1+dfsg1-6+deb13u6 amd64 [installed] dovecot-sieve/stable,stable-security,now 1:2.4.1+dfsg1-6+deb13u6 amd64 [installed]
Client-side: Windows 11 Thunderbird 140.12.1esr (64-bit)
Steve
NonJunk \seen 5 Junk \seen 6 \Seen NonJunk 7 Weird... Is NonJunk reported for hasflag because it is now the only flag associated with the message (after \Seen flag removed)?
dovecot mailing list -- dovecot@dovecot.org To unsubscribe send an email to dovecot-leave@dovecot.org