Sieve is a language for filtering e-mail messages at time of final
delivery. It is designed to be implementable on either a mail client
or mail server, and can on Zeta be used with the e-mail client Beam.
The language has been made simple enough to allow many users to
make use of it, rich enough that it can be used productively, but
also limited in power in order to allow for a safe server-side filtering
system. The intention is to make it impossible for users to do anything
more complex (and dangerous) than write simple mail filters. Because
of the expectation that users will make use of filtering if it is
offered and easy to use, this language.
Scripts written in Sieve are executed during final delivery, when
the message is moved to the user-accessible mailbox. Sieve filters
might be used at a number of "final delivery" points in a mail system:
by an SMTP server, by an IMAP or POP server filing into one or more
mailboxes, or by a Mail User Agent (MUA or mail client) acting as
a delivery agent (for instance, a POP or offline IMAP client.)
There are a number of reasons to use a filtering system. Mail traffic
for most users has been increasing due both to increased usage of
e-mail, the emergence of unsolicited email as a form of advertising,
and increased usage of mailing lists.
The language consists of a set of commands. Each command consists
of a set of tokens delimited by whitespace. The command identifier
is the first token and it is followed by zero or more argument tokens.
Arguments may be literal data, tags, blocks of commands, or test
commands.
Two types of comments are offered:
- Hash comments begin with a "#" character that is not contained
within a string and continue until the next CRLF.
Example: |
if size :over 100K { # this
is a comment
|
|
discard; |
} |
- Bracketed comments begin with the token "/*" and end with "*/"
outside of a string. Bracketed comments may span multiple lines.
Example: |
if size :over 100K { /* this is a comment |
|
this is still a comment */ discard /* this
is a comment */ ; |
} |
Scripts involve large numbers of strings as they are used for pattern
matching, addresses, textual bodies, etc. Typically, short quoted
strings suffice for most uses, but a more convenient form is provided
for longer strings such as bodies of messages.
Match Types
There are three match types describing the matching used in this
specification: ":is", ":contains", and ":matches".
Match type arguments are supplied to those commands which allow
them to specify what kind of match is to be performed. These are
used as tagged arguments to tests that perform string comparison.
The ":contains" match type describes a substring match. If the
value argument contains the key argument as a substring, the match
is true.
The ":is" match type describes an absolute match; if the contents
of the first string are absolutely the same as the contents of the
second string, they match.
The ":matches" version specifies a wildcard match using the characters
"*" and "?". "*" matches zero or more characters,
and "?" matches a single character. "?" and "*"
may be escaped as "\\?" and "\\*" in strings to match
against themselves. The first backslash escapes the second backslash;
together, they escape the "*".
All implementations must support the "i;octet" comparator
(simply compares octets) and the "i;ascii-casemap" comparator (which
treats uppercase and lowercase characters in the ASCII subset of
UTF-8 as the same). If left unspecified, the default is "i;ascii-casemap".
Some comparators may not be usable with substring matches; that
is, they may only work with ":is". It is an error to try
and use a comparator with ":matches" or ":contains"
that is not compatible with it.
Example: |
if header :contains :comparator "i;octet" "Subject" |
|
"MAKE MONEY FAST" { |
|
discard; |
} |
would discard any message with subjects like "You can MAKE MONEY
FAST", but not "You can Make Money Fast", since the comparator used
is case-sensitive.
Both ":matches" and ":contains" match types are compatible
with the "i;octet" and "i;ascii-casemap" comparators and may be
used with them.
Comparisons Against Addresses
Addresses are one of the most frequent things represented as strings.
These are structured, and being able to compare against the local-
part or the domain of an address is useful, so some tests that act
exclusively on addresses take an additional optional argument that
specifies what the test acts on.
These optional arguments are ":localpart", ":domain",
and ":all", which act on the local-part (left-side), the
domain part (right- side), and the whole address.
The semantics are similar to those of any of the many other programming
languages these control commands appear in. When the interpreter
sees an "if", it evaluates the test associated with it. If the test
is true, it executes the block associated with it. If the test of
the "if" is false, it evaluates the test of the first "elsif" (if
any). If the test of "elsif" is true, it runs the elsif's block.
An elsif may be followed by an elsif, in which case, the interpreter
repeats this process until it runs out of elsifs. When the interpreter
runs out of elsifs, there may be an "else" case. If there is, and
none of the if or elsif tests were true, the interpreter runs the
else case. This provides a way of performing exactly one of the
blocks in the chain. In the following example, both Message A and
B are dropped.
Example: |
require "fileinto";
if header :contains "from" "coyote" { |
|
|
discard; |
|
} elsif header :contains ["subject"] ["$$$"]
{ |
|
|
discard; |
|
} else { |
|
fileinto "INBOX"; |
} |
|
When the script below is run over message A, it redirects the message
to acm@example.edu; message B, to postmaster@example.edu; any other
message is redirected to field@example.edu.
Example: if header :contains ["From"] ["coyote"] { redirect "acm@example.edu";
} elsif header :contains "Subject" "$$$" { redirect "postmaster@example.edu";
} else { redirect "field@example.edu"; }
Control Structure Stop
Syntax: stop
The "stop" action ends all processing. If no actions have been
executed, then the keep action is taken.
Action Commands
This document supplies five actions that may be taken on a message:
keep, fileinto, redirect, reject, and discard.
Implementations MUST support the "keep", "discard", and "redirect"
actions. Implementations SHOULD support "reject" and "fileinto".
Action reject
Syntax: reject
The optional "reject" action refuses delivery of a message by sending
back an [MDN] to the sender. It resends the message to the sender,
wrapping it in a "reject" form, noting that it was rejected by the
recipient. In the following script, message A is rejected and returned
to the sender.
Example: if header :contains "from" "coyote@desert.example.org"
{ reject "I am not taking mail from you, and I don't want your birdseed,
either!"; }
A reject message MUST take the form of a failure MDN as specified
by [MDN]. The human-readable portion of the message, the first component
of the MDN, contains the human readable message describing the error,
and it SHOULD contain additional text alerting the original sender
that mail was refused by a filter. This part of the MDN might appear
as follows:
------------------------------------------------------------
Message was refused by recipient's mail
filtering program. Reason given was as follows:
I am not taking mail from you, and I don't want
your birdseed, either!
------------------------------------------------------------
The MDN action-value field as defined in the MDN specification
MUST be "deleted" and MUST have the MDN-sent-automatically and automatic-
action modes set.
Because some implementations can not or will not implement the
reject command, it is optional. The capability string to be used
with the require command is "reject".
Action fileinto
Syntax: fileinto
The "fileinto" action delivers the message into the specified folder.
Implementations SHOULD support fileinto, but in some environments
this may be impossible.
In the following script, message A is filed into folder "INBOX.harassment".
Example: require "fileinto"; if header :contains ["from"] "coyote"
{ fileinto "INBOX.harassment"; }
Action redirect
Syntax: redirect
The "redirect" action is used to send the message to another user
at a supplied address, as a mail forwarding feature does. The "redirect"
action makes no changes to the message body or existing headers,
but it may add new headers. The "redirect" modifies the envelope
recipient.
Example: redirect "bart@example.edu";
Action keep
Syntax: keep
The "keep" action is whatever action is taken in lieu of all other
actions, if no filtering happens at all; generally, this simply
means to file the message into the user's main mailbox. This command
provides a way to execute this action without needing to know the
name of the user's main mailbox, providing a way to call it without
needing to understand the user's setup, or the underlying mail system.
Example: if size :under 1M { keep; } else { discard; }
Action discard Syntax: discard Discard is used to silently throw
away the message. It does so by simply canceling the implicit keep.
If discard is used with other actions, the other actions still happen.
Discard is compatible with all other actions. (For instance fileinto+discard
is equivalent to fileinto.)
Example: if header :contains ["from"] ["idiot@example.edu"] { discard;
}
Test Commands
Tests are used in conditionals to decide which part(s) of the conditional
to execute.
Implementations must support these tests: "address",
"allof", "anyof", "exists", "false",
"header", "not", "size" and "true".
Implementations should support the "envelope" test.
Test envelope
Syntax: envelope [COMPARATOR] [ADDRESS-PART] [MATCH-TYPE]
The "envelope" test is true if the specified part of the
SMTP (or equivalent) envelope matches the specified key.
If one of the envelope-part strings is (case insensitive) "from",
then matching occurs against the FROM address used in the SMTP MAIL
command.
If one of the envelope-part strings is (case insensitive) "to",
then matching occurs against the TO address used in the SMTP RCPT
command that resulted in this message getting delivered to this
user.
Note that only the most recent TO is available, and only the one
relevant to this user. The envelope-part is a string list and may
contain more than one parameter, in which case all of the strings
specified in the key-list are matched against all parts given in
the envelope-part list.
Like address and header, this test returns true if any combination
of the envelope-part and key-list arguments is true.
All tests against envelopes MUST drop source routes.
If the SMTP transaction involved several RCPT commands, only the
data from the RCPT command that caused delivery to this user is
available in the "to" part of the envelope.
If a protocol other than SMTP is used for message transport, implementations
are expected to adapt this command appropriately.
The envelope command is optional. Implementations SHOULD support
it, but the necessary information may not be available in all cases.
Example: require "envelope"; if envelope :all :is "from" "tim@example.com"
{ discard;
Test exists
Syntax: exists
The "exists" test is true if the headers listed in the header-names
argument exist within the message. All of the headers must exist
or the test is false.
The following example throws out mail that doesn't have a From
header and a Date header.
Example: if not exists ["From","Date"] { discard; }
Test false
Syntax: false
The "false" test always evaluates to false.
Two bigger examples on how to write filters with sieve
# Handle messages from known mailing lists # Move messages from
IETF filter discussion list to filter folder # if header :is "Sender"
"owner-ietf-mta-filters@imc.org" { fileinto "filter"; # move to
"filter" folder } # # Keep all messages to or from people in my
company # elsif address :domain :is ["From", "To"] "example.com"
{ keep; # keep in "In" folder }
# Try and catch unsolicited email. If a message is not to me, #
or it contains a subject known to be spam, file it away. # elsif
anyof (not address :all :contains ["To", "Cc", "Bcc"] "me@example.com",
header :matches "subject" ["*make*money*fast*", "*university*dipl*mas*"])
{ # If message header does not contain my address, # it's from a
list. fileinto "spam"; # move to "spam" folder } else { # Move all
other (non-company) mail to "personal" # folder. fileinto "personal";
}
|