Summary

  • RFC 3028 made mail filtering a constrained action language and used implicit keep to protect messages when a script selected no delivery-affecting action.
  • keep, fileinto, redirect, discard and rejection described dispositions at the interpreter boundary; storage, onward acceptance, refusal and final observation still belonged to the running mail system.

In January 2001, server-side filtering posed an awkward design problem. Users wanted mail sorted, forwarded or rejected before opening a mailbox. Operators could not safely give every user a general-purpose program on the delivery server. A language expressive enough to run arbitrary code would turn convenience into a security boundary that was almost impossible to contain.

RFC 3028 answered by removing power. Sieve had no loops, no functions, no variables and no route to an external shell. Tests examined message facts without side effects. Control commands chose which blocks ran. Action commands requested what should happen to the message. This was not merely syntactic tidiness. It separated observation from mutation and kept the mutation vocabulary small enough to reason about.

The most revealing part of the design was what happened when no action ran.

The empty branch acquired a safe meaning

Filtering rules miss cases. A sender changes an address, a list changes a header, or a condition is simply written incorrectly. In a destructive system, falling through every branch could discard mail by accident. RFC 3028 therefore defined an implicit keep: unless an action canceled it, the system performed the same default disposition it would have used without filtering, generally filing the message in the user’s main mailbox.

Implicit keep was not a second copy silently appended to every result. keep, fileinto, redirect and discard canceled it. The interpreter was evaluating an action set, not executing an unconditional safety copy after each branch. Some later extensions could produce side effects without canceling the default, but they had to say so. The question “does this action cancel implicit keep?” became part of the action’s contract.

That contract also explains a seemingly strange rule. discard did not destroy all other actions. It silently canceled implicit keep. If a script performed fileinto and then discard, the filed copy still happened; the discard prevented a further default copy. The verb sounded absolute, but its defined effect was relational: remove the fallback while leaving compatible actions intact.

A disposition verb stopped at a control boundary

The language used concrete verbs, yet each verb depended on machinery outside the script.

keep requested the implementation’s ordinary default disposition. It spared the author from knowing whether the main mailbox was called INBOX, represented by a file or provided by another store. That abstraction was useful precisely because the command did not describe the storage mechanism. A successful branch selection could not prove that disk space existed, permissions remained valid or the mailbox commit completed.

fileinto named a folder. RFC 3028 recommended support while recognizing that some environments could not provide it. A folder string was therefore an instruction to a delivery agent, not proof that the folder existed or that a write became durable.

redirect was more revealing still. It changed the envelope recipient and sent the message outward in the style of an MTA forward. The local interpreter could choose and attempt that path. A remote server still controlled acceptance, and later systems controlled whatever “delivered” meant there. Loop detection was also an implementation duty. The word redirect began another delivery episode; it did not close it.

discard deliberately produced no failure notice. Silence reduced one class of feedback but also removed a sender-visible receipt. Evidence of the script decision had to come from the executing system’s records, if the operator retained them. The absence of a bounce was not proof of storage, forwarding or even a particular filtering reason.

Action interaction was governance for side effects

Once multiple actions could apply to one message, their combinations became more important than their individual names. RFC 3028 required extensions to define interaction with the base actions. Sites could limit how many actions ran and which combinations were allowed. Implementations were expected to avoid duplicate delivery to the same mailbox even when a script requested it twice.

These rules controlled amplification and contradiction. A user who could redirect repeatedly might create a mailbomb. A script that both delivered and rejected could tell the sender that a message failed while retaining a copy. A system that treated every action as independent would produce outcomes no single rule author intended.

The later standards sharpened this point. RFC 5228 replaced RFC 3028 but preserved implicit keep and the base disposition model, making error handling and extension interaction clearer. RFC 9122 eventually created an IANA Sieve Actions registry whose columns include action interactions and whether an action cancels implicit keep. The registry turned an architectural question into explicit metadata. It catalogued contracts; it did not report whether any particular action succeeded.

Rejection exposed the cost of confusing decision and transport

The original reject extension showed what happens when the boundary is placed in the wrong part of the delivery sequence. RFC 3028’s reject discarded the message and sent a Message Disposition Notification to the envelope sender. That could occur after the receiving system had accepted the message. When spam used a forged sender, the notification went to an innocent third party, creating backscatter.

RFC 5429 revised the model. Its ereject action preferred refusing delivery during SMTP or LMTP, when possible, rather than accepting first and constructing a later notification. It also clarified that rejection canceled implicit keep, prohibited multiple rejections and discouraged combining rejection with actions that delivered mail. The reason was not cosmetic consistency. A sender should not be told that a message was rejected when the same system also stored or forwarded it.

This history reveals three different facts that a single status word can hide. The script may select rejection. The executing component may be able to refuse at the protocol boundary or may need another mechanism. The sender may observe a protocol response, a later report or nothing at all. “Rejected” without the layer and evidence is incomplete.

ManageSieve later standardized a separate administrative surface for uploading, checking and activating scripts. That separation matters here. A script can be valid and active without having evaluated a particular message. An evaluation can select an action without the action reaching its intended endpoint. Administration, decision and outcome are three records, not one.

Sources

Lu Heng did not author or endorse RFC 3028, RFC 5228 or RFC 5429. His essays are used here as disclosed analytical lenses.