Sieve E-Mail Filtering Like a boss


Some E-Mail clients support local mail filtering, which is fine as long as this client is the only one you use. But when you access your mail from different clients, like mobile or webmail, you need to filter mails on the server. This is especially useful when you receive a lot of notification mails from services like Jira, Github or monitoring systems.

The following recipes require a server with Sieve capabilities, which are available on most modern IMAP servers like Dovecot or Cyrus.

Sieve scripts are usually uploaded using the manageSieve protocol, but this depends on the system you’re using and is out of the scope of this article. Please ask your friendly system operator how to store Sieve scripts on your mail server.

Here are some Sieve filter snippets that allow you to handle a huge amount of email traffic.

Mark your own mail “seen”

This little snippet marks your own mails as seen. This is useful when you reply to a mailing list address or your MUA (Mail User Agent) sends a reply to all message to your own address as well. The script needs the imap4flags extension to be enabled on your Sieve/IMAP server.

require ["envelope", "imap4flags"];

if envelope "from" ["me@example.com", "other-me@example.com"]
{
    addflag "\\seen";
}

Add some nice labels based on content types

IMAPv4 allows the user to use custom flags. I don’t know if your email client is able to handle them, but at least Neomutt and Thunderbird can use flags to highlight messages in different colors or other user-visible hints.

This snippet helps me not to miss event invitations by adding an invite label to mails containing the application/ics content-type.

require ["foreverypart", "mime", "imap4flags"];

foreverypart
{
    if header :mime :contenttype "Content-Type" "application/ics"
    {
        addflag "invites";
    }
    if header :mime :contenttype "Content-Type" "text/x-vcard"
    {
        addflag "contact";
    }
    if header :mime :param "filename" :contains "Content-Disposition" "attachment"
    {
        addflag "attachment";
    }
    if header :mime :param "filename" :matches ["Content-Type", "Content-Disposition"] ["*.com", "*.exe", "*.vbs", "*.scr", "*.pif", "*.hta", "*.bat", "*.zip" ]
    {
        addflag "dangerous";
    }
    if header :mime :param "filename" :matches ["Content-Type", "Content-Disposition"] ["*.pdf", "*.ods", "*.sdc", "*.sxc", "*.doc", "*.docx", "*.docm", "*.xsl", "*.xlsx", "*.xlsm", "*.ppt", "*.pptx", "*.pptm"]
    {
        addflag "documents";
    }
}

Jira filter rules

If you’re in a corporate environment there might be a chance you have to use Jira. Sieve can help you to see the relevant information of Jira notifications at a glance.

This snippet:

  1. Flags high priority tickets
  2. Adds a label that an issue is assigned to myself
  3. Adds an extra label for bug tickets
  4. Stores the mails into project mailboxes
  5. Rewrites the subject to remove the useless [JIRA] prefix

These rules are a little more complex and there are some more extensions involved.

require ["body", "regex", "imap4flags", "fileinto", "mailbox", "variables", "ihave"];

# The editheader extension is not enabled by default on our server (Dovecot/Pidgeonhole)
if ihave "editheader" {
    require "editheader";
}

# Limit the checks to our jira instance.
if address :is "from" "jira@example.com"
{
    # Flag the mail and add an "urgent" label
    if body :text :contains "Priority: High" {
        addflag "\\Flagged";
        addflag "urgent";
    }

    # Add a label that the message is assigned to you
    if body :text :contains "Assignee: youruser" {
        addflag "assigned";
    }

    # Add a label for bugs
    if body :text :contains "Issue Type: Bug" {
        addflag "bug";
    }

    # Jira mails have some information in the subject
    if header :regex "Subject" "^\\[JIRA\\] \\((.+)-(.+)\\) (.*)$" {
        # set the first match group to the "project" variable
        set "project" "${1}";
        # create a new "subject" variable
        set "subject" "[${1}-${2}] ${3}";

        # if we're able to edit existing headers we can get rid of the
        # [JIRA] prefix!
        if ihave "editheader" {
            deleteheader "Subject";
            addheader "Subject" "${subject}";
        }

        # file into a project subfolder
        fileinto :create "jira.${project}";
        stop;
    } else {
        # subject doesn't match, so put the mail into the "jira" folder
        fileinto :create "jira";
        stop;
    }
}

Team mailing lists

At our company we have team mailing lists for all projects. The scheme is projectname-team@example.com and we want them to appear in different subfolders. We could write an explicit rule for each address, or we just use matching and variables to route them automatically.

require ["fileinto", "mailbox", "variables"];

if address :matches ["to", "cc"] "*-team@example.com"
{
    fileinto :create "projects.${1}";
    stop;
}

Github and Gitlab notifications

In case you’re part of any Github or GitLab you might be interested in putting all project notifications in extra folders.

require ["fileinto", "mailbox", "variables"];

if address :is "from" "notifications@github.com"
{
    if header :matches "List-ID" "*/* <*>" {
        fileinto :create "github.${2}";
    } else {
        fileinto :create "github";
    }
    stop;
}

if header :matches "X-GitLab-Project" "*"
{
    fileinto :create "gitlab.${1}";
    stop;
}

Redmine project notifications

We also have a legacy Redmine instance that sends project notifications.

require ["fileinto", "mailbox", "variables"];

if header :matches "X-Redmine-Project" "*"
{
	fileinto :create "redmine.${1}";
	stop;
}

Handle sub-addresses

Using sub-addresses is an incredibly useful feature, especially for signups at different external services. For example, when registering at a random shop, I always use sub addresses to specify the shop name in my address (myuser+shop-foo-bar@example.com). This snippet stores all spam mails to sub addresses into the appropriate folder.

require ["envelope", "fileinto", "mailbox", "variables", "subaddress"];

if envelope :matches :detail "to" "*" {
    fileinto :create "sub.${1}";
    stop;
}

Discard spam from a newsletter that doesn’t allow me to unsubscribe

if envelope :is "from" "news@shop.example.com"
{
  discard;
  stop;
}

Reject mails from this special coworker

if allof(
  address :is "from" "coworker@example.com",
  header :contains "subject" "kitten"
) {
  reject "Stop sending kitten spam!";
  stop;
}

Conclusion

Sieve is the perfect tool for handling a huge amount of incoming emails. It allows you to deeply inspect you’re mails, and provides a lot of actions for further processing. This article showed only a few of them…