├── sieves ├── real-estate.sieve ├── hiking.sieve ├── health.sieve ├── legal.sieve ├── pets.sieve ├── food-and-drink.sieve ├── social.sieve ├── utilities.sieve ├── coffee.sieve ├── 00-testing.sieve ├── insurance.sieve ├── 02-statements.sieve ├── gaming.sieve ├── 00-ads.sieve ├── reservations.sieve ├── travel.sieve ├── investing.sieve ├── tech.sieve ├── family-and-friends.sieve ├── 01-security.sieve ├── government.sieve ├── finance.sieve └── zz-orders-and-shipping.sieve └── README.md /sieves/real-estate.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Commonly used real estate platforms 4 | if address :matches :domain "from" ["*zillow.com", "*better.com", "*redfin.com"] 5 | { 6 | fileinto "Real Estate"; 7 | expire "day" "365"; 8 | stop; 9 | } -------------------------------------------------------------------------------- /sieves/hiking.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Commonly used hiking services 4 | if address :matches :domain "from" ["*garmin.com", "*gaiagps.com", "*nextmilemeals.com", "*alltrails.com", "*opensummit.com"] 5 | { 6 | fileinto "Hiking"; 7 | expire "day" "365"; 8 | stop; 9 | } -------------------------------------------------------------------------------- /sieves/health.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Commonly used health services 4 | if address :matches :domain "from" ["*carefirst.com", "*ibx.com", "*ibx2.com", "*metlife.com", "*eyemed.com", "*vsp.com", "*teladoc.com", "*lenscrafters.com"] 5 | { 6 | fileinto "Health"; 7 | expire "day" "365"; 8 | stop; 9 | } -------------------------------------------------------------------------------- /sieves/legal.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Commonly used legal platforms 4 | if address :matches :domain "from" ["*docusign.net"] 5 | { 6 | # This is purely extra protection in the event the sieves don't work as expected 7 | if hasexpiration 8 | { 9 | unexpire; 10 | } 11 | fileinto "Legal"; 12 | stop; 13 | } -------------------------------------------------------------------------------- /sieves/pets.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Commonly used pet vendors 4 | if address :matches :domain "from" ["*petco.com", "*litterbox.com", "*chewy.com", "*litter-robot.com", "*foundanimals.org", 5 | "*openfarmpet.com", "*truthaboutpetfood.com", "*found.org"] 6 | { 7 | fileinto "Pets"; 8 | expire "day" "365"; 9 | stop; 10 | } -------------------------------------------------------------------------------- /sieves/food-and-drink.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Commonly used food vendors 4 | if address :matches :domain "from" ["*highkey.com", "*highkeysnacks.com", "*doordash.com", "*instacart.com", "*papajohns.com", 5 | "*yelp.com", "*grubhub.com", "*postmates.com"] 6 | { 7 | fileinto "Food and Drink"; 8 | expire "day" "365"; 9 | stop; 10 | } -------------------------------------------------------------------------------- /sieves/social.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Commonly used social networks 4 | if address :matches :domain "from" ["*linkedin.com", "*facebookmail.com", "*facebook.com", "*reddit.com", "*twitter.com", "*instagram.com", "*pinterest.com", "*meetup.com", "*tumblr.com", "*ycombinator.com"] 5 | { 6 | fileinto "Social"; 7 | expire "day" "365"; 8 | stop; 9 | } -------------------------------------------------------------------------------- /sieves/utilities.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Commonly used utilities 4 | if address :matches :domain "from" ["*bge.com", "*comcast.net", "*xfinity.com", "*t-mobile.com", "*att.com", "*spectrum.com", "*verizon.com", "*verizonwireless.com", "*rentcafe.com", "*duke-energy.com"] 5 | { 6 | fileinto "Finance/Utilities"; 7 | expire "day" "365"; 8 | stop; 9 | } 10 | -------------------------------------------------------------------------------- /sieves/coffee.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Commonly used coffee sites 4 | if address :matches :domain "from" ["*counterculturecoffee.com", "*birdrockcoffee.com", "*jbccoffeeroasters.com", 5 | "*madcapcoffee.com", "*georgehowellcoffee.com", "*onyxcoffeelab.com", "*swiftcupcoffee.com", "*nespresso.com"] 6 | { 7 | fileinto "Coffee"; 8 | expire "day" "365"; 9 | stop; 10 | } -------------------------------------------------------------------------------- /sieves/00-testing.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "extlists", "vnd.proton.expire"]; 2 | 3 | # This sieve is useful for testing. 4 | # Create a contact group and name it "Self". 5 | # Add your personal e-mail addresses to this group. 6 | # You can alternatively use ":addrbook:myself" 7 | # but I prefer to test with external addresses. 8 | if header :list "from" ":addrbook:personal?label=Self" 9 | { 10 | # do things 11 | } -------------------------------------------------------------------------------- /sieves/insurance.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Commonly used insurance providers 4 | if address :matches :domain "from" ["*allstate.com", "*allstate-email.com", "*geico.com", "*geicomail.com", "*jminsure.com", 5 | "*travelguard.com", "*nationwide.com", "*progressive.com", "*statefarm.com", "*travelers.com", "*assurant.com"] 6 | { 7 | fileinto "Finance/Insurance"; 8 | expire "day" "365"; 9 | stop; 10 | } -------------------------------------------------------------------------------- /sieves/02-statements.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # If the string "statement" is found in the subject, set an expiration of 365 days. 4 | # These are almost always informational/no-action emails and only useful for a few minutes, but set a high expiration to be safe. 5 | if header :contains "subject" "statement" 6 | { 7 | expire "day" "365"; 8 | } 9 | 10 | # do NOT stop executing, allow other sieves to continue processing -------------------------------------------------------------------------------- /sieves/gaming.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Commonly used gaming services 4 | if address :matches :domain "from" ["*steampowered.com", "*xbox.com", "*nintendo.net", "*nintendo.com", "*greenmangaming.com", 5 | "*epicgames.com", "*ea.com", "*playstationemail.com", "*rockstargames.com", "*twitch.tv", "*ubi.com", "*blizzard.com", "*bethesda.net"] 6 | { 7 | fileinto "Gaming"; 8 | expire "day" "365"; 9 | stop; 10 | } -------------------------------------------------------------------------------- /sieves/00-ads.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags"]; 2 | 3 | # allowlist - do NOT tag as advertising 4 | if address :matches :domain "from" ["*personalcapital.com", "*robinhood.com", "*nerdwallet.com"] 5 | { 6 | # do nothing 7 | } 8 | 9 | # If "list-unsubscribe" header present, flag for easy manual review 10 | # Ads is a label, NOT a folder 11 | elsif exists "list-unsubscribe" 12 | { 13 | fileinto "Ads"; 14 | } 15 | 16 | # do NOT stop executing, allow other sieves to continue processing -------------------------------------------------------------------------------- /sieves/reservations.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # General catch all for appointments 4 | if header :contains "subject" ["appointment"] 5 | { 6 | fileinto "Reservations"; 7 | expire "day" "365"; 8 | stop; 9 | } 10 | 11 | # More targeted, specific reservation websites 12 | elsif address :matches :domain "from" ["*seatme.com", "*opentable.com", "*resy.com", "*exploretock.com"] 13 | { 14 | fileinto "Reservations"; 15 | expire "day" "365"; 16 | stop; 17 | } -------------------------------------------------------------------------------- /sieves/travel.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Commonly used travel services 4 | if address :matches :domain "from" ["*southwest.com", "*hyatt.com", "*airbnb.com", 5 | "*lyftmail.com", "*uber.com", "*hertz.com", "*alaskaair.com", "*delta.com", 6 | "*goalamo.com", "*avis.com", "*ihg.com", "*kimptonhotels.com", "*hotels.com", 7 | "*marriott.com", "*tripadvisor.com", "*aa.com", "*autoslash.com", "*priceline.com"] 8 | { 9 | fileinto "Travel"; 10 | expire "day" "730"; 11 | stop; 12 | } -------------------------------------------------------------------------------- /sieves/investing.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # For Robinhood Snacks 4 | # Route to main inbox 5 | if header :contains "from" "Robinhood Snacks" 6 | { 7 | expire "day" "3"; 8 | stop; 9 | } 10 | 11 | # Commonly used investing platforms 12 | elsif address :matches :domain "from" ["*troweprice.com", "*e-vanguard.com", "*vanguard.com", "*m1finance.com", "*coinbase.com", "*robinhood.com", "*fidelity.com", "*prudential.com"] 13 | { 14 | fileinto "Finance/Investing"; 15 | expire "day" "365"; 16 | stop; 17 | } -------------------------------------------------------------------------------- /sieves/tech.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Commonly used tech services 4 | # Security has it's own folder - don't put tech security services here. 5 | if address :matches :domain "from" ["*digitalocean.com", "*github.com", 6 | "*cloudflare.com", "*docker.com", "*heroku.com", "*keybase.io", "*adobe.com", 7 | "*netflix.com", "*google.com", "*youtube.com", "*apple.com", "*roku.com", 8 | "*namecheap.com", "*microsoft.com", "*ebay.com", "*spotify.com", "*plex.tv", "*logitech.com"] 9 | { 10 | fileinto "Tech"; 11 | expire "day" "365"; 12 | stop; 13 | } 14 | -------------------------------------------------------------------------------- /sieves/family-and-friends.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "extlists", "vnd.proton.expire"]; 2 | 3 | # Checks if sender is in personal address book with a "Family" group association 4 | if header :list "from" ":addrbook:personal?label=Family" 5 | { 6 | # This is purely extra protection in the event the sieves don't work as expected 7 | if hasexpiration 8 | { 9 | unexpire; 10 | } 11 | fileinto "Family"; 12 | stop; 13 | } 14 | 15 | # Checks if sender is in personal address book with a "Friend" group association 16 | elsif header :list "from" ":addrbook:personal?label=Friends" 17 | { 18 | # This is purely extra protection in the event the sieves don't work as expected 19 | if hasexpiration 20 | { 21 | unexpire; 22 | } 23 | fileinto "Friends"; 24 | stop; 25 | } -------------------------------------------------------------------------------- /sieves/01-security.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # This sieve is high up in the execution chain, as we want to short-circuit these from other sieves and centralize all security events. 4 | 5 | # Common subjects relevant to security events 6 | if header :contains "subject" ["security alert", "security notification", "login", "sign-on", 7 | "sign-in", "sign in", "sign on", "email address", "email change", "password", "terms of service"] 8 | { 9 | fileinto "Security"; 10 | expire "day" "365"; 11 | stop; 12 | } 13 | 14 | # Commonly used security services 15 | elsif address :matches :domain "from" ["*lastpass.com", "*logme.in", "*okta.com", "*accounts.google.com", 16 | "*1password.com", "*haveibeenpwned.com", "*nextdns.io"] 17 | { 18 | fileinto "Security"; 19 | expire "day" "365"; 20 | stop; 21 | } -------------------------------------------------------------------------------- /sieves/government.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Don't expire any emails here. 4 | 5 | # Handle the national parks service 6 | if address :matches :domain "from" ["*recreation.gov"] 7 | { 8 | fileinto "Hiking"; 9 | stop; 10 | } 11 | 12 | # Technically not _exclusively_ a government domain, but most US states use them 13 | if address :matches "from" ["*@*.us"] 14 | { 15 | # This is purely extra protection in the event the sieves don't work as expected 16 | if hasexpiration 17 | { 18 | unexpire; 19 | } 20 | fileinto "Government"; 21 | stop; 22 | } 23 | 24 | # This should catch all US government variations 25 | elsif address :matches "from" ["*@*.gov"] 26 | { 27 | # This is purely extra protection in the event the sieves don't work as expected 28 | if hasexpiration 29 | { 30 | unexpire; 31 | } 32 | fileinto "Government"; 33 | stop; 34 | } -------------------------------------------------------------------------------- /sieves/finance.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # Delete after 30 days (no need to retain routine update emails) 4 | # These emails get routed to the main inbox 5 | if address :matches :domain "from" ["*personalcapital.com", "*nerdwallet.com", "*experian.com", "*equifax.com"] 6 | { 7 | expire "day" "30"; 8 | stop; 9 | } 10 | 11 | # Commonly used financial services 12 | elsif address :matches :domain "from" ["*mtb.com", "*mtbemail.com", "*mtbalerts.com", "*mandtbank.com", "*synchronybank.com", "*synchronyfinancial.com", "*chase.com", 13 | "*jpmorgan.com", "*bankofamerica.com", "*marcus.com", "*visa.com", "*intuit.com", "*serve.com", "*barclaycardus.com", "*venmo.com", "*ngfcu.us", 14 | "*personalcapital.com", "*nerdwallet.com", "*experian.com", "*equifax.com", "*nelnet.net", "*wealthfront.com", "*paypal.com", "*citi.com", "*earnest.com"] 15 | { 16 | fileinto "Finance/Banking"; 17 | expire "day" "365"; 18 | stop; 19 | } -------------------------------------------------------------------------------- /sieves/zz-orders-and-shipping.sieve: -------------------------------------------------------------------------------- 1 | require ["fileinto", "imap4flags", "vnd.proton.expire"]; 2 | 3 | # General catch all for delivery events. 4 | # Delete after 180 days, as this info can almost always be retrieved from the source of truth 5 | # Sieve ordering is important here, as you can receive an email that contains "order delivered" in the subject (which would conflict with the "order handling" sieve that follows). 6 | if header :contains "subject" ["shipping", "shipped", "delivered", "delivery"] 7 | { 8 | # folder 9 | fileinto "Orders and Shipping"; 10 | # label 11 | fileinto "Deliveries"; 12 | expire "day" "180"; 13 | stop; 14 | } 15 | 16 | # General catch all for orders. 17 | # Do not expire, as these emails might be historically significant. 18 | if header :contains "subject" ["order", "invoice", "receipt"] 19 | { 20 | # This is purely extra protection in the event the sieves don't work as expected 21 | if hasexpiration 22 | { 23 | unexpire; 24 | } 25 | # folder 26 | fileinto "Orders and Shipping"; 27 | # label 28 | fileinto "Orders"; 29 | stop; 30 | } 31 | 32 | # More targeted for catching general communication and updates. 33 | # Delete after 180 days, as this info can almost always be retrieved from the source of truth. 34 | elsif address :matches :domain "from" ["*ups.com", "*upsemail.com", "*usps.com", "*fedex.com", "*narvar.com", "*etsy.com", "*amazon.com", "*newegg.com", "*rei.com", "*target.com", "*prana.com", "*walmart.com"] 35 | { 36 | fileinto "Orders and Shipping"; 37 | expire "day" "180"; 38 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Email Sieve Filters 2 | 3 | My personal sieve filters for use with [ProtonMail](https://protonmail.com/). 4 | Sieves are commonly referred to as email filters. 5 | 6 | While these are personal sieves, most of these are general purpose and can be modified/extended as needed. 7 | 8 | Contributions are accepted. 9 | 10 | ## Design Philosophy 11 | 12 | - When in doubt, default to expiring emails. Few things truly need to be kept indefinitely. 13 | - As of 03-18-2021, ProtonMail supports an undocumented maximum expiration of 120 days. Setting a value greater than 120 days will be silently accepted with no server side errors, defaulting to the max of 120 days. Keep this in mind. 14 | - Avoid using a global expiration rule with an "opt-in" model for retention. Emails are too unpredictable - it's a delicate balance. 15 | - Biannual offsite backups should address what concerns may arise from this strategy. 16 | - Scheduled/routine emails of importance (example - daily finance updates) should route to the main inbox, but expire quickly. 17 | - Keep it simple and avoid having too many folders and labels. 18 | - A doctors appointment email and a restaurant reservation email can go to the same folder, and that's okay. 19 | - No organizatonal system will ever be perfect. Expect _and_ accept occasional inefficiencies. 20 | - Think carefully about the execution chain. Use stops when warranted. 21 | - Use subject header parsing with caution, but don't be afraid. It's an immensely powerful routing mechanism. 22 | - IMAP uses [modified UTF-7](https://tools.ietf.org/html/rfc5228#section-2.1). Ensure folder and label names are in the [US-ASCII](https://www.charset.org/charsets/us-ascii) range. 23 | - You can only test so much before going to "prod". Ensure sieves handling ~critical emails explicitly check for _and_ resolve unintended states, such as expirations. 24 | 25 | ## Useful Links 26 | 27 | - [ProtonMail Sieve Docs](https://protonmail.com/support/knowledge-base/sieve-advanced-custom-filters/) 28 | - [Sieve Tutorial](https://p5r.uk/blog/2011/sieve-tutorial.html) 29 | - [Official Sieve Wiki](http://sieve.info/) 30 | - [Sieve Language RFC](https://tools.ietf.org/html/rfc5228) 31 | - [IMAP RFC](https://tools.ietf.org/html/rfc3501) 32 | 33 | ## Testing 34 | 35 | - [Web app for testing sieves](https://www.fastmail.com/cgi-bin/sievetest.pl) 36 | - NOTE - ensure NO personaly identifiable information is pasted into this tool. 37 | - Pro Tip - this app is not aware of the "vnd.proton.expire" package. Remove it when testing with this app. 38 | - When adding the sieve to ProtonMail, basic linting is performed server-side. 39 | 40 | ## Deployment 41 | 42 | - Manually copy and paste the definitions into [ProtonMail Filters](https://beta.protonmail.com/u/0/settings/filters#custom) 43 | - "Ads" sieve is executed 1st (00) 44 | - "Security" sieve is executed 2nd (01) 45 | - "Statements" sieve is executed 3rd (02) 46 | - "Orders and Shipping" sieve is executed last (zz) 47 | 48 | ## To Do 49 | 50 | - Improve local testing by hooking up [sieve script editor](https://github.com/thsmi/sieve) to [docker-mailserver](https://github.com/docker-mailserver/docker-mailserver/wiki/Configure-Sieve-filters) [local dev] 51 | - Add Travis PR tests which check for syntax errors [CI] 52 | - Can likey have Travis run [docker-mailserver](https://github.com/docker-mailserver/docker-mailserver/wiki/Configure-Sieve-filters) and load the sieve filters into memory. Needs further investigation. 53 | - Create test suite with mock email data (CI) 54 | - Automate deployments [CD] 55 | - This is high LOE and has significant security implications. 56 | --------------------------------------------------------------------------------