DATA stage
The DATA command initiates the data transfer phase of message delivery. Once the sender has successfully completed the MAIL FROM and RCPT TO commands, the DATA (or BDAT when chunking is used) command begins transmission of the message. After the message has been transmitted, the server responds with a status code indicating whether it was accepted or rejected.
DATA-stage behaviour is configured on the MtaStageData singleton (found in the WebUI under Settings › MTA › Session › DATA Stage).
Message filtering
Once a message has been submitted with DATA or BDAT, it is possible to run Sieve scripts, Milter filters, or MTA Hooks that accept, reject, or modify the message. When multiple filter types are configured, Stalwart executes Milter filters first, then Sieve scripts, and finally MTA Hooks.
Sieve
The script field selects the Sieve script to run for the DATA stage. Sieve can rewrite the message, add or remove headers, process each MIME part, and apply conditional actions using the full set of supported Sieve extensions.
For example, setting script to the expression "'data'" runs a Sieve script named data which can iterate over MIME parts and rewrite their contents:
require ["envelope", "variables", "replace", "mime", "foreverypart", "editheader", "extracttext"];
if envelope :domain :is "to" "foobar.net" {
set "counter" "a";
foreverypart {
if header :mime :contenttype "content-type" "text/html" {
extracttext :upper "text_content";
replace "${text_content}";
}
set :length "part_num" "${counter}";
addheader :last "X-Part-Number" "${part_num}";
set "counter" "${counter}a";
}
}
Milter
Milter filters are defined as MtaMilter objects. Each configured filter can inspect and potentially modify the message, adding, changing, or removing headers, altering the body, or rejecting the message outright. For details see the Milter section.
MTA Hooks
MTA Hooks provide an HTTP-based alternative to milter. They are defined as MtaHook objects and can inspect or modify messages through a JSON protocol. For details see the MTA Hooks section.
Spam filtering
Whether to run the spam filter on incoming messages is controlled by enableSpamFilter, an expression that returns a boolean. The default policy enables the filter for unauthenticated sessions (is_empty(authenticated_as)). To restrict filtering to a specific listener only:
{
"enableSpamFilter": {"else": "listener == 'smtp'"}
}
Message limits
Message limits prevent abusive use of a single session and protect against message loops. Relevant fields are maxMessages (maximum number of messages per SMTP session, default 10), maxMessageSize (maximum size of a single message in bytes, default 104857600, 100 MB), and maxReceivedHeaders (maximum number of Received headers allowed in a message, which helps prevent message loops, default 50):
{
"maxMessages": {"else": "10"},
"maxMessageSize": {"else": "104857600"},
"maxReceivedHeaders": {"else": "50"}
}
Headers
Headers automatically added to accepted messages are controlled by the following fields, each an expression returning a boolean unless noted:
addReceivedHeader: add aReceivedheader containing the client IP address and TLS information.addReceivedSpfHeader: add aReceived-SPFheader with SPF results.addAuthResultsHeader: add anAuthentication-Resultsheader with DMARC, DKIM, SPF, ARC, and iprev results.addMessageIdHeader: add aMessage-IDheader when the message lacks one.addDateHeader: add aDateheader when the message lacks one.addReturnPathHeader: add aReturn-Pathheader containing the address specified inMAIL FROM.addDeliveredToHeader: add aDelivered-Toheader containing the recipient address. This field is a plain boolean; defaulttrue.
The default policy for every expression-valued header is to add it only when local_port == 25. The following example adds Received, Received-SPF, and Authentication-Results on the SMTP listener; lets submission listeners generate Message-ID and Date when absent; always adds Delivered-To; and never adds Return-Path:
{
"addReceivedHeader": {
"match": [{"if": "listener == 'smtp'", "then": "true"}],
"else": "false"
},
"addReceivedSpfHeader": {
"match": [{"if": "listener == 'smtp'", "then": "true"}],
"else": "false"
},
"addAuthResultsHeader": {
"match": [{"if": "listener == 'smtp'", "then": "true"}],
"else": "false"
},
"addMessageIdHeader": {
"match": [{"if": "listener == 'smtp'", "then": "false"}],
"else": "true"
},
"addDateHeader": {
"match": [{"if": "listener == 'smtp'", "then": "false"}],
"else": "true"
},
"addReturnPathHeader": {"else": "false"},
"addDeliveredToHeader": true
}