RFC 5228

Sieve: An Email Filtering Language

๐Ÿ“… published January 2008, from ietf

Sieve: An Email Filtering Language ๐Ÿ’Œ

Title block tea โ˜•, bestie: this one is RFC 5228, written by P. Guenther, Ed. (Sendmail, Inc.) and T. Showalter, Ed., dated January 2008. It's on the Standards Track ๐ŸŽ€, and it obsoletes RFC 3028. Okay so that means RFC 3028 is out of date and this is the newer version that replaces it โœจ


Status of This Memo

This document is an Internet standards track protocol for the Internet community, and it's asking for discussion and suggestions for improvements ๐Ÿซถ If you want the current standardization state and status of this protocol, check the "Internet Official Protocol Standards" (STD 1), which is the official source. And the distribution of this memo is unlimited, so everyone can read it ๐ŸŒธ

Abstract

Okay, so this doc describes a language for filtering email messages at the time of final delivery ๐Ÿ’– It's designed so it can be implemented on either a mail client or a mail server, and it's meant to be extensible, simple, and independent of the access protocol, mail architecture, and operating system. Literally so flexible ๐Ÿฆ‹

It's also perfect for a mail server where users aren't allowed to run arbitrary programs, like on a black box Internet Message Access Protocol (IMAP) server ๐Ÿ”’ Why? Because the base language has no variables, no loops, and no ability to shell out to external programs. Iconic restraint, honestly ๐Ÿ’…


Table of Contents

1. Introduction ....................................................4
   1.1. Conventions Used in This Document ..........................4
   1.2. Example Mail Messages ......................................5
2. Design ..........................................................6
   2.1. Form of the Language .......................................6
   2.2. Whitespace .................................................7
   2.3. Comments ...................................................7
   2.4. Literal Data ...............................................7
        2.4.1. Numbers .............................................7
        2.4.2. Strings .............................................8
               2.4.2.1. String Lists ...............................9
               2.4.2.2. Headers ....................................9
               2.4.2.3. Addresses .................................10
               2.4.2.4. Encoding Characters Using
                        "encoded-character" .......................10
   2.5. Tests .....................................................11
        2.5.1. Test Lists .........................................12
   2.6. Arguments .................................................12
        2.6.1. Positional Arguments ...............................12
        2.6.2. Tagged Arguments ...................................12
        2.6.3. Optional Arguments .................................13
        2.6.4. Types of Arguments .................................13
   2.7. String Comparison .........................................13
        2.7.1. Match Type .........................................14
        2.7.2. Comparisons across Character Sets ..................15
        2.7.3. Comparators ........................................15
        2.7.4. Comparisons against Addresses ......................16
   2.8. Blocks ....................................................17
   2.9. Commands ..................................................17
   2.10. Evaluation ...............................................18
        2.10.1. Action Interaction ................................18
        2.10.2. Implicit Keep .....................................18
        2.10.3. Message Uniqueness in a Mailbox ...................19
        2.10.4. Limits on Numbers of Actions ......................19
        2.10.5. Extensions and Optional Features ..................19
        2.10.6. Errors ............................................20
        2.10.7. Limits on Execution ...............................20
3. Control Commands ...............................................21
   3.1. Control if ................................................21
   3.2. Control require ...........................................22
   3.3. Control stop ..............................................22
4. Action Commands ................................................23
   4.1. Action fileinto ...........................................23
   4.2. Action redirect ...........................................23
   4.3. Action keep ...............................................24
   4.4. Action discard ............................................25

5. Test Commands ..................................................26
   5.1. Test address ..............................................26
   5.2. Test allof ................................................27
   5.3. Test anyof ................................................27
   5.4. Test envelope .............................................27
   5.5. Test exists ...............................................28
   5.6. Test false ................................................28
   5.7. Test header ...............................................29
   5.8. Test not ..................................................29
   5.9. Test size .................................................29
   5.10. Test true ................................................30
6. Extensibility ..................................................30
   6.1. Capability String .........................................31
   6.2. IANA Considerations .......................................31
        6.2.1. Template for Capability Registrations ..............32
        6.2.2. Handling of Existing Capability Registrations ......32
        6.2.3. Initial Capability Registrations ...................32
   6.3. Capability Transport ......................................33
7. Transmission ...................................................33
8. Parsing ........................................................34
   8.1. Lexical Tokens ............................................34
   8.2. Grammar ...................................................36
   8.3. Statement Elements ........................................36
9. Extended Example ...............................................37
10. Security Considerations .......................................38
11. Acknowledgments ...............................................39
12. Normative References ..........................................39
13. Informative References ........................................40
14. Changes from RFC 3028 .........................................41

1. Introduction โœจ

Okay, so this memo documents a language you can use to make filters for electronic mail ๐Ÿ“ฌ It isn't tied to any particular operating system or mail architecture, which is so iconic. It does require [IMAIL]-compliant messages, but otherwise it should generalize to lots of systems ๐ŸŒˆ

The language is powerful enough to be genuinely useful, but it's also limited on purpose so you get a safe server-side filtering system ๐Ÿ” The goal is to make it impossible for users to do anything more complex (and dangerous) than write simple mail filters. It also makes graphical user interfaces (GUIs) for creating and manipulating filters way easier ๐ŸŽจ The base language was not designed to be Turing-complete: there's no loop control structure and no functions. Not me being like "wait, no loops?" but also... it's giving intentional ๐Ÿ’…

Sieve scripts run during final delivery, which is when the message gets moved into the user-accessible mailbox ๐Ÿ“ฅ In systems where the Mail Transfer Agent (MTA) does the final delivery, like traditional Unix mail, it makes sense to filter when the MTA drops mail into the user's mailbox ๐Ÿฆ‹

There are a bunch of reasons people use a filtering system ๐Ÿ˜ญ Mail traffic has been climbing for most users because email is used more, unsolicited email has become a form of advertising, and mailing lists have gotten more popular too. Honestly, the inbox chaos is real ๐Ÿ’€

Here's the tea ๐Ÿต: Carnegie Mellon's experience showed that when a filtering system is available, lots of people use it to file messages from specific users or mailing lists ๐Ÿ—‚๏ธ But many others didn't use the Andrew system's FLAMES filtering language [FLAMES] because it was hard to set up. Major pain point, bestie ๐Ÿฅฒ

So because people are expected to use filtering if it's offered and it's easy to use, this language is simple enough for lots of users to pick up, but still rich enough to be used productively ๐Ÿ’œ It's also expected that GUI-based editors will be the preferred way to edit filters for a large number of users, which, fr fr, makes total sense ๐ŸŽ€

1.1. Conventions Used in This Document โญ

Okay, so in the sections that talk about the requirements of various keywords and operators, these conventions apply ๐Ÿ“

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [KEYWORDS]. Those capital words are the official BCP 14 vibes, so read them with full seriousness ๐Ÿซก

Every section on a command (test, action, or control) has a line labeled "Usage:". That line describes how to use the command, including its name and its arguments ๐Ÿงฉ Required arguments go inside angle brackets (< and >). Optional arguments go inside square brackets ([ and ]). Each argument is followed by its type, so <key: string> means an argument called "key" that is a string ๐Ÿ”‘ Literal strings are shown in double quotes. Alternatives are separated with slashes, and parentheses are used for grouping, similar to [ABNF]. It's giving formal grammar energy ๐Ÿค“

In the "Usage:" line there are three special bits of syntax that keep showing up: MATCH-TYPE, COMPARATOR, and ADDRESS-PART ๐ŸŒธ These get covered in sections 2.7.1, 2.7.3, and 2.7.4, respectively.

Also, the formal grammar for these commands is defined in section 8, and it's the authoritative reference on how to construct commands. But the grammar doesn't specify the order, semantics, number or types of arguments to commands, or which command names are legal. That's on purpose, so the language can be extended without changing the grammar ๐Ÿฆ‹โœจ Extension without grammar changes? Iconic design, no cap ๐Ÿ‘‘

1.2. Example Mail Messages โœจ

okay so we're gonna be using these two emails all the way through the doc as examples, so get to know them bestie ๐Ÿ’Œ

   Message A
   -----------------------------------------------------------
   Date: Tue, 1 Apr 1997 09:06:31 -0800 (PST)
   From: coyote@desert.example.org
   To: roadrunner@acme.example.com
   Subject: I have a present for you

   Look, I'm sorry about the whole anvil thing, and I really
   didn't mean to try and drop it on you from the top of the
   cliff.  I want to try to make it up to you.  I've got some
   great birdseed over here at my place--top of the line
   stuff--and if you come by, I'll have it all wrapped up
   for you.  I'm really sorry for all the problems I've caused
   for you over the years, but I know we can work this out.
   --
   Wile E. Coyote   "Super Genius"   coyote@desert.example.org
   -----------------------------------------------------------
   Message B
   -----------------------------------------------------------
   From: youcouldberich!@reply-by-postal-mail.invalid
   Sender: b1ff@de.res.example.com
   To: rube@landru.example.com
   Date:  Mon, 31 Mar 1997 18:26:10 -0800
   Subject: $$$ YOU, TOO, CAN BE A MILLIONAIRE! $$$

   YOU MAY HAVE ALREADY WON TEN MILLION DOLLARS, BUT I DOUBT
   IT!  SO JUST POST THIS TO SIX HUNDRED NEWSGROUPS!  IT WILL
   GUARANTEE THAT YOU GET AT LEAST FIVE RESPONSES WITH MONEY!
   MONEY! MONEY! COLD HARD CASH!  YOU WILL RECEIVE OVER
   $20,000 IN LESS THAN TWO MONTHS!  AND IT'S LEGAL!!!!!!!!!
   !!!!!!!!!!!!!!!!!!111111111!!!!!!!11111111111!!1  JUST
   SEND $5 IN SMALL, UNMARKED BILLS TO THE ADDRESSES BELOW!
   -----------------------------------------------------------

Message A is a super sweet apology from Wile E. Coyote to Roadrunner ๐Ÿฆ with birdseed and everything, and Message B is pure spam chaos from b1ff ๐Ÿ’€. Iconic pair, no notes.

2. Design ๐ŸŽ€

2.1. Form of the Language ๐Ÿ’–

okay so the language is basically a bunch of commands. Each command is made of tokens that are separated by whitespace. The first token is the command identifier, and it's followed by zero or more argument tokens. Arguments can be literal data, tags, blocks of commands, or test commands ๐Ÿงฉ.

Only US-ASCII characters are allowed, except strings and comments can have octets outside that range. Those will normally be UTF-8, as specified in [UTF-8]. NUL (US-ASCII 0) is never allowed in scripts, and CR and LF can only show up as the CRLF line ending ๐Ÿšซ.

Note: While this specification permits arbitrary octets to appear in Sieve scripts inside strings and comments, this has made it difficult to robustly handle Sieve scripts in programs that are sensitive to the encodings used. The "encoded-character" capability (section 2.4.2.4) provides an alternative means of representing such octets in strings using just US-ASCII characters. As such, the use of non-UTF-8 text in scripts should be considered a deprecated feature that may be abandoned.

Tokens other than strings are case-insensitive, so don't stress about capitals ๐ŸŒธ.

2.2. Whitespace ๐Ÿฆ‹

Whitespace is how you separate tokens. It's made up of tabs, newlines (CRLF, never just CR or LF), and the space character. How much whitespace you use doesn't matter at all โœจ.

2.3. Comments ๐Ÿ’ฌ

There are two kinds of comments, and they're basically the same as whitespace. You can put them anywhere whitespace can go, with one exception in multi-line strings, as described in the grammar ๐Ÿ™ˆ.

Hash comments start with a "#" character that isn't inside a string, and they keep going until the next CRLF.

Example:

  if size :over 100k { # this is a comment
                discard;
             }

Bracketed comments start with the token "/" and end with "/" outside of a string. They can span multiple lines. Bracketed comments do not nest, so no nesting drama here ๐Ÿ™…โ€โ™€๏ธ.

Example:

  if size :over 100K { /* this is a comment
                this is still a comment */ discard /* this is a comment
                */ ;
             }

2.4. Literal Data ๐Ÿ“

Literal data is data that doesn't get executed. It's just evaluated "as is" and used as arguments to commands. Literal data is limited to numbers, strings, and string lists ๐Ÿง.

2.4.1. Numbers ๐Ÿ”ข

Numbers are plain decimal numbers. To make big values easier to write, like message sizes, you MAY add a suffix of "K", "M", or "G" to mean a multiple of a power of two ๐Ÿงฎ. To match the power-of-two versions of SI units that computers love, "K" means kibi-, which is 1,024 (2^10) times the number, "M" means mebi-, which is 1,048,576 (2^20) times the number, and "G" means gibi-, which is 1,073,741,824 (2^30) times the number [BINARY-SI].

Implementations MUST support integer values in the inclusive range zero to 2,147,483,647 (2^31 - 1), but they MAY support larger values too ๐Ÿ’œ.

Only non-negative integers are allowed by this spec, so no negatives, sorry not sorry ๐Ÿšซโž–.

2.4.2. Strings ๐Ÿงต

Scripts use tons of string values for pattern matching, addresses, bodies of text, and more ๐Ÿ“จ. Short quoted strings usually do the trick, but there's a nicer form for longer strings like message bodies.

