User Sieve fileinto "INBOX" cannot override spam classification; only non-Inbox folders can

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:

  1. Add to Allowed Senders
  2. 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:

  1. Keep in Inbox ← this is for allowed senders list
  2. 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

This confirms the docs only describe two deterministic trust mechanisms (address book match and sent-thread reply), and there’s no documented way for a Sieve fileinto "INBOX" to override spam classification independently of those. That matches the behavior you describe as a bug/limitation, not a documented feature.

Given that this is a specific behavioral/code-level report rather than a documentation gap, and no doc or changelog entry addresses distinguishing explicit vs. implicit fileinto to Inbox for spam override, I can’t point you to an authoritative fix or statement on this from current docs. Your analysis of ingest.rs and the suggested fix look sound as a bug report; a human maintainer will need to confirm whether this is intended behavior or a defect, since docs currently describe address-book and sent-thread trust as the only supported overrides, with no mention of Sieve-based override of Inbox delivery for spam-classified mail.

This isn’t covered in the current docs. A community member or staff member will follow up.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

Fixed in v0.16.22 (yet to be released).

Thank you! Users will be very happy when the app is finally released. It will be a very fast way to ensure future emails from the sender stay in the Inbox feature.