Skip to main content

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.

ActionWhat it doesSource
keepStore the message in the InboxRFC 5228
fileintoStore it in a named folderRFC 5228
discardThrow it away silentlyRFC 5228
redirectSend it on to another addressRFC 5228
stopStop running the scriptRFC 5228
:copyModifier - keep delivering as wellRFC 3894
rejectAccept it, then mail the sender a refusalRFC 5429
erejectRefuse the delivery at SMTP timeRFC 5429
setPut a value in a variableRFC 5229
addheader, deleteheaderAdd or remove a headerRFC 5293
notifySend an out-of-band notificationRFC 5435 or Zimbra's own
tagApply a Zimbra tagZimbra
flagMark read, flagged or high priorityZimbra
replySend a reply to the senderZimbra
replaceheaderRename a header or change its valueZimbra
logWrite a line to the server logZimbra

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:

ValueEffect
read / unreadMark as read or unread
flagged / unflaggedSet or clear the flag
priority / unprioritySet 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's reply, or the separate out-of-office feature in Preferences, which is not Sieve at all.
  • imap4flags (RFC 5232) - Zimbra's flag covers 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.