Examples
Most filters follow a handful of patterns, and a working script is easier to adapt than a blank page. The examples on this page are starting points for common filters, grouped by who installs them.
The first group are system scripts. An administrator installs each one as a SieveSystemScript and selects it from an SMTP stage, as described in Trusted interpreter. The second group are user scripts. A user uploads them over ManageSieve or JMAP, and they run at delivery to that user’s mailbox.
Examples that run in Sievepad are followed by a link that opens them with a matching test message, so each one can be tried before it is installed.
System scripts
Section titled “System scripts”System scripts enforce policy while the message is still being received, so most of them end in a reject. A system script refuses a command or a message with reject. The reject text becomes the SMTP reply only when it starts with a three-digit reply code followed by a space (Replies and actions). Every example below therefore starts the reject text with a full reply code.
Reject clients with a blocklisted EHLO domain
Section titled “Reject clients with a blocklisted EHLO domain”A client that announces a known bad name in its EHLO command can be turned away before it sends anything. The following script rejects a client whose EHLO domain is listed in the blocked-domains lookup list. Install it as a system script named block-helo, and select it from the script field of MtaStageEhlo:
require ["variables", "extlists", "reject"];
if string :list "${env.helo_domain}" "blocked-domains" { reject "550 5.7.1 Your domain '${env.helo_domain}' has been blocklisted.";}The script field then names the script:
{ "script": {"else": "'block-helo'"}}The list is a set of MemoryLookupKey entries with the namespace blocked-domains, one per domain. Lists held in an SQL store work differently. They cannot be tested with :list, so a script that needs them calls the query function.
Require the From header to match the sender of authenticated mail
Section titled “Require the From header to match the sender of authenticated mail”An authenticated user could otherwise send mail whose visible sender is someone else. The mustMatchSender check compares the envelope sender of an authenticated submission with the addresses of the account. It does not look at the From header, which is what recipients see.
The following script closes that gap at the DATA stage. It requires the From header of authenticated submissions to carry the envelope sender, which mustMatchSender has already verified. Install it as a system script named from-header, and select it from the script field of MtaStageData:
require ["variables", "envelope", "reject"];
if not string :is "${env.authenticated_as}" "" { if not address :all :comparator "i;unicode-casemap" :is "from" "${envelope.from}" { reject "550 5.7.1 The From header must match the sender address."; }}The script tests env.authenticated_as first, so unauthenticated messages are not affected. Messages that list several addresses in the From header pass when one of them is the envelope sender. Addresses, aliases and catch-all describes the addresses an account may send from.
Reject messages with an executable attachment
Section titled “Reject messages with an executable attachment”The spam filter treats dangerous attachments as evidence: it adds them to the score of a message. Some sites prefer a hard rule instead. The following script, run at the DATA stage, rejects messages that carry an attachment whose name ends in .exe, .scr or .js, which refuses the message outright whatever its score:
require ["mime", "foreverypart", "reject"];
foreverypart { if header :mime :param "filename" :matches "Content-Disposition" ["*.exe", "*.scr", "*.js"] { reject "550 5.7.1 Executable attachments are not accepted."; stop; }}Two common tasks need no example here. Adding a disclaimer, or otherwise changing the content of outgoing messages, is described in Content rewriting. Greylisting needs no script, because it is built into the SMTP server, as described in Greylisting.
User scripts
Section titled “User scripts”User scripts organise mail rather than refuse it. They run once for each message delivered to the user, after the spam filter has scored it. A script that ends without filing the message keeps it in the inbox, or in the Junk folder when the spam filter classified it as spam.
Automatic reply
Section titled “Automatic reply”An out-of-office reply is the most common user script. The following script answers each sender at most once every seven days while the user is away:
require ["vacation"];
vacation :days 7 :subject "Out of office" "I am out of the office until 3 November and will reply when I return.";The reply is sent with an empty envelope sender. It is not sent to mailing lists, or in answer to messages that are themselves automatic replies, which prevents loops between two absent users. JMAP clients usually configure the same reply through the vacation response, described in Vacation responses.
File mailing list messages
Section titled “File mailing list messages”Mailing list traffic is easier to read when each list has its own folder. The following script files messages from mailing lists into a folder named after the list, creating the folder when it does not exist:
require ["fileinto", "mailbox", "variables"];
if header :matches "List-Id" "*<*.*>*" { fileinto :create "Lists/${2}";}For the header List-Id: Release announcements <releases.lists.example.com>, the message is filed into Lists/releases.
File suspected spam into a review folder
Section titled “File suspected spam into a review folder”Some users prefer to review likely spam separately from the Junk folder. The following script files messages that the spam filter considers likely spam into a Spam review folder:
require ["fileinto", "mailbox", "spamtest", "relational", "comparator-i;ascii-numeric"];
if spamtest :value "ge" :comparator "i;ascii-numeric" "5" { fileinto :create "Spam review";}The spamtest value is a number from 1 (not spam) to 10 (spam), and 5 corresponds to the spam threshold of the spam filter. A message filed by the script stays where the script put it, and is not moved to the Junk folder.
Forward a copy
Section titled “Forward a copy”Forwarding sends mail on to another mailbox, for example a personal address. The following script forwards a copy of every message to another address, and keeps the original in the mailbox:
require ["copy"];
redirect :copy "[email protected]";The :copy argument makes the difference between forwarding a copy and moving the message. Without it, the message is forwarded and not stored. The number of forwards per message is limited by maxRedirects, 1 by default.
Forwarding has a known weakness with sender authentication. The forwarded copy keeps the original sender, and Stalwart does not rewrite it with the Sender Rewriting Scheme (SRS), a technique that replaces the envelope sender with an address of the forwarding server. The receiving server may therefore find that Stalwart is not authorised by the sender’s SPF record. When the sender’s domain also enforces DMARC, and the message carries no valid aligned DKIM signature, the receiving server may reject the copy.
File by sub-address
Section titled “File by sub-address”Many users give out addresses with a sub-address, such as [email protected], to see who uses which address and to sort the replies. The following script files messages sent to such an address into a folder named after the sub-address:
require ["fileinto", "mailbox", "variables", "subaddress"];
if address :detail :matches ["to", "cc"] "*" { if not string :is "${1}" "" { fileinto :create "${1}"; }}The script tests the To and Cc headers rather than the envelope. The envelope recipient cannot be used for this, because Stalwart removes the sub-address from the recipient when it accepts the message, as described in Addresses, aliases and catch-all.