Disable global Junk filing while keeping per-mailbox Sieve spam thresholds

Your question

Hi,

We are using Stalwart 0.15.5 and want to support per-mailbox spam thresholds.

Goal

  • Keep global spam scoring enabled
  • Keep spam headers enabled
  • Keep global reject at score 15
  • Let each mailbox decide when to move mail to Junk

Current server config

  • spam-filter.score.spam = “15.0”
  • spam-filter.score.reject = “15.0”
  • spam-filter.score.discard = “0.0”
  • spam-filter.header.status.enable = true
  • spam-filter.header.result.enable = true

Mailbox Sieve example
require [“fileinto”, “vnd.stalwart.expressions”];

if test eval “score >= 6” {
fileinto “Junk”;
stop;
}

Expected behavior

  • score >= 15: reject
  • score >= mailbox threshold and < 15: move to Junk by Sieve
  • score < mailbox threshold: deliver normally
  • spam headers are still added

This setup seems to work correctly in production.

Questions

  1. Is setting spam-filter.score.spam to the same value as spam-filter.score.reject the right way to stop global Junk placement while keeping scoring and headers?
  2. Does spam-filter.score.spam control automatic Junk placement in 0.15.5?
  3. Is there a separate setting to keep spam scoring/headers but disable automatic Junk filing?
  4. Does raising spam-filter.score.spam affect the Sieve score variable, or does Sieve still see the raw spam score?
  5. For Stalwart 0.16, is the equivalent setting SpamSettings.scoreSpam?

The question is specifically about 0.15.5 behaviour, which falls under the legacy docs. The user is also asking forward-looking questions about the current version’s scoreSpam property, which I can partially ground. Let me answer from what I’ve found.


The questions span both the legacy 0.15 config format and the current version. Here’s what the source code confirms for each:


1 & 3. Is setting spam-filter.score.spam equal to spam-filter.score.reject the right way to suppress global Junk filing?

In the current server code, spam_threshold (mapped from scoreSpam / spam-filter.score.spam) is the value checked to decide whether a message is classified as spam, which is what triggers automatic Junk filing. Raising it to match the reject threshold means no message will be classified as spam before being rejected — so automatic Junk filing is effectively bypassed. Your approach is correct in principle.

There is no separate “disable automatic Junk filing” toggle distinct from spam-filter.score.spam; the threshold is the only gate.


2. Does spam-filter.score.spam control automatic Junk placement?

Yes. The legacy 0.15 docs describe it as “the score threshold for marking messages as spam” — crossing it is what triggers the Junk classification action. Your per-mailbox Sieve script then independently decides what to do with messages that are scored but not yet rejected.


4. Does raising spam-filter.score.spam affect the Sieve score variable?

No. The raw cumulative spam score is computed before the threshold comparison, and the threshold only controls the classification outcome. Sieve scripts receive the raw score regardless of what spam-filter.score.spam is set to.


5. Is the current-version equivalent scoreSpam?

Yes — the server source confirms that scoreSpam is the current property name (part of the SpamSettings struct), and it maps directly to spam_threshold internally. Your approach of setting it to the same value as the reject threshold carries over to the current version.


:warning: Note: Your config is using 0.15.5 TOML-style keys. These are legacy; the current version uses JMAP-style object configuration via the WebUI or stalwart-cli. If you upgrade, use scoreSpam in the SpamSettings object rather than spam-filter.score.spam.

For reference on the current spam filter settings, the closest docs are at https://stalw.art/docs/spamfilter/settings (note: the 0.15-era page is at a different path).

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong. If you found it useful, mention @helpbot in any reply to ask follow-up questions and the bot will respond again.

Short answers:

  1. Setting spam-filter.score.spam = spam-filter.score.reject doesn’t prevent Junk filing; it only changes the threshold for the X-Spam-Status flag. The check that moves a message to Junk on local ingest is is_spam = score >= spam_threshold, where spam_threshold is exactly spam-filter.score.spam. So raising it does effectively disable global Junk filing for messages under reject. Pick a value above reject (say 999) if you want it unambiguously off, or use the dedicated setting in 2 below.

  2. In 0.15.5, the way the scoring affects Sieve is: the score variable in Sieve is the raw numeric spam score (whatever the analysis produced), not a thresholded boolean. Raising spam-filter.score.spam does NOT change the value the Sieve sees; it only changes the binary “is_spam” decision that drives the Junk move. So your mailbox-side Sieve that does if test eval "score >= 6" will continue to see whatever the analysis produced, regardless of the global threshold.

  3. There’s no separate “scoring on, junk filing off” toggle in 0.15.5. Raising the threshold is the lever.

  4. In 0.16 the equivalent path is Settings → Spam filter → Scores → Spam threshold (the property is SpamSettings.scoreSpam in the JMAP schema). The same shape applies: threshold drives the binary move-to-Junk decision; the Sieve score is independent.

The setup you describe (global reject at 15, mailbox Sieve files to Junk above its own threshold) is the supported pattern. Set the global Spam threshold to 999 (or anything above reject) and the local sieve script becomes the only thing that touches Junk.

Thank you, it’s clear :slight_smile: