Sieve is a programming language used for email filtering. Today, I show you how I automatically sort my ProtonMail inbox into folders and subfolders using custom sieve filters. My setup uses the catch-all feature requiring at least a ProtonMail Professional subscription and a properly configured custom domain.

The ProtonMail Bridge has been supporting subfolders for a while now. With the release of the re-designed ProtonMail for web, subfolders are now officially supported. So now is a great time to share my setup with you guys!

The Goal

I only want emails from unknown senders to land in my inbox, e.g., non-automated messages from people directly contacting me or emails from unknown websites. Sieve filters sort emails from well-known senders into folders and subfolders, like webshops or social media for which I have signed up.

I use root-level folders to categorize my email. Subfolders are for specific websites belonging to a category:

Inbox/
Webshops/
├─ Amazon/
├─ Ex Libris/
Entertainment/
├─ Netflix/
├─ Spotify/

Here, Webshops and Entertainment are the categories, and Amazon and Spotify are specific websites. For each website, I use a separate email address that maps to a pre-created folder. The generic structure of the email address I use to sign up for websites is the following:

For the example folder structure above, the email-to-folder mapping looks like this:

The Solution

You can add custom sieve filters in the ProtonMail web client by navigating to SettingsFiltersAdd sieve filter. The following snippet meets all of my requirements:

require ["include", "variables", "fileinto", "envelope"];

if envelope :localpart :matches "To" "*+*" {
  set :lower "category" "${1}";
  set :lower "website"  "${2}";
}
else {
  return;
}

if string :is "${website}" "exlibris" {
  fileinto "${category}/Ex Libris";
} else {
  fileinto "${category}";
  fileinto "${category}/${website}";
}

The require command in the first line is used to load extensions that provide functionality, like fileinto to file messages into folders or variables to declare variables.

Next, the if-conditional checks the :localpart, the part before the @ symbol, of the To address. If it matches the pattern *+* (category+website):

  • the value of the category variable is set to the match left of + symbol (${1})
  • the value of the website variable is set to the match right of the + symbol (${2}).

If :localpart does not match the pattern (else), the sieve filter exits (return).

Next, the actual sorting happens. Let’s look into the else case first:

} else {
  fileinto "${category}";
  fileinto "${category}/${website}";
}

Most of the magic happens here. The email is first moved to the category folder and then moved to the more specific website folder. This way, if the website subfolder does not (yet) exist, the email is at least moved into the root-level category folder. I find this useful when only ordering at a webshop once via guest checkout instead of signing up. This way, I don’t need to bother creating an extra subfolder that will only ever contain one or two emails.

Lastly, let’s cover the exception for the exlibris webshop in the if-part:

if string :is "${website}" "exlibris" {
  fileinto "${category}/Ex Libris";
}

It is just a cosmetic exception. When I register at a webshop with [email protected] but want to map it to a folder called Ex Libris, an explicit mapping is required. Note that fileinto is not case-sensitive, so it is valid if the email address is [email protected], but the folder name is Webshops/Amazon/. It also is easily extensible by adding more elseif conditions.

For more details on advanced custom sieve filtering, check the official ProtonMail documentation.