Summary

  • RFC 5229 added set, string expansion, and match variables to Sieve only when a script explicitly required the variables capability.
  • The resulting memory was deliberately bounded: match captures depended on control flow, values belonged to the currently running script, and implementation limits could truncate oversized values.

A filter needed a small memory, not a larger machine

By January 2008, Sieve already had a settled identity as a mail-filtering language for final delivery. Its base specification described a language meant to be useful while remaining limited: it had no variables, loops, or ability to invoke shell commands. Those omissions were not empty spaces waiting for any programming feature. They marked a boundary around what a server-side user filter could ask the mail system to do.

RFC 5229 crossed one part of that boundary carefully. A script could declare require "variables"; then it could assign a named string with set, interpolate it inside another string, and test strings created by the script. A successful wildcard match could expose the whole match as ${0} and the wildcard pieces as ${1}, ${2}, and so on. The value was practical: a rule could extract a list name from a header, carry it into a mailbox path, or build later text without repeating the same fragment.

That is not the same as giving the filter a durable memory. The extension says variables are visible only to the currently running script. It does not define a mailbox-wide store, a record of earlier messages, or a shared state between users. Its use of the word “variable” can sound larger than the object the standard actually specifies: a name bound to string data while this script runs.

Declaration was part of the safety model

The capability had to be named before use. A script without the variables requirement did not silently inherit changed string semantics. That detail matters in an extensible language: a feature is not just an operation, but also an explicit contract between a script and an interpreter that says the operation exists.

Once enabled, variable references were evaluated when control reached the statement containing them. The interpreter performed one substitution pass. A variable whose name was not defined expanded to the empty string; names were case-insensitive. Those choices made composition compact, but they also made some mistakes quiet. A misspelled reference could disappear into a string rather than announce itself as a missing value. A value that itself contained another ${...} reference was not recursively expanded in a second pass.

The extension added several narrow modifiers—ASCII case changes, first-character case changes, wildcard quoting, and string length—alongside a string test. It did not provide arithmetic, loops, or arbitrary code execution. “More expressive” therefore did not mean “unbounded.” It meant a finite number of simple values could be named, normalized in limited ways, and reused by the script.

A capture is a fact about one evaluated branch

Match variables made control flow part of the data model. RFC 5229 requires tests to run left to right and to short-circuit when a Boolean answer is already known. Consider anyof (true, header :matches ...): the header test is not reached, so it does not create captures. A successful later :matches can replace the previous match list; a failed match does not supply new pieces. In a more complicated rule, the programmer must know which test actually ran before trusting ${1}.

The extension also did not impose capture side effects on every later match type. RFC 5173, published three months after RFC 5229, makes an especially revealing boundary explicit: variable references in a body-test key can be expanded when the extension is enabled, but wildcard matching in that body test must not set match variables. The standards text therefore allows one capability to feed another while refusing to make every superficially similar operation behave alike.

The limit table turns this boundedness into an implementation contract. A conforming implementation had to support at least 128 variables, names of at least 32 characters, values of at least 4,000 characters, and captures ${1} through ${9}. When a value exceeded what a particular implementation could hold, the RFC recommended catching that at compile time when possible; if discovered only at run time, truncation was expected and was not to be treated as an error. The security section consequently warns against placing large, security-significant structures in variables and notes that captures may contain arbitrary text controlled by the sender.

An extension accumulated a long standards history

The Datatracker record traces the individual draft line back to March 2003, followed by working-group drafts from late 2004 and successive revisions through 2005. The IESG write-up identifies the specification as a product of the Sieve Mail Filtering Language Working Group. Approval in 2006 and RFC publication in January 2008 are different recorded events; that chronology does not tell us why the work took that long or how widely implementations adopted it.

Read through Heng Lu’s “Minimum Initial Specification” as an analytical lens, the require gate looks like a localized future decision: a small base can remain stable while a script opts into a precise extension. “Running-Code Primacy” supplies a second distinction: a published capability does not prove that any named server implements or enables it. “Reality Layers” keeps the script’s declared feature, interpreter state, server behavior, and the user’s eventual mailbox experience from being collapsed into one claim. These are later interpretive frames, not evidence of RFC 5229’s authors’ intent.

The filter could now remember what one evaluated match had captured, and a script could reuse a few named strings. It could not, on that basis alone, remember a conversation across executions, authenticate the sender, or prove where a message ended up. RFC 5229’s historical move was not to erase Sieve’s boundary but to make one carefully declared seam more useful.

Sources