A quoted string starts and ends with a single double quote (the <"> character, US-ASCII 34). A backslash ("", US-ASCII 92) inside a quoted string is followed by either another backslash or a double quote. Those two-character sequences stand for a single backslash or double quote in the value, respectively ๐Ÿ”.

Scripts SHOULD NOT escape other characters with a backslash, so don't go escaping stuff for fun ๐Ÿ™…โ€โ™€๏ธ.

An undefined escape sequence, like "\a" where "a" doesn't have any special meaning, gets read as if there were no backslash. So "\a" is just "a". Extensions might change this though ๐Ÿคทโ€โ™€๏ธ.

Non-printing characters like tabs, CRLF, and control characters are allowed in quoted strings. Quoted strings MAY span multiple lines ๐ŸŒˆ. An unencoded NUL (US-ASCII 0) is not allowed in strings, but section 2.4.2.4 shows how to encode it.

Message header data gets converted to [UTF-8] for comparison (see section 2.7.2), so most string values will be UTF-8 encoded ๐ŸŒธ. However, implementations MUST accept all strings that match the grammar in section 8. Being able to use non-UTF-8 encoded strings matches what people actually do in the wild, and it's been really useful both for testing invalid data and for arguments with raw MIME parts for extension actions that make outgoing messages ๐Ÿ“ฆ.

For bigger chunks of text, like a whole email, there's a multi-line form. It starts with the keyword "text:", followed by a CRLF, and it ends with a CRLF, a single period, and another CRLF. The CRLF right before the final period counts as part of the value ๐Ÿ“. To let the message contain lines with just a single dot, lines get dot-stuffed. So when you're writing a message body, you add an extra "." before each line that starts with a "." ๐Ÿฆถ. When the server reads the script, it takes those extra dots back out. Heads up: a line that starts with a dot followed by a non-dot character isn't treated as dot-stuffed, so ".foo" is read as ".foo" ๐Ÿ‘€. Since that's kinda confusing, scripts SHOULD be properly dot-stuffed so those lines never show up.

A hashed comment or whitespace can sit between "text:" and the CRLF, but not inside the string itself. Bracketed comments aren't allowed here ๐Ÿ™…โ€โ™€๏ธ.

2.4.2.1. String Lists ๐Ÿ“š

When you're matching patterns, it's often handy to match against a group of strings instead of just one. So a list of strings is allowed in lots of tests. If the test is true for any one of the strings, then the whole test is true ๐ŸŽฏ.

For example, the test 'header :contains ["To", "Cc"] ["me@example.com", "me00@landru.example.com"]' is true if either a To header or a Cc header of the input message contains either of the email addresses "me@example.com" or "me00@landru.example.com" ๐Ÿ’Œ.

And whenever a list of strings is appropriate, a single string is fine by itself. It's treated like a list with one member. So the test 'exists "To"' is the same as the test 'exists ["To"]' โœจ.

2.4.2.2. Headers ๐Ÿ“ฎ

Headers are a subset of strings. In the Internet Message Specification [IMAIL], each header line can have whitespace almost anywhere in the line, including after the field name and before the colon that follows it. Extra spaces between the header name and the ":" in a header field are ignored ๐Ÿซง.

A header name never contains a colon. The "From" header refers to a line that begins with "From:" (or "From :", and so on). No header will match the string "From:" because of that trailing colon ๐Ÿ™ƒ.

Similarly, no header will match a syntactically invalid header name. An implementation MUST NOT cause an error for syntactically invalid header names in tests. Not an error, not a crash, nothing ๐Ÿ’….

Header lines get unfolded as described in [IMAIL] section 2.2.3. Header data SHOULD be interpreted according to [MIME3] section 6.2 (see section 2.7.2 below for details) ๐Ÿง .

2.4.2.3. Addresses ๐Ÿ“ฌ

A bunch of commands want email addresses, which are also a subset of strings. When these addresses are used for outbound stuff, they have to be compliant with [IMAIL], but this document adds some extra limits too. Using the symbols defined in [IMAIL], section 3, the syntax of an address looks like this:

   sieve-address = addr-spec                ; simple address
                 / phrase "<" addr-spec ">" ; name & addr-spec

So routes and group syntax are not allowed. If you need multiple addresses, use a string list ๐Ÿ“‹. Named groups are not allowed either.

It's an error for a script to run an action with a value used as an outbound address that doesn't match the "sieve-address" syntax ๐Ÿšจ.

2.4.2.4. Encoding Characters Using "encoded-character" โœจ

okay so when the "encoded-character" extension is on, certain character sequences in strings get swapped out for their decoded value ๐Ÿฆ‹ this happens after escape sequences are interpreted and dot-unstuffing is done. implementations SHOULD support "encoded-character" ๐Ÿ’…

you can stick arbitrary octets into a string with encoded-arb-octets. the whole sequence gets replaced by the octets matching the hexadecimal values in each hex-pair ๐ŸŒธ

blank                = WSP / CRLF
encoded-arb-octets   = "${hex:" hex-pair-seq "}"
hex-pair-seq         = *blank hex-pair *(1*blank hex-pair) *blank
hex-pair             = 1*2HEXDIG

the WSP and HEXDIG non-terminals are defined in Appendix B.1 of [ABNF] ๐ŸŽ€

sometimes typing Unicode characters straight in is a hassle (or just not it), so there's encoded-unicode-char for that ๐Ÿซถ the sequence gets replaced by the UTF-8 encoding of the Unicode characters you named, and they're identified by the hexadecimal value of unicode-hex ๐ŸŒˆ

encoded-unicode-char = "${unicode:" unicode-hex-seq "}"
unicode-hex-seq      = *blank unicode-hex
                       *(1*blank unicode-hex) *blank
unicode-hex          = 1*HEXDIG

it's an error for a script to use a hex value that isn't in either the range 0 to D7FF or the range E000 to 10FFFF ๐Ÿ™ˆ (the range D800 to DFFF is excluded because those numbers only exist as part of the UTF-16 encoding form and don't apply to the UTF-8 encoding this syntax represents, so no, they don't count ๐Ÿ’€)

Note: Implementations MUST NOT raise an error for an out-of-range Unicode value unless the sequence containing it is well-formed according to the grammar.

the capability string for use with the require command is "encoded-character" ๐Ÿงš

in the script below, message B gets discarded, since the test string is equivalent to "$$$" ๐Ÿ“

require "encoded-character";
if header :contains "Subject" "$${hex:24 24}" {
   discard;
}

here are some valid and invalid encodings and how they get handled ๐Ÿ‘‘

"$${hex:40}"         -> "$@"
"${hex: 40 }"        -> "@"
"${HEX: 40}"         -> "@"
"${hex:40"           -> "${hex:40"
"${hex:400}"         -> "${hex:400}"
"${hex:4${hex:30}}"  -> "${hex:40}"
"${unicode:40}"      -> "@"
"${ unicode:40}"     -> "${ unicode:40}"
"${UNICODE:40}"      -> "@"
"${UnICoDE:0000040}" -> "@"
"${Unicode:40}"      -> "@"
"${Unicode:Cool}"    -> "${Unicode:Cool}"
"${unicode:200000}"  -> error
"${Unicode:DF01}     -> error

2.5. Tests ๐Ÿงช

tests are given as arguments to commands so you can control what they do ๐Ÿ’– in this document, tests get handed to if/elsif to decide which block of code runs.

2.5.1. Test Lists ๐Ÿ’œ

some tests ("allof" and "anyof", which do logical "and" and logical "or") may need more than one test as an argument. the test-list syntax element lets you group tests as a comma-separated list in parentheses ๐ŸŒธ

if anyof (not exists ["From", "Date"],
          header :contains "from" "fool@example.com") {
   discard;
}

2.6. Arguments ๐ŸŽ€

to tell a command what to do, most commands take arguments ๐Ÿซถ there are three types: positional, tagged, and optional.

it's an error for a script, on a single command, to use conflicting arguments or to use a tagged or optional argument more than once ๐Ÿ™ˆ

2.6.1. Positional Arguments ๐Ÿ“

positional arguments are given to a command that figures out their meaning from their order. when a command takes positional arguments, all of them must be supplied and they must be in the order prescribed ๐Ÿ’…

2.6.2. Tagged Arguments ๐Ÿฆ‹

this document has tagged arguments in the style of CommonLISP ๐ŸŒˆ they're also kinda like flags on command-line tools, lowkey.

a tagged argument is an argument for a command that starts with ":" followed by a tag naming the argument, like ":contains" โœจ it means that zero or more of the next tokens have some particular meaning depending on the argument. those next tokens can be literal data, but they're never blocks.

tagged arguments are like positional arguments, except the meaning comes from the tag instead of from the command ๐Ÿงš

tagged arguments must appear before positional arguments, but they can show up in any order with other tagged arguments. for simplicity of the spec, the syntax definitions for commands don't express this, but they can still be reordered however you want as long as they come before positional arguments. tagged arguments may be mixed with optional arguments ๐Ÿ‘‘

tagged arguments SHOULD NOT take tagged arguments as arguments ๐Ÿ™ˆ

2.6.3. Optional Arguments ๐ŸŒธ

optional arguments are exactly like tagged arguments, except they can be left out, in which case a default value is implied. because optional arguments tend to make scripts shorter, people have used them way more than tagged arguments ๐Ÿ’–

one especially noteworthy case is the ":comparator" argument, which lets you pick which comparator [COLLATION] gets used to compare two strings, since different languages can put UTF-8 [UTF-8] strings in different orders ๐ŸŒˆ

2.6.4. Types of Arguments ๐Ÿซถ

in the abstract, arguments can be literal data, tests, or blocks of commands ๐ŸŽ€ that means an "if" control structure is just a command that happens to take a test and a block as arguments and may run the block of code.

however, that abstraction is ambiguous from a parsing standpoint ๐Ÿ’€

the grammar in section 8.2 gives a parsable version of this: arguments are string lists (string-lists), numbers, and tags, which may be followed by a test or a test list (test-list), which may be followed by a block of commands. no more than one test or test list, or more than one block of commands, may be used, and commands that end with a block of commands don't end with semicolons ๐Ÿฆ‹


2.7. String Comparison ๐Ÿ”ฅ

when you match one string against another, there are a bunch of ways to do it ๐Ÿ’… these are done with three types of matches: an exact match, a substring match, and a wildcard glob-style match. they're described below.

to allow matches across character sets and case insensitivity, Sieve uses the comparators defined in the Internet Application Protocol Collation Registry [COLLATION] โœจ

but when a string is the name of a header, the comparator is never user-specified ๐Ÿ™… header comparisons are always done with the "i;ascii-casemap" operator, meaning case-insensitive comparisons, because that's how the message specification [IMAIL] defines things ๐Ÿ’œ

2.7.1. Match Type ๐ŸŒˆ

commands that do string comparisons may have an optional match type argument ๐Ÿ“ the three match types in this spec are ":contains", ":is", and ":matches".

the ":contains" match type is a substring match. if the value argument contains the key argument as a substring, it's a match. for example, the string "frobnitzm" contains "frob" and "nit", but not "fbm" ๐Ÿฆ‹ the empty key ("") is contained in all values.

the ":is" match type is an absolute match. if the contents of the first string are absolutely the same as the contents of the second, they match. only the string "frobnitzm" is the string "frobnitzm" ๐ŸŽ€ the empty key ("") only ":is" matches with the empty value.

the ":matches" match type is a wildcard match using the characters "" and "?", and the entire value must be matched ๐Ÿซถ "" matches zero or more characters in the value, and "?" matches a single character in the value, where the comparator in use (see section 2.7.3) decides what a character is. for example, the comparators "i;octet" and "i;ascii-casemap" define a character as a single octet, so "?" will always match exactly one octet when either of those is in use ๐Ÿงš a Unicode-based comparator, on the other hand, would define a character as any UTF-8 octet sequence encoding one Unicode character, so "?" might match more than one octet ๐ŸŒธ "?" and "" can be escaped as "\?" and "\" in strings to match against themselves. the first backslash escapes the second backslash, and together they escape the "*". it's awkward, but it's pretty normal in a bunch of programming languages that use globs and regular expressions ๐Ÿ™„

to pick what type of match happens, commands that support matching take the optional arguments ":matches", ":is", and ":contains" ๐Ÿ’– commands default to ":is" matching if you don't give a match type argument. note that these modifiers interact with comparators. in particular, only comparators that support the "substring match" operation work with ":contains" or ":matches" ๐Ÿ“ it's an error to use a comparator with ":contains" or ":matches" that isn't compatible with it.

it's an error to give more than one of these arguments to a given command ๐Ÿ™ˆ

for convenience, the "MATCH-TYPE" syntax element is defined here as follows:

Syntax:   ":is" / ":contains" / ":matches"

2.7.2. Comparisons across Character Sets ๐ŸŒ

messages can involve a bunch of character sets ๐Ÿ’… so for comparisons to work across them, implementations SHOULD implement the following behavior:

Comparisons are performed on octets. Implementations convert text from header fields in all charsets [MIME3] to Unicode, encoded as UTF-8, as input to the comparator (see section 2.7.3). Implementations MUST be capable of converting US-ASCII, ISO-8859-1, the US-ASCII subset of ISO-8859-* character sets, and UTF-8. Text that the implementation cannot convert to Unicode for any reason MAY be treated as plain US-ASCII (including any [MIME3] syntax) or processed according to local conventions. An encoded NUL octet (character zero) SHOULD NOT cause early termination of the header content being compared against.

if implementations don't support the above behavior, they MUST conform to the following:

No two strings can be considered equal if one contains octets greater than 127.

2.7.3. Comparators ๐Ÿ’–

okay so here's the vibe: you can pair a match type with a comparator name so your matches go language-independent and case-independent. The [COLLATION] registry (the Internet Application Protocol Collation Registry) is the framework that describes and names these comparators ๐Ÿงš

Every implementation MUST support "i;octet" (it just compares octets) and "i;ascii-casemap" (which treats uppercase and lowercase letters in the US-ASCII subset of UTF-8 as the same) ๐ŸŽ€ If you don't specify one, the default is "i;ascii-casemap".

Some comparators aren't usable with substring matches. They only work with :is. If you try to pair one with :matches or :contains, that's an error ๐Ÿ™ˆ

Sieve treats a comparator result of "undefined" the same as "no-match". This base spec doesn't give you a way to directly detect invalid comparator input, so keep that in mind ๐Ÿ’…

You use the :comparator option on any command that supports matching. It's followed by a string with the comparator's name. For convenience, the syntax is shortened to COMPARATOR, and it shows up in several tests like this:

Syntax:   ":comparator" <comparator-name: string>

Here's the example. It's giving case-sensitive drama ๐Ÿ˜ญ

Example:  if header :contains :comparator "i;octet" "Subject"
                "MAKE MONEY FAST" {
             discard;
          }

This would discard any message with subjects like "You can MAKE MONEY FAST", but NOT "You can Make Money Fast", because the comparator is case-sensitive. Iconic behaviour, honestly ๐Ÿ‘‘

Comparators other than "i;octet" and "i;ascii-casemap" must be declared with require, because they're extensions ๐Ÿ”ฅ If a comparator you declared with require isn't known, that's an error and execution fails. And if a comparator isn't declared with require, that's also an error, even if it's actually supported. (See section 2.10.5.) Not me making the rules, but okay ๐Ÿ’€

Both ":matches" and ":contains" are compatible with the "i;octet" and "i;ascii-casemap" comparators, so you can use them with those โœจ

It's an error to give more than one of these arguments to a given command ๐Ÿšซ

2.7.4. Comparisons against Addresses ๐Ÿฆ‹

Addresses get represented as strings all the time, and they have structure. Being able to compare just the local-part or just the domain is super useful, so some tests that only work on addresses take an extra optional argument that says what they act on ๐Ÿ’œ

Those optional arguments are ":localpart", ":domain", and ":all". They act on the local-part (the left side), the domain-part (the right side), and the whole address, respectively.

If an address isn't syntactically valid, it won't be matched by tests that specify ":localpart" or ":domain". Bestie, that's a real gotcha ๐Ÿ˜ญ

The kind of comparison you get, like whether it's case-insensitive, is set by a comparator argument to the test.

If you leave out the address-part, the default is ":all" ๐Ÿ“

It's an error to give more than one of these arguments to a given command.

For convenience, the ADDRESS-PART syntax element is defined here like this:

Syntax:   ":localpart" / ":domain" / ":all"

2.8. Blocks ๐ŸŒธ

Blocks are sets of commands wrapped in curly braces and passed as the final argument to a command. That command is a control structure, and when it runs, it decides how many times the commands inside the block get executed ๐Ÿงš

With the commands in this memo, there are no loops. The control structures (if, elsif, and else) run a block either once or not at all. No loops, no drama ๐Ÿ’…

2.9. Commands ๐ŸŽ€

Sieve scripts are sequences of commands. Commands can take any of the tokens above as arguments, and those arguments can be tagged or positional. Not every command takes every argument type โœจ

There are three kinds of commands: test commands, action commands, and control commands ๐Ÿ‘‘

The simplest one is the action command. It's an identifier, then zero or more arguments, then a semicolon. Action commands can't take tests or blocks as arguments. The actions in this document are:

  • keep, to save the message in the default location
  • fileinto, to save the message in a specific mailbox
  • redirect, to forward the message to another address
  • discard, to silently throw away the message

A control command affects the parsing or the flow of execution of the Sieve script somehow. A control structure is a control command that ends with a block instead of a semicolon ๐Ÿฆ‹

A test command is used as part of a control command. It decides whether or not the block given to that control command gets executed ๐ŸŒˆ

2.10. Evaluation ๐Ÿ’œ

2.10.1. Action Interaction

Some actions just can't be used together because the result would be absurd ๐Ÿ™ˆ These restrictions are noted throughout this memo.

Extension actions MUST state how they interact with the actions defined in this specification. Keep that receipt, bestie ๐Ÿงพ

2.10.2. Implicit Keep

Past experience with filtering systems shows that cases tend to get missed in scripts. So to prevent errors, Sieve has an "implicit keep" ๐Ÿซถ

An implicit keep is a keep action (see section 4.3) that gets performed when there's no action that cancels it.

An implicit keep is performed if a message isn't written to a mailbox, isn't redirected to a new address, and isn't explicitly thrown out. In other words, if a fileinto, a keep, a redirect, or a discard is performed, the implicit keep is not.

Some actions may be defined so they don't cancel the implicit keep. Those actions may not directly affect delivery, and they're used for their side effects. None of the actions in this document fit that description, but extension actions may โœจ

For instance, with any of the short messages offered above, the following script produces no actions.

Example:  if size :over 500K { discard; }

As a result, the implicit keep is taken ๐Ÿ“

2.10.3. Message Uniqueness in a Mailbox

Implementations SHOULD NOT deliver a message to the same mailbox more than once, even if a script explicitly asks for it to be written to a mailbox twice ๐Ÿ™ˆ

How two messages are tested for equality is implementation-defined.

If a script asks for a message to be written to a mailbox twice, that MUST NOT be treated as an error. Double-filing is not a crime ๐Ÿ’–

2.10.4. Limits on Numbers of Actions

Site policy MAY limit the number of actions taken and MAY impose restrictions on which actions can be used together. If a script hits a policy limit on the number of actions taken for a particular message, an error occurs ๐Ÿšซ

Implementations MUST allow at least one keep or one fileinto. If fileinto isn't implemented, implementations MUST allow at least one keep ๐ŸŽ€

2.10.5. Extensions and Optional Features

Because mail systems differ so much in what they can do, several features of this specification are optional. Before any of these extensions can be executed, they must be declared with the "require" action ๐ŸŒˆ

If an extension isn't enabled with "require", implementations MUST treat it as if they didn't support it at all. This keeps scripts from having their behaviour changed by extensions the script author might not even know about ๐Ÿซถ

Implementations MUST NOT execute any Sieve script test or command that comes after "require" if one of the required extensions is unavailable.

Note: The reason for this restriction is that prior experiences with languages such as LISP and Tcl suggest that this is a workable way of noting that a given script uses an extension. ๐Ÿง 

Extensions that define actions MUST state how they interact with the actions discussed in the base specification ๐Ÿ’…

2.10.6. Errors

In any programming language there are compile-time errors and run-time errors ๐Ÿ’€

Compile-time errors are errors in syntax that you can detect if a syntax check is done.

Run-time errors can't be detected until the script actually runs. That includes transient failures like a full disk, but also issues like invalid combinations of actions ๐Ÿ˜ญ

When an error occurs in a Sieve script, all processing stops. Full stop ๐Ÿ›‘

Implementations MAY choose to do a full parse, then evaluate the script, then do all the actions. They might even go so far as to make execution atomic, meaning either all actions are executed or none are ๐Ÿ‘‘

Other implementations may choose to parse and run at the same time. Those are simpler, but they have issues with partial failure, where some actions happen and others don't ๐Ÿ™ˆ

Implementations MUST perform syntactic, semantic, and run-time checks on code that is actually executed. Implementations MAY perform those checks, or any part of them, on code that isn't reached during execution ๐Ÿ”

When an error happens, implementations MUST notify the user that an error occurred and which actions (if any) were taken, and then do an implicit keep ๐ŸŽ€

2.10.7. Limits on Execution

Implementations may limit certain constructs. However, this specification puts a lower bound on some of these limits โœจ

Implementations MUST support fifteen levels of nested blocks.

Implementations MUST support fifteen levels of nested test lists.


3. Control Commands ๐ŸŽ€

Control structures are needed to allow for multiple and conditional actions. Iconic, really ๐Ÿ‘‘

3.1. Control if ๐Ÿฆ‹

There are three pieces to if: "if", "elsif", and "else". Each one is actually a separate command in terms of the grammar. However, an elsif or else MUST only follow an if or elsif. If these conditions aren't met, an error occurs ๐Ÿšซ

Usage:   if <test1: test> <block1: block>

Usage:   elsif <test2: test> <block2: block>

Usage:   else <block3: block>

The semantics are similar to those of the many other programming languages these control structures show up in ๐Ÿ’… When the interpreter sees an "if", it evaluates the test attached to it. If the test is true, it executes the block attached to it.

If the "if" test is false, the interpreter evaluates the test of the first "elsif" (if there is one). If that elsif's test is true, it runs the elsif's block. An elsif can be followed by another 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 one, and none of the if or elsif tests were true, the interpreter runs the else's block ๐Ÿงš

This gives you a way of running exactly one of the blocks in the chain. Chef's kiss ๐ŸคŒ

In the following example, both messages A and B get dropped.

Example:  require "fileinto";
          if header :contains "from" "coyote" {
             discard;
          } elsif header :contains ["subject"] ["$$$"] {
             discard;
          } else {
             fileinto "INBOX";
          }

When the script below runs over message A, it redirects the message to acm@example.com. For message B, it redirects to postmaster@example.com. Any other message is redirected to field@example.com ๐Ÿ’Œ

Example:  if header :contains ["From"] ["coyote"] {
             redirect "acm@example.com";
          } elsif header :contains "Subject" "$$$" {
             redirect "postmaster@example.com";
          } else {
             redirect "field@example.com";
          }

Note that this definition prohibits the "... else if ..." sequence used by C. This is intentional, because this construct produces a shift-reduce conflict. So no else if in Sieve, the vibe is "use elsif or bust" ๐Ÿ’…โœจ

3.2. Control require ๐ŸŽ€

Usage:

require <capabilities: string-list>

okay so require is basically a script going "hey, I'm using this extension!" ๐Ÿงš You have to declare it before you can use the extension, see section 2.10.5 for the tea. And you can declare a bunch of capabilities in one single require, bestie, that's so efficient ๐Ÿ’…

The require command, if present, MUST be used before anything other than a require can be used. An error occurs if a require appears after a command other than require. ๐Ÿ˜ญ no being late to the party!

Example:

require ["fileinto", "reject"];

Example:

require "fileinto";
require "vacation";

3.3. Control stop โœ‹

Usage:

stop

The "stop" action ends all processing. Everything just stops, period ๐Ÿ›‘. If the implicit keep hasn't been cancelled, then it gets taken.


4. Action Commands ๐ŸŽฌ

This doc gives us four actions you can take on a message: keep, fileinto, redirect, and discard. โœจ

Implementations MUST support the "keep", "discard", and "redirect" actions. ๐Ÿ’–

Implementations SHOULD support "fileinto". ๐Ÿ“

Implementations MAY limit the number of certain actions taken (see section 2.10.4).

4.1. Action fileinto ๐Ÿ“

Usage:

fileinto <mailbox: string>

The "fileinto" action delivers the message into the specified mailbox. It's the whole point of filing ๐Ÿ—‚๏ธ Implementations SHOULD support fileinto, but in some environments this may be impossible. Implementations MAY place restrictions on mailbox names, and use of an invalid mailbox name MAY be treated as an error or result in delivery to an implementation-defined mailbox. If the specified mailbox doesn't exist, the implementation MAY treat it as an error, create the mailbox, or deliver the message to an implementation-defined mailbox. If the implementation uses a different encoding scheme than UTF-8 for mailbox names, it SHOULD reencode the mailbox name from UTF-8 to its encoding scheme. For example, the Internet Message Access Protocol [IMAP] uses modified UTF-7, so a mailbox argument of "odds & ends" would appear in IMAP as "odds &- ends". the way the encoding just rearranges your folder names ๐Ÿ™ˆ

The capability string for use with the require command is "fileinto".

In the following script, message A is filed into mailbox "INBOX.harassment". ๐Ÿšซ

Example:

require "fileinto";
if header :contains ["from"] "coyote" {
   fileinto "INBOX.harassment";
}

4.2. Action redirect ๐Ÿ“จ

Usage:

redirect <address: string>

The "redirect" action sends the message to another user at a supplied address, just like a mail forwarding feature does. It makes no changes to the message body or existing headers, but it may add new headers. Existing Received headers MUST be preserved, and the count of Received headers in the outgoing message MUST be larger than the same count on the message as received by the implementation. (An implementation that adds a Received header before processing the message does not need to add another when redirecting.) okay this is a whole vibe of making sure the trail never gets erased ๐Ÿ“

The message is sent back out with the address from the redirect command as an envelope recipient. Implementations MAY combine separate redirects for a given message into a single submission with multiple envelope recipients. (This is not a Mail User Agent (MUA)-style forward, which creates a new message with a different sender and message ID, wrapping the old message in a new one.) ๐Ÿ™…โ€โ™€๏ธ

The envelope sender address on the outgoing message is chosen by the sieve implementation. It MAY be copied from the message being processed. However, if the message being processed has an empty envelope sender address, the outgoing message MUST also have an empty envelope sender address. This last requirement is imposed to prevent loops in the case where a message is redirected to an invalid address, which then returns a delivery status notification that also ends up being redirected to the same invalid address. ๐Ÿ” loop prevention is literally iconic.

A simple script can be used for redirecting all mail:

Example:

redirect "bart@example.com";

Implementations MUST take measures to implement loop control, possibly including adding headers to the message or counting Received headers as specified in section 6.2 of [SMTP]. If an implementation detects a loop, it causes an error. ๐Ÿšจ

Implementations MUST provide means of limiting the number of redirects a Sieve script can perform. See section 10 for more details.

Implementations MAY ignore a redirect action silently due to policy reasons. For example, an implementation MAY choose not to redirect to an address that is known to be undeliverable. Any ignored redirect MUST NOT cancel the implicit keep. ๐Ÿซถ

4.3. Action keep ๐Ÿ 

Usage:

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 filing the message into the user's main mailbox. This command gives you a way to do this without needing to know the name of the user's main mailbox, so you can call it without needing to understand the user's setup or the underlying mail system. Main character energy, no setup required โœจ

For instance, in an implementation where the IMAP server is running scripts on behalf of the user at time of delivery, a keep command is equivalent to fileinto "INBOX".

Example:

if size :under 1M { keep; } else { discard; }

Note that the above script is identical to the one below.

Example:

if not size :under 1M { discard; }

4.4. Action discard ๐Ÿ—‘๏ธ

Usage:

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.) ๐Ÿ’…

