Sieve Actions in Zimbra: What a Filter Can Do to a Message
A Sieve rule has two halves: a condition that decides whether the rule applies, and one or more actions that say what to do about it. This article covers the actions. Zimbra's set is not the same as the one in the RFCs - it leaves out some standard extensions and adds five of its own, and the difference matters as soon as you write a script by hand instead of clicking it together in the web client.
For the other half of a rule, see Sieve Conditions in Zimbra. For complete scripts you can copy, see 10 Zimbra Sieve Filter Examples.
The complete list
Zimbra builds its filter engine on Apache jSieve and then registers its own keywords on top. A keyword that is in neither set is a syntax error, so this really is the whole list.
| Action | What it does | Source |
|---|---|---|
keep | Store the message in the Inbox | RFC 5228 |
fileinto | Store it in a named folder | RFC 5228 |
discard | Throw it away silently | RFC 5228 |
redirect | Send it on to another address | RFC 5228 |
stop | Stop running the script | RFC 5228 |
:copy | Modifier - keep delivering as well | RFC 3894 |
reject | Accept it, then mail the sender a refusal | RFC 5429 |
ereject | Refuse the delivery at SMTP time | RFC 5429 |
set | Put a value in a variable | RFC 5229 |
addheader, deleteheader | Add or remove a header | RFC 5293 |
notify | Send an out-of-band notification | RFC 5435 or Zimbra's own |
tag | Apply a Zimbra tag | Zimbra |
flag | Mark read, flagged or high priority | Zimbra |
reply | Send a reply to the sender | Zimbra |
replaceheader | Rename a header or change its value | Zimbra |
log | Write a line to the server log | Zimbra |
Every action outside the RFC 5228 base needs to be named in a require line at the top of
the script, Zimbra's own actions included.
Deciding where the message goes
fileinto stores the message in a folder, and the folder has to exist already. discard
throws it away without telling anyone. stop ends the script, which is how you keep later
rules from touching a message you have already dealt with.
require ["fileinto"];
if header :contains "subject" "invoice" {
fileinto "Invoices";
stop;
}
The important detail is what happens when no rule fires. Sieve performs an implicit
keep: a message that no action has claimed still lands in the Inbox. Writing keep
yourself is only necessary when you want delivery to the Inbox and something else that
would otherwise cancel it.
Refusing a message
reject and ereject both turn mail away, but at different moments and with different
results for the sender.
require ["reject"];
if size :over 10M {
reject "Messages over 10 MB are not accepted. Please send a file-sharing link.";
}
reject accepts the message, does not store it, and sends the sender a refusal notice
containing your text. It works only while zimbraSieveRejectMailEnabled is TRUE, which
is the default; set it to FALSE and the message is kept instead.
require ["ereject"];
if header :is "X-Spam-Flag" "YES" {
ereject "Spam is not accepted.";
}
ereject refuses the delivery itself: Zimbra answers Postfix with 550 5.7.1 Message rejected and Postfix bounces it. Your text is not passed to the sender, so treat it as a
comment rather than as a message.
Sending it somewhere else
redirect forwards the message to another address. On its own it replaces delivery, so
add :copy when you want a copy to reach your own mailbox too.
require ["copy"];
if header :contains "subject" "urgent" {
redirect :copy "on-call@example.com";
}
:copy works the same way on fileinto, which is the usual way to archive a copy while
still delivering to the Inbox:
require ["fileinto", "copy"];
fileinto :copy "Archive";
Marking it
tag and flag are Zimbra's own, and they are the two actions with no equivalent in the
base RFC.
require ["tag"];
if header :contains "subject" "Project Apollo" {
tag "Apollo";
}
tag takes exactly one string. If the tag does not exist yet, Zimbra creates it, which is
the opposite of how fileinto treats folders.
flag also takes exactly one string, not case-sensitive, from a fixed list:
| Value | Effect |
|---|---|
read / unread | Mark as read or unread |
flagged / unflagged | Set or clear the flag |
priority / unpriority | Set or clear high priority |
require ["flag"];
if address :is "from" "manager@example.com" {
flag "flagged";
}
The web client calls this action mark, which is worth remembering when you compare a script with the rule you see in Preferences > Filters.
Changing the headers
addheader, deleteheader and replaceheader rewrite the message before it is stored.
All three are off by default: an administrator has to set zimbraSieveEditHeaderEnabled
to TRUE first, and they are meant for admin scripts rather than user filters.
require ["editheader"];
addheader "X-External" "yes";
deleteheader "X-Mailer";
deleteheader :contains "X-Tracking" "campaign";
replaceheader is Zimbra's own addition and does what the other two cannot - change a
header in place:
require ["editheader"];
replaceheader :newname "X-Original-Priority" "X-Priority";
replaceheader :newvalue "Other" :is "X-Category" "misc";
replaceheader :index 1 :newvalue "Checked" "X-Status";
Header editing is also the most practical way to debug a filter: stamp a header when a rule fires, then read the source of a delivered message to see which rules matched.
Answering and notifying
reply is Zimbra's own auto-reply action. It takes one string, the body of the reply, and
sends it to the sender.
require ["reply"];
reply "Thank you for your message. We will answer within one business day.";
Note what reply does not do. Zimbra has no RFC 5230 vacation command, so there is
no built-in "answer each sender only once" and no automatic silence on mailing lists. A
reply fires on every message that matches, which is why an out-of-office rule written
this way needs conditions of its own to stay out of trouble - excluding bulk mail, for
instance.
notify is one keyword with two different grammars, chosen by the account attribute
zimbraSieveNotifyActionRFCCompliant. While it is FALSE, which is the default, Zimbra's
original positional form applies:
require ["notify"];
notify "alerts@example.com" "New mail" "A new message arrived." 1024 ["From", "Subject"];
Set it to TRUE and the RFC 5435 form applies instead, with tagged arguments and a
mailto: URI:
require ["enotify"];
notify :from "noreply@example.com"
:importance "1"
:message "Urgent message received"
"mailto:alerts@example.com";
Mixing the two forms is a common cause of a notify rule that saves without complaint and then never fires, so check the attribute before writing either one.
Variables and logging
set stores a value for later in the same script, usually something pulled out of a header
by a :matches test. The modifiers :lower, :upper, :lowerfirst, :upperfirst,
:quotewildcard, :length and :encodeurl transform the value as it is stored.
require ["variables", "tag"];
if address :domain :matches "from" "*" {
set :lower "domain" "${1}";
tag "${domain}";
}
log writes a line to the mailbox log, at one of :fatal, :error, :warn, :info,
:debug or :trace. It is the other half of the debugging story, and it costs nothing in
production if you leave it at :debug.
require ["log", "variables"];
log :info "Filtered message: ${subject}";
Two keywords you will only meet in generated scripts
Zimbra writes disabled_if in place of if for a rule the user has switched off in the
Filters UI, so the rule survives an edit but never runs. zimbravariablesctrl :reset
clears every variable set so far. Both are internal - you will see them when you read a
script back out of a mailbox, but there is no reason to write either by hand.
What Zimbra leaves out
Two extensions that are common elsewhere are simply not registered, and using one is a syntax error rather than a silent no-op:
vacation(RFC 5230) - use Zimbra'sreply, or the separate out-of-office feature in Preferences, which is not Sieve at all.imap4flags(RFC 5232) - Zimbra'sflagcovers the same ground with different syntax and a fixed list of values.
If you are unsure whether a keyword is available, the fastest check is to save the script
through the web client or the SOAP API, which parses it and refuses anything invalid.
Setting the script attribute directly with zmprov does not validate it, and a broken
script saved that way only surfaces later as a filter error in
/opt/zimbra/log/mailbox.log.
Next
Actions are half of a rule. For the tests that decide when they run - including Zimbra's
own attachment, me, list, bulk and current_time - read
Sieve Conditions in Zimbra.