Issue Description
In Stalwart Server v0.16.21, conditional IF/THEN branches in the match expression of an MtaOutboundThrottle appear to be ignored.
I have an outbound rate limiter configured with:
Description: sender: 300 messages per day
Key: Sender
Rate: 300 messages per 1 day
I need to exempt a high-volume journal mailbox from this sender-based throttle.
The match condition is configured as:
IF: contains_ignore_case(recipients, ‘[email protected]’)
THEN: false
ELSE: true
The configuration is successfully saved and is visible through stalwart-cli. I also performed a full Stalwart server restart after making the change.
Despite this, messages whose only recipient is [email protected] continue to be limited by this MtaOutboundThrottle.
The queued message clearly contains:
to = [“[email protected]”]
but the throttle is still applied.
The behavior suggests that the conditional IF/THEN portion of the MtaOutboundThrottle match expression is not being evaluated and that only the default/ELSE expression may be used.
Expected Behavior
The complete match expression configured on an MtaOutboundThrottle should be evaluated.
For this configuration:
IF contains_ignore_case(recipients, ‘[email protected]’)
THEN false
ELSE true
a message with [email protected] in the recipients array should evaluate the throttle match expression to false.
Therefore, the sender rate limiter should not apply to that message.
Messages to other recipients should continue to match the ELSE true branch and remain subject to the configured sender rate limit.
Actual Behavior
Messages addressed only to [email protected] continue to be subject to the MtaOutboundThrottle.
The recipient is present in the queued message as:
to = [“[email protected]”]
but Stalwart still reports:
queue.rate-limit-exceeded
delivery.rate-limit-exceeded
for the same MtaOutboundThrottle.
This continues after:
- Saving the modified throttle.
- Verifying the saved object using stalwart-cli.
- Fully restarting the Stalwart server.
The CLI shows the expected condition:
Match condition:
Expression
Conditions:
# Condition Result
0 contains_ignore_case(recipients, '[email protected]') false
Default Value: true
However, at delivery time the throttle still matches the message.
Reproduction Steps
-
Run Stalwart Server v0.16.21.
-
Create an outbound rate limiter with:
- Key: Sender
- Rate limit: 300 messages per 1 day
- Match condition: true
-
Cause a sender to reach the configured rate limit.
In my case, journal messages have an empty envelope sender:
from = “<>”
and therefore share the same sender rate-limit bucket.
-
Modify the outbound rate limiter match condition to:
IF: contains_ignore_case(recipients, ‘[email protected]’)
THEN: false
ELSE: true -
Save the throttle.
-
Verify the stored configuration:
stalwart-cli get MtaOutboundThrottle
The output shows:
Outbound Rate Limiter
Description: sender: 300 messages per day
Keys: Sender
Rate limit:
Rate
Count: 300
Period: 1 d
Match condition:
Expression
Conditions:
# Condition Result
0 contains_ignore_case(recipients, '[email protected]') false
Default Value: true
Enabled: Yes
-
Restart the Stalwart server to ensure the updated configuration has been loaded.
-
Retry or enqueue a message where:
from = “<>”
to = [“[email protected]”] -
Observe that the message is still rejected/deferred by the same MtaOutboundThrottle with:
queue.rate-limit-exceeded
delivery.rate-limit-exceeded
even though the match expression should evaluate to false for that recipient.
Relevant Log Output
2026-09-09T23:30:25Z INFO Rate limit exceeded (queue.rate-limit-exceeded) queueId = 328594213249564160, queueName = "local", from = "<>", to = ["[email protected]"], size = 13021, total = 1, id = "MtaOutboundThrottle with id itupxlq1abqa", limit = [300, 86400000ms]
2026-09-09T23:30:25Z WARN Rate limit exceeded (delivery.rate-limit-exceeded) queueId = 328594213249564160, queueName = "local", from = "<>", to = ["[email protected]"], size = 13021, total = 1, id = "MtaOutboundThrottle with id itupxlq1abqa", nextRetry = 2026-09-10T00:00:00Z
2026-09-09T23:35:34Z WARN Rate limit exceeded (delivery.rate-limit-exceeded) queueId = 328593976512555520, queueName = "local", from = "<>", to = ["[email protected]"], size = 13010, total = 1, id = "MtaOutboundThrottle with id itupxlq1abqa", nextRetry = 2026-09-10T00:00:00Z
Stalwart Version
v0.16.x
Installation Method
Binary (Linux)
Database Backend
PostgreSQL
Blob Storage
S3-compatible
Search Engine
PostgreSQL
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