Discard MUST be silent; that is, it MUST NOT return a non-delivery notification of any kind ([DSN], [MDN], or otherwise). ๐Ÿคซ

In the following script, any mail from "idiot@example.com" is thrown out.

Example:

if header :contains ["from"] ["idiot@example.com"] {
   discard;
}
While discard is an important part of this language, it has the potential to create serious problems for users. Students who leave themselves logged in to an unattended machine in a public computer lab may find their script changed to just "discard". In order to protect users in this situation (along with similar situations), implementations MAY keep messages destroyed by a script for an indefinite period, and MAY disallow scripts that throw out all mail. ๐Ÿ˜ฑ

5. 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.

5.1. Test address ๐Ÿ’Œ

Usage:

address [COMPARATOR] [ADDRESS-PART] [MATCH-TYPE]
        <header-list: string-list> <key-list: string-list>

The "address" test matches Internet addresses in structured headers that contain addresses. It returns true if any header contains any key in the specified part of the address, as modified by the comparator and the match keyword. Whether there are other addresses present in the header doesn't affect this test. This test does not provide any way to determine whether an address is the only address in a header.

Like envelope and header, this test returns true if any combination of the header-list and key-list arguments match, and returns false otherwise.

Internet email addresses [IMAIL] have the somewhat awkward characteristic that the local-part to the left of the at-sign is considered case sensitive, while the domain-part to the right of the at-sign is case insensitive. The "address" command doesn't deal with this itself, but it provides the ADDRESS-PART argument so users can deal with it. it's giving "you decide how picky to be" ๐Ÿฆ‹

