Skip to content

Mail delivery, spam and Sieve

How do I send all outgoing mail through a smart host?

Section titled “How do I send all outgoing mail through a smart host?”

Define a route that points at the smart host, then select that route for all remote mail. The route is of type Relay and holds the smart host’s host, port and, if required, credentials. It is selected in the route expression of the outbound strategy, which picks a route for each recipient, so make the expression return it for every domain that is not local. See Send all outgoing mail through a smart host and Relay delivery.

How do I route mail differently per listener or sender?

Section titled “How do I route mail differently per listener or sender?”

Test the variables that describe the message in the route expression of the outbound strategy. The server evaluates this expression for each recipient, and the variables available there include received_via_port, the port on which the message was received, and sender and sender_domain.

The listener variable of the inbound stages is not available at this point. To route by listener, test received_via_port instead. See Recipes.

For incoming mail, the SMTP reply names the check that failed, for example 550 5.7.23 SPF validation failed, status: <result>. or 550 5.7.1 Email rejected per DMARC policy. The log may say more, but many rejections are logged at the debug level, which a tracer at its default level of info does not write.

For outgoing mail, the queue and the delivery history show the last error that the remote server returned. See Rejections and deferrals and A message is not delivered.

The default signing rule covers only some mail. With the default dkimSignDomain expression, a message is signed only when the session is authenticated and the sender’s domain is hosted on the server. Mail relayed for other systems and mail submitted without authentication is therefore sent unsigned.

A domain also needs a usable key: only DKIM signatures whose stage is active are used for signing. Messages generated by user Sieve scripts, such as vacation replies, are signed with the account’s domain. See Signing domain.

How do I stop sending DMARC reports to my own domain?

Section titled “How do I stop sending DMARC reports to my own domain?”

Nothing needs to be configured. The server never sends reports about mail whose DMARC domain is hosted on the server, alias domains included.

The report frequency expressions are not suited to this purpose. They are evaluated for each received message, and in them rcpt_domain is the domain of the last accepted recipient, not the From domain. They are the place to turn reports off altogether: aggregate reports with the value disable in aggregateSendFrequency, and failure reports with false in failureSendFrequency. See Suppressing reports.

How does spam filtering work, and is Pyzor enabled?

Section titled “How does spam filtering work, and is Pyzor enabled?”

The filter scores each incoming message from several sources of evidence:

  • The authentication results, such as SPF, DKIM and DMARC.
  • DNS blocklists.
  • The filter’s rules.
  • A statistical classifier trained on the users’ decisions.
  • Optionally, a collaborative digest service and an AI model.

Pyzor is such a digest service: it compares a fingerprint of the message with those reported by other servers. It is supported but disabled until enable is set on the SpamPyzor object. See Spam filter and Pyzor.

Why is good mail marked as spam, and how is the classifier trained?

Section titled “Why is good mail marked as spam, and how is the classifier trained?”

The classifier learns from the users’ decisions. Moving a message into or out of the Junk Mail folder adds a training sample, and so does setting the $Junk or $NotJunk keyword. Deleting a message or moving it to Trash adds none.

The model is shared by all users, with a per-account component. It classifies nothing until it has 100 spam and 100 ham samples. To find out which tags produced a particular verdict, read the message’s X-Spam-Result header or run the ClassifySpam action on it. See Training by users and Investigating a verdict.

How do I exempt a sender or IP from spam checks?

Section titled “How do I exempt a sender or IP from spam checks?”

The right method depends on how far the exemption should go:

  • The trusted-domains list lowers the score of messages from a listed domain that pass DMARC.
  • A rule can add a tag with a negative score for a sender or an address.
  • The enableSpamFilter expression decides which sessions are filtered at all. Its default, is_empty(authenticated_as), leaves authenticated sessions unfiltered.

See Exempting a sender, domain or server.

Most likely because it is off. Greylisting, which defers the first delivery attempt from an unknown sender on the expectation that legitimate servers retry, is disabled until greylistFor is set on the SpamSettings object.

When it is enabled, the first attempt for a sender and recipient pair is deferred with 452 4.2.2 Greylisted, please try again in a few moments. Any later attempt for the same pair is accepted, including an immediate retry. The client IP address is not part of the pair, so a retry from another server of the same sender is accepted too. Messages from authenticated sessions are never greylisted. See Deferral and acceptance.

What do URIBL_BLOCKED, DNSWL_BLOCKED and similar tags mean?

Section titled “What do URIBL_BLOCKED, DNSWL_BLOCKED and similar tags mean?”

The blocklist operator refused the query. Operators do this for queries that arrive from public or high-volume DNS resolvers, and they answer with a special code, which the filter maps to a _BLOCKED tag. These tags score 0, but they also mean that the list was not consulted for the message, so the filter works with less evidence. Query the blocklists through a dedicated recursive resolver run for the server. See Blocked queries.

A spam trap is an address that no legitimate sender writes to, so any mail it receives can be treated as spam. Make the trap address an accepted local recipient, as an account, an alias or under a catch-all address, and add it to the spam-traps list.

The address must be a local recipient, because the filter never sees mail for any other address. Such mail is refused at RCPT with 550 5.1.1 Mailbox does not exist. before it reaches the filter. See Setting up a spam trap.

Which Sieve features work in system scripts and which in user scripts?

Section titled “Which Sieve features work in system scripts and which in user scripts?”

The two kinds of script run at different moments, in different interpreters. System scripts run in the trusted interpreter during the SMTP session. User scripts run in the untrusted interpreter when a message is delivered to a mailbox.

The trusted interpreter does not provide the extensions that act on a mailbox: fileinto, fcc, mailbox, mailboxid, mboxmetadata, servermetadata, imapsieve, vacation, vacation-seconds and duplicate. Filing into folders and vacation replies therefore belong in user scripts. See Two interpreters.

The usual causes are these:

  • The script is not active.
  • The stage’s script expression returns a name that does not match the script.
  • The script was created or changed after the settings were last reloaded.
  • The script uses an extension that the trusted interpreter does not provide, which is refused with the event sieve.not-supported.

Each of these cases is recorded in the log. See Troubleshooting system scripts.

When do user scripts run, and what can they see?

Section titled “When do user scripts run, and what can they see?”

A user script runs when a message is delivered to a local mailbox, once for each recipient. By then, the message has passed the SMTP stages and the spam filter.

The script sees the envelope sender, the recipient and the original recipient. It also sees the message with the X-Spam-Result and X-Spam-Score headers that the spam filter added. The X-Spam-Status header is not visible yet, because it is added only after the script runs. See Execution points and Envelope.

How do I forward a user’s mail to another address?

Section titled “How do I forward a user’s mail to another address?”

Use redirect :copy in the user’s Sieve script. It forwards a copy and keeps the message in the mailbox, whereas redirect without :copy forwards the message without storing it.

Expect some forwarded mail to be rejected by the receiving server. Stalwart does not rewrite the envelope sender, so the forwarded message keeps its original sender, which can fail that server’s checks. See Forward a copy.

How do I add a disclaimer to outgoing mail?

Section titled “How do I add a disclaimer to outgoing mail?”

Use a system Sieve script that appends the text to both the plain-text and the HTML part of a multipart message. The example on the content rewriting page does this. It leaves single-part messages unchanged and describes the other cases it does not handle. See Adding a disclaimer.

Run it in Sievepad, which runs the same Sieve interpreter in the browser against a test message. Functions that only the server can run, such as DNS, SQL and AI calls, are not available there. See Testing scripts.