Sieve filters and vacation replies
Personal mail rules with Sieve, every supported Sieve extension and limit, managing scripts over ManageSieve, and out-of-office replies set over JMAP or Sieve.
Nixt Server runs each person’s own rules on the server, as mail is delivered, so they work whichever app — or no app — is open. Rules are written in Sieve, the standard language for mail filters, and managed over ManageSieve. Out-of-office replies can also be set from a JMAP app without writing any Sieve.
How rules run
- Each account can keep up to 32 Sieve scripts. One of them is active, and only the active script runs.
- The active script runs once for every message delivered to the account, after the server’s own spam and malware filtering has accepted it.
- Mail the filter tagged as junk or quarantined goes to Junk or Quarantine whatever the script says; the script’s other actions, such as flags and notifications, still apply.
- If the script does nothing that files or discards the message, the message is kept in Inbox.
- If the script fails while running — it goes over a limit, for example — the message is kept in Inbox and the failure is written to the server’s log.
- Scripts are checked when they are uploaded, so a script with a mistake is refused with the reason and its position rather than failing later.
A first script
require ["fileinto", "imap4flags", "vacation"];
# File mailing-list mail into its own folder, creating it if needed
if header :contains "list-id" "announce.example.org" {
fileinto :create "Lists/Announce";
stop;
}
# Flag mail from the boss
if address :is "from" "sam@example.com" {
addflag "\\Flagged";
}
# Out of office
vacation :days 7 :subject "Away until Monday" "I am away and will reply when I am back.";
Upload it with any ManageSieve app, then make it active. See ManageSieve.
Supported extensions
Every script may require any of these. The ManageSieve SIEVE capability lists them, together with the comparators i;octet and i;ascii-casemap.
| Extension | What it adds | Standard |
|---|---|---|
envelope | Test the envelope sender and recipient. | RFC 5228 |
fileinto | File into a folder. | RFC 5228 |
encoded-character | Write characters as ${hex:…} and ${unicode:…} in strings. | RFC 5228 |
comparator-i;ascii-numeric | Compare strings as numbers. | RFC 4790 |
vacation | Automatic replies. | RFC 5230 |
vacation-seconds | vacation :seconds, for reply periods shorter than a day. | RFC 6131 |
variables | set, variables and match variables such as ${1}. | RFC 5229 |
relational | :count and :value with gt, lt and the rest. | RFC 5231 |
imap4flags | Set, add and remove flags and keywords. | RFC 5232 |
subaddress | Test :user and :detail in user+detail@example.com. | RFC 5233 |
body | Test the message body. | RFC 5173 |
copy | :copy on fileinto and redirect, keeping the message where it would have gone. | RFC 3894 |
editheader | addheader and deleteheader. | RFC 5293 |
include | Run another of your scripts from this one. | RFC 6609 |
date | Test dates in headers and the current date. | RFC 5260 |
index | Test the first, second or last occurrence of a header. | RFC 5260 |
extlists | Test against named external lists. No lists are defined, so these tests never match. | RFC 6134 |
enotify | Send a notification, with the mailto method. | RFC 5435, RFC 5436 |
ihave | Test whether an extension is available. | RFC 5463 |
mailbox | fileinto :create, and test whether a folder exists. | RFC 5490 |
environment | Test facts about the environment. | RFC 5183 |
reject | Refuse a message with a reason sent back to the sender. | RFC 5429 |
ereject | The same, for protocols that can refuse during delivery. | RFC 5429 |
duplicate | Test whether this message has been seen before. | RFC 7352 |
fcc | Keep a copy of a vacation reply or notification in a folder. | RFC 8580 |
special-use | File into a folder by its use, such as \Junk, whatever it is called. | RFC 8579 |
mailboxid | File into a folder by its id, which survives renaming. | RFC 9042 |
regex | :regex matching. | draft-ietf-sieve-regex |
Notes on some extensions
- Folder names.
fileinto "Receipts"files into a folder of that name. If it does not exist and:createis not given, the message goes to Inbox.fileinto :specialuse "\\Archive" "Archive"finds the folder by use first. - Special uses available:
\Drafts,\Sent,\Junk,\Trashand\Archive. redirectsends the message on to another address. A destination outside your organisation goes only once your organisation allows it: saving the script answersOK (WARNINGS)saying the forward waits for approval, and until then the message is kept in your mailbox (see Forwarding outside the organisation). The server rewrites the envelope sender so the destination’s SPF check sees this server’s domain. A redirect to the account’s own address is ignored. Without:copy, the message is not kept.rejectanderejectkeep the message out of the mailbox and send the sender a delivery failure notice carrying your reason, with status550 5.7.1, when the sender asked for failure notices and passed SPF or DKIM.notifywith amailto:URI sends a short notification message from your address. A:fromthat is not one of your own addresses is set aside, and the notification comes from you, because the server signs the mail it sends in your domain’s name.includecan include your own scripts (:personal).environmentanswersname,version,locationandphase.dateandcurrentdateuse UTC when the script names no zone.duplicateremembers what it has seen for up to 30 days.
Limits
| Limit | Value |
|---|---|
| Scripts per account | 32 |
| Size of all an account’s scripts together | 1 MiB |
| Size of one script | 64 KiB |
| Script name | 1 to 128 characters; no control characters, / or \; no leading or trailing blanks |
| Nesting of blocks and tests | 32 |
| One string, literal or expanded | 64 KiB |
| Variables a script may set | 128 |
| Length of one variable’s value | 8 KiB; longer values are cut |
| Commands and tests evaluated for one message | 100,000 |
Characters compared by :matches, :contains and :regex for one message | 50 million |
redirect actions per run | 4 (advertised as MAXREDIRECTS) |
fileinto and redirect actions together | 32 |
notify actions per run | 8 |
include depth, and includes per run | 8 deep, 32 in all |
addheader and deleteheader actions per run | 32 |
| Header fields read from a message | 1,000 |
Compiled size of one :regex pattern | 256 KiB |
vacation :days | 1 to 30, default 7 |
vacation :seconds | 0 to 30 days |
duplicate :seconds | Up to 30 days |
Limits the organisation sets
An organisation can set tighter limits on its people’s scripts in its runtime settings:
| Setting | What it limits |
|---|---|
sieve.redirects_per_message | Places one message may be redirected to by a person’s script. |
sieve.redirects_per_day | Messages one person’s scripts may redirect in a day. Past it, the redirect is held and the message is kept. |
sieve.vacations_per_day | Automatic replies one person may send in a day, from a script or from the out-of-office setting. |
sieve.script_bytes | How large a script may be. |
sieve.steps | Commands and tests one run of a script may evaluate. |
Each refusal names the setting that caused it in the message’s trace.
The organisation’s own rules
An organisation can run a Sieve script of its own before everybody’s and another after:
- Before runs first, on every message to the organisation’s people. What it decides stands: a
discard, arejector astopthere ends the run, and nothing a person’s own script does takes back what it filed. - A person’s own script runs next, as ever.
- After runs last, on every message — one a person’s script rejected too — which is where an archive or journal copy goes. What it files stands, even over a person’s
discard.
Each script sees the message as the one before left it, headers it edited included. A script that fails to run counts as having done nothing, so mail is never lost. The organisation’s redirects are its own forwards: they never wait for its approval of forwarding, and never count against a person’s limits.
For example, to keep a copy of every message in an archive:
require "copy";
redirect :copy "vault@archive.example";
Organisation administrators set them, and auditors read them:
-
Console: Settings › Organisation rules.
-
Command line:
versealx-server admin scripts show versealx-server admin scripts set --after after.sieve versealx-server admin scripts set --no-after -
API:
GETandPUT /api/v1/tenants/{tenant}/scripts, both saved whole with theversionread.
A script that doesn’t compile is refused, naming the line and the column.
ManageSieve
ManageSieve is the protocol apps use to upload and choose scripts. Connect to the server’s host name on port 4190, start TLS with STARTTLS, and sign in with your email address and password.
| Command | What it does |
|---|---|
CAPABILITY | Lists IMPLEMENTATION, VERSION 1.0, SIEVE (the extensions), SASL (once TLS is on), STARTTLS (before it is), NOTIFY mailto, MAXREDIRECTS 4 and UNAUTHENTICATE. |
STARTTLS | Starts TLS. Required before signing in. |
AUTHENTICATE | Signs in. See Signing in. |
LISTSCRIPTS | Lists your scripts, marking the active one ACTIVE. |
PUTSCRIPT <name> <script> | Uploads or replaces a script. The script is checked first and refused with the reason if it does not compile. |
CHECKSCRIPT <script> | Checks a script without saving it. |
GETSCRIPT <name> | Downloads a script. |
SETACTIVE <name> | Makes a script the active one. SETACTIVE "" turns all scripts off. |
RENAMESCRIPT <old> <new> | Renames a script. |
DELETESCRIPT <name> | Deletes a script. The active script cannot be deleted; make another active, or none, first. |
HAVESPACE <name> <size> | Asks whether a script of that size would fit. |
NOOP | Keeps the connection alive. |
UNAUTHENTICATE | Signs out without closing the connection. |
LOGOUT | Ends the session. |
Refusals are NO replies. The response code in brackets is what an app acts on; the text is what it can show you.
| Code | Text | Cause |
|---|---|---|
| — | Authenticate first | A script command before signing in. |
ENCRYPT-NEEDED | Use STARTTLS before authenticating | Signing in without TLS. |
NONEXISTENT | no script named "<name>" | No script has that name. |
ACTIVE | "<name>" is the active script | Deleting the active script. |
ALREADYEXISTS | a script named "<name>" exists | Renaming onto an existing name. |
QUOTA/MAXSCRIPTS | the account may keep 32 scripts | The account already has 32 scripts. |
QUOTA/MAXSIZE | <n> bytes of scripts would exceed the quota of 1048576 | The scripts together would be over 1 MiB, or one upload is too large. |
| — | a script name is 1 to 128 characters | An empty or overlong name. |
| — | a script name cannot start or end with a blank | Spaces around the name. |
| — | The position and reason | The script does not compile. |
TRYLATER | — | The store did not answer; try again. |
After several failed sign-ins on one connection, the server sends BYE "Too many failed authentications" and closes it.
Vacation replies
An out-of-office reply can come from two places:
| Set with | Where | Best for |
|---|---|---|
JMAP VacationResponse | A JMAP app such as Nixt Mail. | People who want a switch, dates and a message, without writing Sieve. |
A Sieve vacation action | Your active script. | Replies that depend on who wrote, or what about. |
If the active script runs a vacation action for a message, that reply is used and the JMAP setting is ignored for that message.
Setting a reply over JMAP
VacationResponse/set on the singleton object takes:
| Property | Meaning |
|---|---|
isEnabled | Whether to reply at all. |
fromDate | Do not reply before this time. Leave empty to start now. |
toDate | Stop replying at this time. Leave empty to reply until turned off. |
subject | The reply’s subject. Empty means Auto: followed by the original subject. |
textBody | The reply’s text. |
A JMAP reply goes to each sender at most once every 7 days. Nixt Mail’s out-of-office setting uses this; see the Nixt Mail documentation at /docs/mail.
Who gets a reply
For both kinds of reply, a message gets none when:
| Rule | Why |
|---|---|
| The envelope sender is empty | It is a bounce or a notification. |
It has a List-Id, List-Unsubscribe or List-Post header, or Precedence: bulk, list or junk | It came from a mailing list or a bulk sender. |
It has an Auto-Submitted header other than no | It was sent automatically. |
None of your addresses is in To or Cc | It reached you as a copy, through a list, or through an alias. A Sieve vacation :addresses can name more addresses. |
| It is from yourself | Replying to yourself helps nobody. |
| The sender already had a reply within the period | One reply per sender per period. |
| The filter put it in Junk or held it in quarantine | Spam’s sender is usually forged, so a reply would go to somebody who never wrote. |
| You, your domain or your organisation block the sender | A reply would tell them your address is read. See Allowed and blocked senders. |
What the reply looks like
Fromis your address, or the Sieve:fromaddress when it is one of your own; any other:fromis set aside.Tois the sender.- It is signed with your domain’s DKIM keys, so it passes DMARC where it lands. See Mail the server writes itself.
Subjectis the one you set, orAuto:and the original subject. A subject that is not plain ASCII is encoded so every mail app displays it correctly.In-Reply-ToandReferenceslink it to the original, so it appears in the same conversation.Auto-Submitted: auto-repliedandPrecedence: bulkstop other servers’ auto-responders replying back.- The envelope sender is empty, so the reply can never itself cause a bounce loop.
Something unclear or out of date on this page? Tell us.