The address primitive never acts on the phrase part of an email address or on comments within that address. It also never acts on group names, although it does act on the addresses within the group construct.

Implementations MUST restrict the address test to headers that contain addresses, but MUST include at least From, To, Cc, Bcc, Sender, Resent-From, and Resent-To, and it SHOULD include any other header that utilizes an "address-list" structured header body. ๐Ÿ“ฌ

Example:

if address :is :all "from" "tim@example.com" {
   discard;
}

5.2. Test allof ๐Ÿค

Usage:

allof <tests: test-list>

The "allof" test performs a logical AND on the tests supplied to it. Both sides have to be true for it to be true ๐Ÿ‘‘

Example:

allof (false, false)  =>   false
allof (false, true)   =>   false
allof (true,  true)   =>   true

The allof test takes as its argument a test-list.

5.3. Test anyof ๐ŸŒˆ

Usage:

anyof <tests: test-list>

The "anyof" test performs a logical OR on the tests supplied to it. Just one true is enough to slay ๐Ÿ’–

Example:

anyof (false, false)  =>   false
anyof (false, true)   =>   true
anyof (true,  true)   =>   true

5.4. Test envelope ๐Ÿ“ฎ

Usage:

envelope [COMPARATOR] [ADDRESS-PART] [MATCH-TYPE]
         <envelope-part: string-list> <key-list: string-list>

