Issue Description
A user Sieve script that files a message into the Inbox is overridden by the spam filter, while filing into any other mailbox is honoured. The cause is in crates/email/src/message/ingest.rs:
if is_spam && params.mailbox_ids == [INBOX_ID] {
params.mailbox_ids[0] = JUNK_ID;
params.keywords.push(Keyword::Junk);
}
The user’s script runs before this and sets mailbox_ids. If it filed into the Inbox, explicitly with fileinto “INBOX” or implicitly with keep, the destination equals [INBOX_ID] and the message is moved to Junk anyway. Discussion #2637 states that using fileinto on a junk email tells Stalwart to override the classification, but the Inbox is the one folder where that override is impossible, and it is the folder an allow-list rule needs.
I am creating a custom iOS and macOS email app for Stalwart and Fastmail since both support JMAP. I added a feature where a user can right-click on a message and select:
- Add to Allowed Senders
- Add to Blocked Senders
Then, it adds the email address to a custom rule on the server that essentially says, If the email is FROM: test@example(.)org then:
- Keep in Inbox ← this is for allowed senders list
- Mark as read and move to Junk Mail ← this is for the blocked senders list
Of course the blocked senders rule works. However, if a message is flagged as spam when it arrives, the rule about adding them to the Allowed Senders list and keeping the message in the Inbox folder is never applied.
It would be super helpful if you could fix this, as some users may not want to login to their Stalwart and see lots of contacts with only email addresses saved.
Expected Behavior
message delivered to the Inbox, since the user’s script filed it there explicitly.
Actual Behavior
message delivered to Junk Mail with the $junk keyword. Other actions in the same script, such as addflag, are applied, so the script did run.
Reproduction Steps
Set the spam score threshold low enough that a test message classifies as spam.
As a user, activate a Sieve script: require [“fileinto”]; if address :is “from” “[email protected]” { fileinto “INBOX”; }
Send a message from that sender that scores above the threshold.
Stalwart Version
v0.16.x
Installation Method
Docker
Database Backend
RocksDB
Blob Storage
RocksDB
Search Engine
Internal
Directory Backend
Internal
Additional Context
Suggested fix:
Distinguish an explicit fileinto “INBOX” from the implicit default. For example, only redirect to Junk when the Sieve script produced no explicit fileinto, or have the Sieve ingest set a flag when the destination was chosen by the script.
The address book exemption (trust contacts) works as documented and is a reasonable workaround, but it is not equivalent to a user rule: it cannot express domains or patterns, and it requires every allowed sender to become a contact.
I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.
on
I have searched this support forum (open and closed topics) and confirm this is not a duplicate.
on
I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.
on