Spamtest sieve function only returns binary spam or ham, no intermediate values

Issue Description

Stalwart supports the Sieve spamtest extension in user and system sieve scripts as defined in RFC 5235 (RFC 5235 - Sieve Email Filtering: Spamtest and Virustest Extensions).
According to the RFC, the spamtest function should return a value between 0 and 10, where:

  • 0 — message was not tested
  • 1 — tested and clear of spam
  • 2–9 — tested and may contain spam; higher numbers mean a greater likelihood of spam
  • 10 — tested and definitely contains spam

But stalwart only returns a binary

  • 1 - ham
  • 10 - spam
    scoring, which makes it impossible to react differently to different spam levels. Like “add a [SPAM] subject above score 2”, “move to Junk above score 5”, “reject above score 8”

Expected Behavior

spamtest should return the actual spam score as a number between 0 and 10 as defined in the RFC.
I know this is not quite trivial to do, because stalwarts spam score can’t be mapped 1:1 to the spamscore values.
Maybe a simple solution would be to let the admin configure the spamscore behaviour by setting minimum stalwart score settings for each spamtest-step.

This way one could define, spamscore

  • < 2 = spamtest 1
  • < 2.5 = spamtest 2

Actual Behavior

spamtest only ever returns either 1 (ham) or 10 (spam), depending on whether the configured spam setting considers the message spam or not. The intermediate values 2–9 are never returned.

Stalwart Version

v0.16.x

Installation Method

Docker

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

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 reported behavior: Stalwart’s Sieve engine indeed sets a binary SpamStatus::Spam / SpamStatus::Ham value (in server/crates/smtp/src/inbound/data.rs and server/crates/email/src/sieve/ingest.rs), based only on whether the message crossed the spam threshold. There’s no intermediate scoring passed to the spamtest result in the current code, matching the report.

Confirmed: as currently implemented, Stalwart’s spamtest Sieve test only reports a binary result (1 for ham, 10 for spam), based on whether the message’s cumulative score crosses the scoreSpam threshold. It does not currently interpolate the RFC 5235 intermediate 2–9 range from the actual score, so your reading of the behavior is correct — this isn’t a config knob you’re missing.

For per-score branching today, the practical workaround is to test the message’s actual score rather than spamtest: Stalwart adds an X-Spam-Status header to every message containing Yes/No plus the final numeric score, and an X-Spam-Result header listing every triggered tag and its score. A Sieve script can use a header :matches (or regex) test against X-Spam-Status to extract the numeric score and drive graded actions (tag subject at 2, move to Junk at 5, reject at 8, etc.) instead of relying on spamtest.

Reference: Overview | Stalwart and Scores | Stalwart

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

The header is not available in system scripts. For user scripts that workaround works, but not in the context of system scripts.

I would need it in system scripts to be able to reject the message.

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