The "envelope" test is true if the specified part of the [SMTP] (or equivalent) envelope matches the specified key. This specification defines the interpretation of the (case insensitive) "from" and "to" envelope-parts. Additional envelope-parts may be defined by other extensions. Implementations SHOULD consider unknown envelope parts an error.

If one of the envelope-part strings is (case insensitive) "from", then matching occurs against the FROM address used in the SMTP MAIL command. The null reverse-path is matched against as the empty string, regardless of the ADDRESS-PART argument specified.

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 that 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 list and key-list arguments match, and returns false otherwise.

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. The capability string for use with the require command is "envelope". ๐Ÿงš

Example:

require "envelope";
if envelope :all :is "from" "tim@example.com" {
   discard;
}

5.5. Test exists ๐Ÿ”

Usage:

exists <header-names: string-list>

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;
}

5.6. Test false ๐Ÿšซ

Usage:

false

The "false" test always evaluates to false. Literally never a vibe ๐Ÿ’€

5.7. Test header

Okay bestie, the header test is pure pattern-matching realness ๐Ÿ’… It checks the value of any of the named headers, ignoring leading and trailing whitespace, and sees whether it matches any key. Its usage looks like this:

Usage:   header [COMPARATOR] [MATCH-TYPE]
            <header-names: string-list> <key-list: string-list>

The match type is the optional argument, and if you don't pick one it defaults to :is ๐ŸŽ€ (that's from section 2.6, so go check it if you're curious).

Just like address and envelope, this test is true if any combo of the header-names list and key-list arguments matches, and false otherwise. Very team-player of it ๐Ÿซถ

Now for the plot twist ๐Ÿ˜ญ If a header in the header-names argument exists, it contains the empty key (""). BUT if the named header isn't there at all, it doesn't match any key, not even the empty one. So if a message had this header:

X-Caffeine: C8H10N4O2

these tests evaluate like so:

header :is ["X-Caffeine"] [""]         => false
header :contains ["X-Caffeine"] [""]   => true

Iconic โ˜• Because it exists, :contains with the empty key is true, and :is with the empty key is false, since the value isn't actually empty.

If you want to check that a header is either missing or has no non-whitespace characters at all, you can use a negated header test:

not header :matches "Cc" "?*"

It's giving "absent or basically empty" energy ๐Ÿฆ‹


5.8. Test not

Usage: not <test1: test>

The not test takes another test as its argument and flips the result. not false becomes true, and not true becomes false. Simple, but so powerful ๐Ÿ’œ


5.9. Test size

Usage: size <":over" / ":under"> <limit: number>

The size test is all about how big the message is ๐Ÿ“ You give it a tagged argument of either ":over" or ":under", followed by a number that represents the size.

If you pick ":over" and the message is bigger than your number, the test is true. Otherwise it's false.

If you pick ":under" and the message is smaller than your number, the test is true. Otherwise it's false.

You have to specify exactly one of ":over" or ":under". Anything else is an error, so don't try to be sneaky ๐Ÿ’€

The size of a message is defined as the number of octets in the [IMAIL] representation of the message.

Also, a little heads-up: for a message that is exactly 4,000 octets, it's neither ":over" nor ":under" 4000 octets. It's just in the middle, no cap ๐Ÿ™ˆ


5.10. Test true

Usage: true

The true test always evaluates to true. It's the friend who always shows up and never causes drama โœจ


6. Extensibility ๐ŸŒˆ

New control commands, actions, and tests can be added to the language. Sites need to make these features known to their users, though. This document doesn't define a way to discover which extensions a server supports, so you'll have to ask around, bestie ๐Ÿงš

Any extensions to this language MUST define a capability string that uniquely identifies that extension. Capability strings are case-sensitive, so "foo" and "FOO" are two totally different capabilities, which is so chaotic ๐Ÿ˜ญ If a new version of an extension changes what a previously defined extension does, it MUST use a different name. Extensions can also register a whole family of related capabilities by registering just a unique prefix. The "comparator-" prefix is the classic example. That prefix MUST end with a "-" and MUST NOT overlap any existing registrations. Keep the drama off the group chat ๐Ÿ’…

Here's a neat idea. In a situation where there's a script submission protocol and an extension advertisement mechanism that knows the details of this language, submitted scripts can be checked against the mail server. That way you can stop someone from using an extension the server doesn't support. Preventing crashes, we love to see it ๐Ÿ”ฅ

Extensions MUST state how they interact with the constraints defined in section 2.10, for example whether they cancel the implicit keep, and which actions they're compatible and incompatible with. Extensions MUST NOT change the behavior of the "require" control command or alter the interpretation of the argument to the "require" control. Hands off require, it's literally its whole job ๐Ÿ™…โ€โ™€๏ธ

Extensions that can submit new email messages or otherwise generate new protocol requests MUST consider loop suppression, at least to document any security considerations. Loops are the villain arc nobody wants ๐ŸŒ€

6.1. Capability String

Capability strings are typically short strings that describe which capabilities a server supports. Think of them as the server's little bio ๐Ÿ’–

Capability strings that begin with "vnd." represent vendor-defined extensions. These aren't defined by Internet standards or RFCs, but they still get registered with IANA so nobody steps on anybody else's toes ๐Ÿซถ Extensions starting with "vnd." SHOULD be followed by the name of the vendor and product, such as "vnd.acme.rocket-sled". Yes, a rocket-powered sled is an amazing extension name, I'm not even mad ๐Ÿš€

The following capability strings are defined by this document:

  • encoded-character: The string "encoded-character" means the implementation supports the interpretation of "${hex:...}" and "${unicode:...}" inside strings. Very cute, very useful ๐ŸŒธ
  • envelope: The string "envelope" means the implementation supports the "envelope" command ๐Ÿ’œ
  • fileinto: The string "fileinto" means the implementation supports the "fileinto" command ๐Ÿ“ฌ
  • comparator-: The string "comparator-elbonia" is provided if the implementation supports the "elbonia" comparator. So every implementation has at least the "comparator-i;octet" and "comparator-i;ascii-casemap" capabilities. Even so, these comparators can be used without being declared with require. Bonus points for not needing the paperwork ๐ŸŽ€

6.2. IANA Considerations

To provide a standard set of extensions, IANA maintains a registry. It contains both vendor-controlled capability names (starting with "vnd.") and IETF-controlled capability names. Vendor-controlled names can be registered on a first-come, first-served basis by applying to IANA with the form in the following section. Registration of capability prefixes that don't begin with "vnd." REQUIRES a standards track or IESG-approved experimental RFC. That's the VIP line, sorry not sorry ๐Ÿ‘‘

Extensions designed for interoperable use SHOULD use IETF-controlled capability names. Team interop, let's gooo ๐Ÿฆ‹

6.2.1. Template for Capability Registrations

If you're registering a new Sieve extension with IANA, use this template:

