Default global queue quota is enabled but never enforced because its match expression is empty

Issue Description

On first start Stalwart creates an MtaQueueQuota “Global queue quota” with enable=true, 100000 messages and 10737418240 bytes, but with an empty match expression. Quotas with an empty expression are skipped, so neither limit is enforced.

x:MtaQueueQuota/get returns for this object:
{“enable”: true, “description”: “Global queue quota”, “key”: {}, “match”: {“match”: {}, “else”: “”}, “messages”: 100000, “size”: 10737418240}

Code (v0.16.24):

  • crates/common/src/manager/defaults.rs L101-113: the quota is created with the match expression left at its default (empty).
  • crates/common/src/expr/if_block.rs L97: an expression with an empty “else” and no “match” entries compiles to an empty IfBlock.
  • crates/smtp/src/queue/quota.rs L130-177: limits are checked and quota accounting is recorded only when !quota.expr.is_empty() && eval_if(…). With an empty expression the function returns true without checking or recording anything.

Inbound and outbound throttles treat an empty expression the opposite way: they apply when the expression is empty or true (crates/smtp/src/core/throttle.rs L182, crates/smtp/src/queue/throttle.rs L30).

The documentation’s global quota example uses the same limits with “match”: {“else”: “true”}: Quotas | Stalwart

Expected Behavior

The enabled global queue quota created at setup enforces its message and size limits.

Actual Behavior

The quota is shown as enabled with its limits, but check_quota() skips it, so the queue has no global limit.

Reproduction Steps

  1. Start a new Stalwart v0.16.24 installation.
  2. Read the “Global queue quota” with x:MtaQueueQuota/get: enable is true, the limits are set, and match is {“match”: {}, “else”: “”}.
  3. Follow the expression through compile_expr() and check_quota(): the empty IfBlock bypasses both limits.

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

Additional Context

The quota is created only when no MtaQueueQuota object exists (defaults.rs L101), so installations that already hold it keep the empty expression unless existing objects are also covered.

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 matches what the docs show for the default global quota, and it confirms there’s a mismatch: the documented example explicitly sets "match": {"else": "true"}, but the quota auto-created on first start leaves match empty, which per the code path you traced is treated as “never enforced” rather than “always enforced” (unlike the throttle logic, which treats empty as “always true”). That’s a legitimate defaults bug.

As a workaround until this is addressed, you can fix any existing installation by updating the “Global queue quota” MtaQueueQuota object’s match field to {"else": "true"} via the WebUI (Settings → MTA → Rates & Quotas → Queue Quotas) or with MtaQueueQuota/set, which will make it enforce the 100,000 message / 10 GB limits as documented here: Quotas | Stalwart

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.25 (will be released today).