Overview
Every message that reaches Stalwart over SMTP arrives in a session. A session is one connection from a client, and the client can be another mail server delivering mail to the local domains or a user’s mail client submitting mail for sending. During the session, the server decides step by step whether to accept what the client sends. These decisions keep spam out, stop the server from relaying mail for clients that may not use it, and tell the client, through the reply code, why a command was refused.
The session progresses through stages, and each stage corresponds to a command of the protocol. Each stage is configured by its own object, so that different rules can apply at each point of the conversation:
| Stage | Command | Configured on | Decides |
|---|---|---|---|
| Connect | (connection) | MtaStageConnect | The greeting and host name; whether to accept the client. |
| EHLO | EHLO, HELO, LHLO | MtaStageEhlo, MtaExtensions | Whether the client’s name is acceptable; which extensions are offered. |
| AUTH | AUTH | MtaStageAuth | Whether and how the client may authenticate. |
| MAIL FROM | MAIL FROM | MtaStageMail | Whether the sender is accepted; sender rewriting. |
| RCPT TO | RCPT TO | MtaStageRcpt | Whether each recipient is accepted; relaying; recipient rewriting. |
| DATA | DATA, BDAT | MtaStageData | Whether the message is accepted, after authentication checks, filtering and signing. |
Most settings of these objects are expressions rather than fixed values. Stalwart evaluates them for each session, with variables that describe it: the listener, the client’s IP address and the authenticated account, and, from the stage where they become known, the sender and the recipients. Because of this, a single setting can behave differently on port 25 and on the submission ports, for a particular network, or for authenticated users.
The stage objects are not the only way to act on a session. At every stage except AUTH, a system Sieve script, milters and MTA hooks can inspect the command and reject it. Separately, throttles limit the rate of connections, senders and recipients.
Session limits
Section titled “Session limits”Some limits apply to the session as a whole rather than to one command. They stop a client from holding a connection open indefinitely or from sending an unbounded amount of data. These limits are fields of the MtaInboundSession singleton (found in the WebUI under Settings › MTA › Inbound › Session). Each is an expression, so it can depend on the listener or the client:
| Field | Default | Limit | Reply when exceeded |
|---|---|---|---|
idleTimeout | 5 minutes | Time the server waits for the next command. | 221 2.0.0 <host> Disconnecting inactive client. |
maxDuration | 10 minutes | Total duration of the session. | 421 4.3.2 <host> Session open for too long. |
transferLimit | 250 MiB | Bytes the client may send in the session. | 452 4.7.28 <host> Session exceeded transfer quota. |
Because the limits are expressions, they can be tighter for unknown servers than for the server’s own users. The following object body gives clients on port 25 one minute between commands and authenticated clients ten:
{ "idleTimeout": { "match": {"0": {"if": "local_port == 25", "then": "1m"}}, "else": "10m" }}Other limits concern a single message rather than the whole session. The size of a message is limited at the DATA stage, which also limits the number of messages per session. The number of recipients is limited at the RCPT TO stage.
Memory budget
Section titled “Memory budget”Stalwart buffers incoming messages in memory, and it limits the memory used for this buffering. The limit is maxBufferedMemory on the SystemSettings singleton. When the setting is not set, the budget is one quarter of the memory available to the server, and a container’s memory limit is taken into account.
When the budget is exhausted, the server defers new messages instead of accepting them. MAIL FROM, DATA and BDAT are answered with 452 4.3.1 Insufficient system storage, try again later., and the event limit.memory-budget is logged as a warning. Sending servers retry later, so no mail is lost. A server that logs this event frequently, however, has too little memory for its load. Sizing memory describes the budget and how to size it.
Rejections and deferrals
Section titled “Rejections and deferrals”When Stalwart refuses a command, the reply code tells the client how to proceed. A 5xx code means that the refusal is permanent, so the client must not try again. A 4xx code means that it is temporary, and the client should retry later. The following table lists the replies most often seen in logs and bounce messages, with the check that produces each of them:
| Reply | Stage | Cause |
|---|---|---|
550 5.5.0 Invalid EHLO domain. | EHLO | The EHLO name is not a fully qualified domain name and rejectNonFqdn is enabled (the default on port 25). |
503 5.5.1 Polite people say EHLO first. | MAIL FROM | The client sent MAIL FROM without EHLO. |
503 5.5.1 You must authenticate first. | MAIL FROM | The port requires authentication (every port except 25 by default). |
535 5.7.8 Authentication credentials invalid. | AUTH | Wrong credentials, or an account whose domain is served by another directory. |
550 5.7.1 Your account is not authorized to use this service. | AUTH | The account may not send mail, or the access token lacks the mail scope (AUTH stage). |
501 5.5.4 You are not allowed to send from this address. | MAIL FROM | The sender address does not belong to the authenticated account (mustMatchSender). |
550 5.7.1 Sender address not allowed. | MAIL FROM | The sender is refused by isSenderAllowed. |
550 5.7.23 SPF validation failed, status: <result>. | EHLO, MAIL FROM | Strict SPF verification failed. 451 4.7.24 for a temporary DNS error. |
550 5.7.25 Reverse DNS validation failed. | MAIL FROM | Strict reverse DNS verification failed. 451 4.7.25 for a temporary error. |
550 5.1.1 Mailbox does not exist. | RCPT TO | The recipient is not a local address. |
550 5.7.1 Relay not allowed. | RCPT TO | The recipient is in a domain that is not local, and the session may not relay (RCPT TO). |
452 4.2.2 Greylisted, please try again in a few moments. | RCPT TO | Greylisting: the first attempt from this sender to this recipient. |
452 4.4.5 Rate limit exceeded, try again later. | MAIL FROM, RCPT TO | A throttle was exceeded. |
455 4.5.3 Too many recipients. | RCPT TO | The message has more recipients than maxRecipients. |
552 5.3.4 Message too big for system. | MAIL FROM, DATA | The message exceeds the maximum message size. |
552 5.3.4 Message header too large. | DATA | The header section exceeds the header limits. |
550 5.7.20 No passing DKIM signatures found. | DATA | Strict DKIM verification found no valid signature. 451 4.7.20 for a temporary error. |
550 5.7.29 ARC validation failed. | DATA | Strict ARC verification failed. 451 4.7.29 for a temporary error. |
550 5.7.7 Failed to parse message. | DATA | The message could not be parsed. |
550 5.7.1 DSN rejected due to DKIM2 verification failure. | DATA | A delivery status notification signed with DKIM2 fails verification or does not concern a message this server sent. 451 4.7.5 Unable to verify DKIM2 DSN, try again later. for a temporary DNS error when dkimVerify is strict. |
451 4.7.5 Unable to verify DKIM2 signatures, try again later. | DATA | The DKIM2 chain could not be verified because of a temporary DNS error, dkimVerify is strict, and the message has a recipient on another system and would be signed with DKIM2. |
550 5.7.1 Email rejected per DMARC policy. | DATA | DMARC failed and the domain’s policy is reject. 451 4.7.1 for a temporary error. |
550 5.7.1 Multiple author domains in From header are not allowed. | DATA | The From header holds addresses in several domains (DMARC). |
550 5.7.1 Message rejected due to excessive spam score. | DATA | The spam filter score reached the reject threshold, or a reject tag matched. |
450 4.4.6 Too many Received headers. Possible loop detected. | DATA | The message has passed through more servers than maxReceivedHeaders allows. |
452 4.3.1 Mail system full, try again later. | DATA | A queue quota is exhausted. |
452 4.3.1 Insufficient system storage, try again later. | MAIL FROM, DATA | The memory budget is exhausted. |
503 5.5.3 <text> | any | A Sieve script rejected the command with a text that does not start with a reply code (Trusted interpreter). |
Each rejection is also logged as an event, with the client’s address and the reason. Many of these events are logged at the debug level, among them failed authentication (auth.failed), refused senders (smtp.mail-from-unauthorized, smtp.mail-from-not-allowed), spam filter rejections and Sieve rejections. A tracer at the default info level therefore does not show them. To investigate a rejection, raise the level of the tracer as described in Telemetry.