To: iana@iana.org
Subject: Registration of new Sieve extension

Capability name: [the string for use in the 'require' statement]
Description:     [a brief description of what the extension adds
                  or changes]
RFC number:      [for extensions published as RFCs]
Contact address: [email and/or physical address to contact for
                  additional information]

Fill in the blanks and you're basically a registered legend ๐Ÿ“

6.2.2. Handling of Existing Capability Registrations

To line up the existing capability registrations with the new template, IANA has modified each one as follows:

  1. The "capability name" and "capability arguments" fields have been eliminated.
  2. The "capability keyword" field has been renamed to "Capability name".
  3. An empty "Description" field has been added.
  4. The "Standards Track/IESG-approved experimental RFC number" field has been renamed to "RFC number".
  5. The "Person and email address to contact for further information" field should be renamed to "Contact address".

It's a full glow-up for the registry, honestly iconic ๐Ÿ’…โœจ

6.2.3. Initial Capability Registrations

This RFC updates the following entries in the IANA registry for Sieve extensions. These are the originals, and they're coming in hot ๐Ÿ”ฅ

Capability name: encoded-character
Description:     changes the interpretation of strings to allow
                 arbitrary octets and Unicode characters to be
                 represented using US-ASCII
RFC number:      RFC 5228 (Sieve base spec)
Contact address: The Sieve discussion list <ietf-mta-filters@imc.org>

Capability name: fileinto
Description:     adds the 'fileinto' action for delivering to a
                 mailbox other than the default
RFC number:      RFC 5228 (Sieve base spec)
Contact address: The Sieve discussion list <ietf-mta-filters@imc.org>

Capability name: envelope
Description:     adds the 'envelope' test for testing the message
                 transport sender and recipient address
RFC number:      RFC 5228 (Sieve base spec)
Contact address: The Sieve discussion list <ietf-mta-filters@imc.org>

Capability name: comparator-* (anything starting with "comparator-")
Description:     adds the indicated comparator for use with the
                 :comparator argument
RFC number:      RFC 5228 (Sieve base spec) and [COLLATION]
Contact address: The Sieve discussion list <ietf-mta-filters@imc.org>

6.3. Capability Transport

Figuring out how to advertise which capabilities an implementation supports is tricky, because there's a wide range of possible implementations ๐Ÿ˜ตโ€๐Ÿ’ซ Still, any such mechanism should have one key property: the implementation can advertise its complete set of supported extensions. No hiding your best features ๐ŸŒŸ


7. Transmission ๐Ÿ“ฌ

The [MIME] type for a Sieve script is "application/sieve". Main character energy ๐Ÿ’–

The registration of this type for RFC 2048 requirements is updated as follows:

 Subject: Registration of MIME media type application/sieve

 MIME media type name: application
 MIME subtype name: sieve
 Required parameters: none
 Optional parameters: none
 Encoding considerations: Most Sieve scripts will be textual,
    written in UTF-8.  When non-7bit characters are used,
    quoted-printable is appropriate for transport systems
    that require 7bit encoding.
 Security considerations: Discussed in section 10 of this RFC.
 Interoperability considerations: Discussed in section 2.10.5
    of this RFC.
 Published specification: this RFC.
 Applications that use this media type: sieve-enabled mail
   servers and clients
 Additional information:
   Magic number(s):
   File extension(s): .siv .sieve
   Macintosh File Type Code(s):

 Person & email address to contact for further information:
    See the discussion list at ietf-mta-filters@imc.org.
 Intended usage:
    COMMON
 Author/Change controller:
    The SIEVE WG, delegated by the IESG.

8. Parsing ๐Ÿงฉ

The Sieve grammar is split into tokens and a separate grammar, the same way most programming languages do it. Additional rules are supplied here for common arguments to various language facilities, so the grammar gets a little extra love right here ๐Ÿ’œ

8.1. Lexical Tokens โœจ

okay so the first thing you need to know is that Sieve scripts are encoded in UTF-8 ๐Ÿซถ The whole thing assumes you've got a valid UTF-8 encoding, and the special characters in Sieve scripts are all US-ASCII, which is so clean ๐Ÿงš

Here are the tokens in Sieve:

  • identifiers
  • tags
  • numbers
  • quoted strings
  • multi-line strings
  • other separators

Identifiers, tags, and numbers are case-insensitive, but quoted strings and multi-line strings are case-sensitive ๐Ÿ’… So be careful with your strings, bestie ๐Ÿ˜ญ

Blanks, horizontal tabs, CRLFs, and comments ("whitespace") get ignored, except when they separate tokens. Some whitespace is required to separate tokens that would otherwise sit right next to each other, and it's also needed in specific places inside multi-line strings ๐ŸŽ€ CR and LF can only show up as a CRLF pair, no solo CRs or LFs allowed, ngl ๐Ÿ™ˆ

The other separators are single characters, and they're spelled out in the grammar. The lexical structure of Sieve is defined in the following grammar (written in [ABNF]) ๐ŸŒธ

