Skip to content

Quotas

Messages that cannot be delivered at once wait in the queue to be retried, and while they wait they occupy storage. Some events, however, make the queue grow without bound. A remote domain that stops accepting mail leaves every message addressed to it waiting, and a compromised account that sends in bulk adds messages faster than they can leave. Without limits, either can fill the queue and the disk.

A queue quota sets such a limit. It caps how many messages, or how many bytes, may wait in the queue at once, either overall or for one sender, recipient or domain. When a quota is full, Stalwart defers the new messages that would exceed it, and the clients retry later.

Each quota is an MtaQueueQuota object (found in the WebUI under Settings › MTA › Rates & Quotas › Queue Quotas). Defining one involves three choices.

The first choice is what the quota counts separately. Its key divides the queued messages into groups, by sender, senderDomain, rcpt or rcptDomain (MtaQueueQuotaKey), and the limit applies to each group on its own. With no key, one group covers the whole queue.

The second choice is the size of the limit. For each group, messages sets the number of messages allowed in the queue, and size their total size in bytes. A quota may set messages, size or both.

The third choice is which messages the quota applies to. match is an expression that restricts the quota to some messages, and enable decides whether the quota is active at all.

A new installation has one quota, which protects the whole queue. It is the following object, which caps the queue at 100000 messages and 10 GiB:

{
"messages": 100000,
"size": 10737418240
}

A key narrows the limit to one sender, recipient or domain at a time. The following quota allows at most 10 queued messages for any one recipient:

{
"key": {"rcpt": true},
"messages": 10
}

A key and a match together limit one kind of traffic. The following quota allows 900 messages and 7 MiB per recipient, only for messages from one sender domain:

{
"key": {"rcpt": true},
"match": {"else": "sender_domain == 'example.org'"},
"messages": 900,
"size": 7340032
}

The quotas are checked when a message is about to be queued, at the end of the DATA stage. A message that would exceed a quota is refused with 452 4.3.1 Mail system full, try again later. The refusal comes after the client has transferred the message. Stalwart logs the event queue.quota-exceeded, with the quota and the kind of group.

The 452 code is a temporary failure, so sending servers keep the message and retry. A full quota therefore delays mail without losing it, as long as the condition clears before the sender’s retry period ends.

Not every message is treated in this way. Messages sent by Sieve scripts, such as vacation replies, are dropped when a quota is full, and the event sieve.quota-exceeded is logged. Delivery status notifications and reports are not subject to the quotas at all.

A message counts towards the quotas from the moment it is queued. It stops counting once each of its recipients has been delivered, has failed permanently, or has expired.