bracket-comment    = "/*" *not-star 1*STAR
                     *(not-star-slash *not-star 1*STAR) "/"
                       ; No */ allowed inside a comment.
                       ; (No * is allowed unless it is the last
                       ; character, or unless it is followed by a
                       ; character that isn't a slash.)

comment            = bracket-comment / hash-comment

hash-comment       = "#" *octet-not-crlf CRLF

identifier         = (ALPHA / "_") *(ALPHA / DIGIT / "_")

multi-line         = "text:" *(SP / HTAB) (hash-comment / CRLF)
                     *(multiline-literal / multiline-dotstart)
                     "." CRLF

multiline-literal  = [ octet-not-period *octet-not-crlf ] CRLF

multiline-dotstart = "." 1*octet-not-crlf CRLF
                       ; A line containing only "." ends the
                       ; multi-line.  Remove a leading '.' if
                       ; followed by another '.'.

not-star           = CRLF / %x01-09 / %x0B-0C / %x0E-29 / %x2B-FF
                       ; either a CRLF pair, OR a single octet
                       ; other than NUL, CR, LF, or star

not-star-slash     = CRLF / %x01-09 / %x0B-0C / %x0E-29 / %x2B-2E /
                     %x30-FF
                       ; either a CRLF pair, OR a single octet
                       ; other than NUL, CR, LF, star, or slash

number             = 1*DIGIT [ QUANTIFIER ]

octet-not-crlf     = %x01-09 / %x0B-0C / %x0E-FF
                       ; a single octet other than NUL, CR, or LF

octet-not-period   = %x01-09 / %x0B-0C / %x0E-2D / %x2F-FF
                       ; a single octet other than NUL,
                       ; CR, LF, or period

octet-not-qspecial = %x01-09 / %x0B-0C / %x0E-21 / %x23-5B / %x5D-FF
                       ; a single octet other than NUL,
                       ; CR, LF, double-quote, or backslash

QUANTIFIER         = "K" / "M" / "G"

quoted-other       = "\" octet-not-qspecial
                       ; represents just the octet-no-qspecial
                       ; character.  SHOULD NOT be used

quoted-safe        = CRLF / octet-not-qspecial
                       ; either a CRLF pair, OR a single octet other
                       ; than NUL, CR, LF, double-quote, or backslash

quoted-special     = "\" (DQUOTE / "\")
                       ; represents just a double-quote or backslash

quoted-string      = DQUOTE quoted-text DQUOTE

quoted-text        = *(quoted-safe / quoted-special / quoted-other)

STAR               = "*"

tag                = ":" identifier

white-space        = 1*(SP / CRLF / HTAB) / comment

8.2. Grammar ๐Ÿง 

This is the grammar of Sieve after it's been lexically interpreted, so no whitespace or comments show up in here. The start symbol is "start" ๐Ÿ‘‘

argument     = string-list / number / tag

arguments    = *argument [ test / test-list ]

block        = "{" commands "}"

command      = identifier arguments (";" / block)

commands     = *command

start        = commands

string       = quoted-string / multi-line

string-list  = "[" string *("," string) "]" / string
                 ; if there is only a single string, the brackets
                 ; are optional

test         = identifier arguments

test-list    = "(" test *("," test) ")"

8.3. Statement Elements ๐Ÿฆ‹

These elements are collected from the "Syntax" sections elsewhere in this document. They're written here in [ABNF] syntax so that extensions can modify them, which is honestly such a smart move ๐Ÿ’œ

ADDRESS-PART = ":localpart" / ":domain" / ":all"
COMPARATOR   = ":comparator" string

MATCH-TYPE   = ":is" / ":contains" / ":matches"

9. Extended Example ๐Ÿ“

Here's an extended example of a Sieve script ๐Ÿ’– Heads up, it doesn't make use of the implicit keep, so pay attention to that ๐Ÿ‘€

 #
 # Example Sieve Filter
 # Declare any optional features or extension used by the script
 #
 require ["fileinto"];

 #
 # Handle messages from known mailing lists
 # Move messages from IETF filter discussion list to filter mailbox
 #
 if header :is "Sender" "owner-ietf-mta-filters@imc.org"
         {
         fileinto "filter";  # move to "filter" mailbox
         }
 #
 # Keep all messages to or from people in my company
 #
 elsif address :DOMAIN :is ["From", "To"] "example.com"
         {
         keep;               # keep in "In" mailbox
         }

 #
 # 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*"])
         {
         fileinto "spam";   # move to "spam" mailbox
         }
 else
         {
         # Move all other (non-company) mail to "personal"
         # mailbox.
         fileinto "personal";
         }

10. Security Considerations ๐Ÿ”ฅ

Users must get their mail. ๐Ÿ˜ญ Whatever implementations use to store the user-defined filtering scripts has to protect them from unauthorized modification, because this is what keeps the mail system's integrity intact. An attacker who can modify a script can make mail get discarded, rejected, or forwarded to an unauthorized recipient. It's also possible that Sieve scripts could expose private information, like mailbox names or the email addresses of favored (or disfavored) correspondents. Because of that, scripts SHOULD also be protected from unauthorized retrieval.

Several commands, like "discard", "redirect", and "fileinto", let you take actions that can be potentially very dangerous ๐Ÿ’€

Using the "redirect" command to generate notifications can easily overwhelm the target address, especially if that address wasn't designed to handle large messages ๐Ÿ“ฌ

Allowing a single script to redirect to multiple destinations can be used to amplify the number of messages in an attack. And if loop detection isn't properly implemented, it may be possible to set up exponentially growing message loops ๐ŸŒ€ So, Sieve implementations need to do the following:

(1) Sieve implementations MUST implement facilities to detect and break message loops. See section 6.2 of [SMTP] for additional information on basic loop detection strategies.

(2) Sieve implementations MUST provide the means for administrators to limit the ability of users to abuse redirect. In particular, it MUST be possible to limit the number of redirects a script can perform. Additionally, if no use cases exist for using redirect to multiple destinations, this limit SHOULD be set to 1. Additional limits, such as the ability to restrict redirect to local users, MAY also be implemented.

(3) Sieve implementations MUST provide facilities to log use of redirect in order to facilitate tracking down abuse.

(4) Sieve implementations MAY use script analysis to determine whether or not a given script can be executed safely. While the Sieve language is sufficiently complex that full analysis of all possible scripts is computationally infeasible, the majority of real-world scripts are amenable to analysis. For example, an implementation might allow scripts that it has determined are safe to run unhindered, block scripts that are potentially problematic, and subject unclassifiable scripts to additional auditing and logging ๐Ÿ”

Allowing redirects at all may not be appropriate in situations where email accounts are freely available and/or not trackable to a human who can be held accountable for creating message bombs or other abuse ๐Ÿšจ

As with any filter on a message stream, if the Sieve implementation and the mail agents 'behind' Sieve in the message stream differ in how they interpret the messages, it may be possible for an attacker to subvert the filter ๐Ÿ•ต๏ธโ€โ™€๏ธ Differences in how malformed messages are interpreted (e.g., missing or extra syntax characters) are especially notable, as are messages that exhibit corner cases (e.g., NUL octets encoded via [MIME3]).


11. Acknowledgments ๐Ÿซถ

This document has been revised in part based on comments and discussions that took place on and off the SIEVE mailing list. Huge thanks to Sharon Chisholm, Cyrus Daboo, Ned Freed, Arnt Gulbrandsen, Michael Haardt, Kjetil Torgrim Homme, Barry Leiba, Mark E. Mallett, Alexey Melnikov, Eric Rescorla, Rob Siemborski, and Nigel Swinson for reviews and suggestions ๐Ÿ’

12. Normative References ๐Ÿ“š

  • [ABNF] Crocker, D., Ed., and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", RFC 4234, October 2005.
  • [COLLATION] Newman, C., Duerst, M., and A. Gulbrandsen, "Internet Application Protocol Collation Registry", RFC 4790, March 2007.
  • [IMAIL] Resnick, P., Ed., "Internet Message Format", RFC 2822, April 2001.
  • [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
  • [MIME] Freed, N. and N. Borenstein, "Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies", RFC 2045, November 1996.
  • [MIME3] Moore, K., "MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text", RFC 2047, November 1996.
  • [SMTP] Klensin, J., Ed., "Simple Mail Transfer Protocol", RFC 2821, April 2001.
  • [UTF-8] Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629, November 2003.

13. Informative References โœจ

okay bestie, these are the extra reads if you want to go deeper. None of them are required to implement Sieve, but they're the receipts ๐Ÿงพ๐Ÿ’…

  • [BINARY-SI] "Standard IEC 60027-2: Letter symbols to be used in electrical technology - Part 2: Telecommunications and electronics", January 1999.
  • [DSN] Moore, K. and G. Vaudreuil, "An Extensible Message Format for Delivery Status Notifications", RFC 3464, January 2003.
  • [FLAMES] Borenstein, N, and C. Thyberg, "Power, Ease of Use, and Cooperative Work in a Practical Multimedia Message System", Int. J. of Man-Machine Studies, April, 1991. Reprinted in Computer-Supported Cooperative Work and Groupware, Saul Greenberg, editor, Harcourt Brace Jovanovich, 1991. Reprinted in Readings in Groupware and Computer-Supported Cooperative Work, Ronald Baecker, editor, Morgan Kaufmann, 1993. ๐ŸŒธ
  • [IMAP] Crispin, M., "Internet Message Access Protocol - version 4rev1", RFC 3501, March 2003.
  • [MDN] Hansen, T., Ed., and G. Vaudreuil, Ed., "Message Disposition Notification", RFC 3798, May 2004.
  • [RFC3028] Showalter, T., "Sieve: A Mail Filtering Language", RFC 3028, January 2001.

14. Changes from RFC 3028 ๐Ÿฆ‹

okay this is the glow-up list ๐Ÿ“ˆ these are all the things that changed from the original Sieve spec in [RFC3028]. the whole list is the vibe check, so read it all:

  1. Removed ban on tests having side-effects
  2. Removed reject extension (will be specified in a separate RFC)
  3. Clarified description of comparators to match [COLLATION], the new base specification for them
  4. Require stripping of leading and trailing whitespace in "header" test
  5. Clarified or tightened handling of many minor items, including:
    • invalid [MIME3] encoding
    • invalid addresses in headers
    • invalid header field names in tests
    • 'undefined' comparator result
    • unknown envelope parts
    • null return-path in "envelope" test
  6. Capability strings are case-sensitive
  7. Clarified that fileinto should reencode non-ASCII mailbox names to match the mailstore's conventions
  8. Errors in the ABNF were corrected
  9. The references were updated and split into normative and informative
  10. Added encoded-character capability and deprecated (but did not remove) use of arbitrary binary octets in Sieve scripts.
  11. Updated IANA registration template, and added IANA considerations to permit capability prefix registrations.
  12. Added .sieve as a valid extension for Sieve scripts.

Editors' Addresses ๐Ÿ’Œ

Philip Guenther
Sendmail, Inc.
6425 Christie St. Ste 400
Emeryville, CA 94608
EMail: guenther@sendmail.com

Tim Showalter
EMail: tjs@psaux.com

Copyright (C) The IETF Trust (2008).

This document is subject to the rights, licenses and restrictions contained in BCP 78, and except as set forth therein, the authors retain all their rights.

This document and the information contained herein are provided on an "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.

Intellectual Property ๐Ÿงš

The IETF takes no position regarding the validity or scope of any Intellectual Property Rights or other rights that might be claimed to pertain to the implementation or use of the technology described in this document or the extent to which any license under such rights might or might not be available; nor does it represent that it has made any independent effort to identify any such rights. Information on the procedures with respect to rights in RFC documents can be found in BCP 78 and BCP 79.

Copies of IPR disclosures made to the IETF Secretariat and any assurances of licenses to be made available, or the result of an attempt made to obtain a general license or permission for the use of such proprietary rights by implementers or users of this specification can be obtained from the IETF on-line IPR repository at http://www.ietf.org/ipr.

The IETF invites any interested party to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology that may be required to implement this standard. Please address the information to the IETF at ietf-ipr@ietf.org. ๐Ÿ’œ

๐Ÿ“ฆ where this page comes from