diff --git a/mail_routes.py b/mail_routes.py index 15c281b..a86d105 100644 --- a/mail_routes.py +++ b/mail_routes.py @@ -1,10 +1,14 @@ """Shared mail routing policy for mailcat.""" import email.utils +import json import re from datetime import datetime +from functools import cache +from pathlib import Path PREFIX = "INBOX." +MAILINGLIST_ROUTES_FILE = Path(__file__).parent / "mailinglist_routes.json" DOMAIN_ROUTES: list[tuple[list[str], str]] = [ (["nl.abnamro.com", "abnamro.nl"], "Financieel.Bank.ABN AMRO"), @@ -104,6 +108,31 @@ EXCLUDED_SOURCE_FOLDERS = { ALREADY_SORTED_FOLDERS = {"INBOX.Facturen - verwerkt"} +@cache +def mailinglist_routes() -> list[dict]: + """Return normalized mailing-list routing rules.""" + if not MAILINGLIST_ROUTES_FILE.exists(): + return [] + return json.loads(MAILINGLIST_ROUTES_FILE.read_text(encoding="utf-8")) + + +def mailinglist_destination(from_addr: str) -> str | None: + """Return a mailing-list destination based on normalized policy rules.""" + domain = sender_domain(from_addr) + from_lo = from_addr.lower() + + for rule in mailinglist_routes(): + domains = {item.lower() for item in rule.get("domains", [])} + if domain and domain in domains: + return rule["mailbox"] + + for needle in rule.get("from_contains", []): + if needle.lower() in from_lo: + return rule["mailbox"] + + return None + + def sender_domain(from_addr: str) -> str: match = re.search(r"@([\w.\-]+)", from_addr) return match.group(1).lower() if match else "" @@ -126,6 +155,8 @@ def route_from_subject(from_addr: str, subject: str) -> str | None: subj_lo = subject.lower() if any(keyword in subj_lo for keyword in FACTUUR_KEYWORDS): return "__FACTUUR_DATE__" + if destination := mailinglist_destination(from_addr): + return destination return DOMAIN_LOOKUP.get(sender_domain(from_addr)) @@ -137,6 +168,11 @@ def is_source_folder(folder: str) -> bool: def destination_folders(start_year: int = 2020, end_year: int = 2026) -> list[str]: """Return all destination folders without the INBOX. prefix.""" folders = {target for _, target in DOMAIN_ROUTES} + folders.update( + rule["mailbox"] + for rule in mailinglist_routes() + if rule.get("mailbox") + ) folders.update( f"Financieel.Facturen.{year}.Q{quarter}" for year in range(start_year, end_year + 1) diff --git a/mailinglist_routes.json b/mailinglist_routes.json new file mode 100644 index 0000000..27a005b --- /dev/null +++ b/mailinglist_routes.json @@ -0,0 +1,924 @@ +[ + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Leren.Coursera", + "domains": ["coursera.org", "email.coursera.org", "m.mail.coursera.org", "m.learn.coursera.org"], + "source_decision_keys": ["coursera.org", "email.coursera.org", "m.mail.coursera.org", "m.learn.coursera.org"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Leren.Storytelling with Data", + "domains": ["storytellingwithdata.com"], + "source_decision_keys": ["storytellingwithdata.com"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Leren.MIT Technology Review", + "domains": ["technologyreview.com", "fulfillment.technologyreview.com"], + "source_decision_keys": ["technologyreview.com", "fulfillment.technologyreview.com"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Overig.Lendahand", + "domains": ["lendahand.com"], + "source_decision_keys": ["lendahand.com"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Leren.Eva Keiffenheim", + "domains": ["evakeiffenheim.com"], + "from_contains": ["evakeiffenheim@substack.com", "eva keiffenheim"], + "source_decision_keys": ["evakeiffenheim.com", "substack.com", "eva keiffenheim s lifelong learning club", "eva keiffenheim from the learn letter"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Overig.The Good Roll", + "domains": ["thegoodroll.nl"], + "source_decision_keys": ["thegoodroll.nl"] + }, + { + "action": "keep", + "mailbox": "Mobiliteit.EV.Eneco eMobility", + "domains": ["eneco-emobility.com", "eneco.nl"], + "source_decision_keys": ["eneco-emobility.com", "eneco.nl"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Leren.Nick Milo", + "domains": ["linkingyourthinking.com"], + "source_decision_keys": ["linkingyourthinking.com"] + }, + { + "action": "keep", + "mailbox": "Mobiliteit.OV & Reizen.NS", + "domains": ["ns.nl", "email.ns.nl"], + "source_decision_keys": ["ns.nl", "email.ns.nl"] + }, + { + "action": "keep", + "mailbox": "Werk.Opdrachten.OneStopSourcing", + "domains": ["onestopsourcing.nl"], + "source_decision_keys": ["onestopsourcing.nl"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Leren.AI Report", + "domains": ["beehiiv.com", "mail.beehiiv.com"], + "source_decision_keys": ["beehiiv.com", "mail.beehiiv.com"] + }, + { + "action": "keep", + "mailbox": "Werk.Opdrachten.Flextender", + "domains": ["flextender.nl"], + "source_decision_keys": ["flextender.nl"] + }, + { + "action": "keep", + "mailbox": "Financieel.Verzekering.Unive", + "domains": ["unive.nl", "nieuwsbrief.unive.nl"], + "source_decision_keys": ["nieuwsbrief.unive.nl"] + }, + { + "action": "keep", + "mailbox": "Financieel.Bank.ICS", + "domains": ["icscards.nl", "icsmarketing.icscards.nl", "service.icscards.nl"], + "source_decision_keys": ["icsmarketing.icscards.nl"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Nieuws & Vakbladen.Vakblad Crisismanager", + "domains": ["crisismanager.nl"], + "source_decision_keys": ["crisismanager.nl"] + }, + { + "action": "keep", + "mailbox": "Diensten.Software.CogSci Apps", + "domains": ["cogsciapps.com"], + "source_decision_keys": ["cogsciapps.com"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Leren.Ben Tiggelaar", + "domains": ["tiggelaar.nl"], + "source_decision_keys": ["tiggelaar.nl"] + }, + { + "action": "keep", + "mailbox": "Werk.Opdrachten.IMMENS", + "domains": ["immens.nu"], + "source_decision_keys": ["immens.nu"] + }, + { + "action": "keep", + "mailbox": "Financieel.Bank.ABN AMRO", + "domains": ["nl.abnamro.com", "abnamro.nl"], + "source_decision_keys": ["nl.abnamro.com"] + }, + { + "action": "keep", + "mailbox": "Bestellingen.PostNL", + "domains": ["postnl.nl", "notificatie.postnl.nl", "edm.postnl.nl"], + "source_decision_keys": ["postnl.nl", "notificatie.postnl.nl", "edm.postnl.nl"] + }, + { + "action": "keep", + "mailbox": "Bestellingen.123inkt", + "domains": ["123inkt.nl", "m.123inkt.nl"], + "source_decision_keys": ["123inkt.nl", "m.123inkt.nl"] + }, + { + "action": "keep", + "mailbox": "Bestellingen.DHL", + "domains": ["dhlparcel.nl", "proxy.privault.rpip.nl"], + "source_decision_keys": ["dhlparcel.nl", "proxy.privault.rpip.nl"] + }, + { + "action": "keep", + "mailbox": "Bestellingen.GLS", + "domains": ["gls-netherlands.com"], + "source_decision_keys": ["gls-netherlands.com"] + }, + { + "action": "keep", + "mailbox": "Diensten.Hosting.STRATO", + "domains": ["strato.com", "news.strato.com", "marketingradar.strato.com"], + "source_decision_keys": ["strato.com", "news.strato.com", "marketingradar.strato.com"] + }, + { + "action": "keep", + "mailbox": "Diensten.Telecom.Vodafone", + "domains": ["zakelijk.vodafone.nl"], + "source_decision_keys": ["zakelijk.vodafone.nl"] + }, + { + "action": "keep", + "mailbox": "Diensten.Software.Formspree", + "domains": ["formspree.io"], + "source_decision_keys": ["formspree.io"] + }, + { + "action": "keep", + "mailbox": "Diensten.Telecom.KPN", + "domains": ["campagnes.kpn.com", "kpn.com"], + "source_decision_keys": ["campagnes.kpn.com", "kpn.com"] + }, + { + "action": "keep", + "mailbox": "Diensten.Hosting.Lets Encrypt", + "domains": ["letsencrypt.org"], + "source_decision_keys": ["letsencrypt.org"] + }, + { + "action": "keep", + "mailbox": "Diensten.Backup.Backblaze", + "domains": ["backblaze.com"], + "source_decision_keys": ["backblaze.com"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Leren.Brian Keating", + "domains": ["briankeating.com"], + "source_decision_keys": ["briankeating.com"] + }, + { + "action": "keep", + "mailbox": "Werk.Opdrachten.Get There", + "domains": ["getthere.nl"], + "source_decision_keys": ["getthere.nl"] + }, + { + "action": "keep", + "mailbox": "Werk.Opdrachten.Jamie", + "domains": ["hey.meetjamie.ai"], + "source_decision_keys": ["hey.meetjamie.ai"] + }, + { + "action": "keep", + "mailbox": "Werk.Opdrachten.Huckr", + "domains": ["huckr.ai"], + "source_decision_keys": ["huckr.ai"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Leren.MIT Open Learning", + "domains": ["mit.edu"], + "source_decision_keys": ["mit.edu"] + }, + { + "action": "keep", + "mailbox": "Bestellingen.Allekabels", + "domains": ["allekabels.nl"], + "source_decision_keys": ["allekabels.nl"] + }, + { + "action": "keep", + "mailbox": "Mobiliteit.OV & Reizen.Booking", + "domains": ["booking.com"], + "source_decision_keys": ["booking.com"] + }, + { + "action": "keep", + "mailbox": "Diensten.Software.Sophos", + "domains": ["cleverbridge.com", "home.sophos.com"], + "source_decision_keys": ["cleverbridge.com", "home.sophos.com"] + }, + { + "action": "keep", + "mailbox": "Mobiliteit.EV.FastNed", + "domains": ["fastned.nl"], + "source_decision_keys": ["fastned.nl"] + }, + { + "action": "keep", + "mailbox": "Mobiliteit.EV.OPnGO", + "domains": ["opngo.com"], + "source_decision_keys": ["opngo.com"] + }, + { + "action": "keep", + "mailbox": "Mobiliteit.EV.Tesla", + "domains": ["tesla.com"], + "source_decision_keys": ["tesla.com"] + }, + { + "action": "keep", + "mailbox": "Diensten.Software.Cursor", + "domains": ["cursor.com"], + "source_decision_keys": ["cursor.com"] + }, + { + "action": "keep", + "mailbox": "Diensten.Software.1Password", + "domains": ["1password.com"], + "source_decision_keys": ["1password.com"] + }, + { + "action": "keep", + "mailbox": "Bestellingen.Viking", + "domains": ["delivery.vikingdirect.nl", "online.vikingdirect.nl", "vikingdirect.nl"], + "source_decision_keys": ["delivery.vikingdirect.nl", "online.vikingdirect.nl"] + }, + { + "action": "keep", + "mailbox": "Diensten.Software.Matter", + "domains": ["getmatter.com"], + "source_decision_keys": ["getmatter.com"] + }, + { + "action": "keep", + "mailbox": "Werk.Overig.Gemeente Groningen", + "domains": ["groningen.nl", "groningenbereikbaar.nl"], + "source_decision_keys": ["groningen.nl", "groningenbereikbaar.nl"] + }, + { + "action": "keep", + "mailbox": "Financieel.Verzekering.VEZA", + "domains": ["heinenoord.nl"], + "source_decision_keys": ["heinenoord.nl"] + }, + { + "action": "keep", + "mailbox": "Diensten.Software.Kahoot", + "domains": ["team.kahoot.com"], + "source_decision_keys": ["team.kahoot.com"] + }, + { + "action": "keep", + "mailbox": "Diensten.Hosting.TransIP", + "domains": ["transip.nl"], + "source_decision_keys": ["transip.nl"] + }, + { + "action": "keep", + "mailbox": "Diensten.Software.Indigo Neo", + "domains": ["indigoneo.com"], + "source_decision_keys": ["indigoneo.com"] + }, + { + "action": "keep", + "mailbox": "Bestellingen.Office Centre", + "domains": ["officecentre.nl"], + "source_decision_keys": ["officecentre.nl"] + }, + { + "action": "keep", + "mailbox": "Mobiliteit.EV.Shell Recharge", + "domains": ["shellrecharge.com"], + "source_decision_keys": ["shellrecharge.com"] + }, + { + "action": "keep", + "mailbox": "Bestellingen.Smartphonehoesjes", + "domains": ["smartphonehoesjes.nl"], + "source_decision_keys": ["smartphonehoesjes.nl"] + }, + { + "action": "keep", + "mailbox": "Werk.Overig.BOIP", + "domains": ["boip.int"], + "source_decision_keys": ["boip.int"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Leren.Elizabeth Butler", + "domains": ["elizabethbutlermd.com"], + "source_decision_keys": ["elizabethbutlermd.com"] + }, + { + "action": "keep", + "mailbox": "Bestellingen.bol", + "domains": ["bol.com", "feedback.bol.com"], + "source_decision_keys": ["bol.com", "feedback.bol.com"] + }, + { + "action": "keep", + "mailbox": "Mobiliteit.OV & Reizen.Flitsmeister", + "domains": ["flitsmeister.nl"], + "source_decision_keys": ["flitsmeister.nl"] + }, + { + "action": "keep", + "mailbox": "Werk.Opdrachten.FutureXL", + "domains": ["futurexl.nl"], + "source_decision_keys": ["futurexl.nl"] + }, + { + "action": "keep", + "mailbox": "Financieel.Bank.NPEX", + "domains": ["npex.nl"], + "source_decision_keys": ["npex.nl"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Nieuws & Vakbladen.The Presentation Guru", + "domains": ["presentation-guru.com"], + "source_decision_keys": ["presentation-guru.com"] + }, + { + "action": "keep", + "mailbox": "Bestellingen.Sligro", + "domains": ["sligro.nl"], + "source_decision_keys": ["sligro.nl"] + }, + { + "action": "keep", + "mailbox": "Financieel.Bank.SnelStart", + "domains": ["snelstart.nl"], + "source_decision_keys": ["snelstart.nl"] + }, + { + "action": "keep", + "mailbox": "Financieel.Bank.Tellow", + "domains": ["tellow.nl"], + "source_decision_keys": ["tellow.nl"] + }, + { + "action": "keep", + "mailbox": "Bestellingen.Coolblue", + "domains": ["coolblue.nl", "coolblue.eu"], + "source_decision_keys": ["coolblue.nl", "coolblue.eu"] + }, + { + "action": "keep", + "mailbox": "Nieuwsbrieven.Nieuws & Vakbladen.Computable", + "domains": ["jaarbeurs.nl"], + "source_decision_keys": ["jaarbeurs.nl"] + }, + { + "action": "keep", + "mailbox": "Diensten.Software.Microsoft Teams", + "domains": ["microsoft365.com"], + "source_decision_keys": ["microsoft365.com"] + }, + { + "action": "keep", + "mailbox": "Mobiliteit.EV.Mitsubishi", + "domains": ["mmsn.nl"], + "source_decision_keys": ["mmsn.nl"] + }, + { + "action": "keep", + "mailbox": "Diensten.Software.Claude", + "domains": ["claude.com"], + "source_decision_keys": ["claude.com"] + }, + { + "action": "keep", + "mailbox": "Bestellingen.Makro", + "domains": ["makro.nl"], + "source_decision_keys": ["makro.nl"] + }, + { + "action": "keep", + "mailbox": "Diensten.Software.MindNode", + "domains": ["mindnode.com"], + "source_decision_keys": ["mindnode.com"] + }, + { + "action": "keep", + "mailbox": "Diensten.Netwerk.Netherfind", + "domains": ["netherfind.com"], + "source_decision_keys": ["netherfind.com"] + }, + { + "action": "keep", + "mailbox": "Diensten.Software.Papers", + "domains": ["papersapp.com"], + "source_decision_keys": ["papersapp.com"] + }, + { + "action": "keep", + "mailbox": "Diensten.Software.Perplexity", + "domains": ["perplexity.ai"], + "source_decision_keys": ["perplexity.ai"] + }, + { + "action": "keep", + "mailbox": "Diensten.Email.Proton", + "domains": ["mail.proton.me"], + "source_decision_keys": ["mail.proton.me"], + "notes": "Zero-count keep alias from decisions.json; proton.me and notify.proton.me remain Afmelden entries." + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Netwerken.ResearchGate", + "domains": ["researchgatemail.net"], + "source_decision_keys": ["researchgatemail.net"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Fitness.STARS Personal Training", + "domains": ["virtuagym.com"], + "source_decision_keys": ["virtuagym.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Netwerken.LinkedIn", + "domains": ["linkedin.com"], + "source_decision_keys": ["linkedin.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Nieuws.Het Financieele Dagblad", + "domains": ["messagent.fdmediagroep.nl"], + "source_decision_keys": ["messagent.fdmediagroep.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Werk.FixedToday", + "domains": ["fixedtoday.nl"], + "source_decision_keys": ["fixedtoday.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Werk.Centre of Excellence Data Sharing Cloud", + "domains": ["coe-dsc.nl"], + "source_decision_keys": ["coe-dsc.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Netwerken.CIONET", + "domains": ["mn.co", "cionet.com"], + "source_decision_keys": ["mn.co", "cionet.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Winkels.Gusti", + "domains": ["gusti-leer.nl", "gusti-leder.de", "gusti-pelle.it"], + "source_decision_keys": ["gusti-leer.nl", "gusti-leder.de", "gusti-pelle.it"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Leren.Forte Labs", + "domains": ["fortelabs.co", "fortelabs.com"], + "source_decision_keys": ["fortelabs.co", "fortelabs.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Telecom.Vonage", + "domains": ["vonage.com", "insights.vonage.com", "nexmo.com"], + "source_decision_keys": ["vonage.com", "insights.vonage.com", "nexmo.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Beveiliging.Nord", + "domains": ["nordvpn.com", "nordpass.com"], + "source_decision_keys": ["nordvpn.com", "nordpass.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Bestellingen.DHL eCommerce", + "domains": ["dhlecommerce.nl"], + "source_decision_keys": ["dhlecommerce.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Leren.Taproot", + "domains": ["taproot.com"], + "source_decision_keys": ["taproot.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Productiviteit.Readwise", + "domains": ["readwise.io"], + "source_decision_keys": ["readwise.io"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Leren.Scrimba", + "domains": ["scrimba.com"], + "source_decision_keys": ["scrimba.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.Kilo Code", + "domains": ["kilocode.ai"], + "source_decision_keys": ["kilocode.ai"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Werk.Jooble", + "domains": ["nl.jooble.org"], + "source_decision_keys": ["nl.jooble.org"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Werk.Pleio IBDS", + "domains": ["pleio.nl", "mail.pleio.nl"], + "source_decision_keys": ["pleio.nl", "mail.pleio.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Leren.FUN MOOC", + "domains": ["fun-mooc.fr"], + "source_decision_keys": ["fun-mooc.fr"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.Stackfield", + "domains": ["stackfield.help"], + "source_decision_keys": ["stackfield.help"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Werk.De Staffing Groep", + "domains": ["destaffinggroep.nl"], + "source_decision_keys": ["destaffinggroep.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Leren.Kaggle", + "domains": ["kaggle.com"], + "source_decision_keys": ["kaggle.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Diensten.Proton", + "domains": ["proton.me", "notify.proton.me"], + "source_decision_keys": ["proton.me", "notify.proton.me"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.Genially", + "domains": ["genially.com"], + "source_decision_keys": ["genially.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Backup.IDrive", + "domains": ["idrive.com"], + "source_decision_keys": ["idrive.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Projectmanagement.PMRGI", + "domains": ["oppmi.com"], + "source_decision_keys": ["oppmi.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Energie.Zelf Energie Produceren", + "domains": ["zelfenergieproduceren.nl"], + "source_decision_keys": ["zelfenergieproduceren.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.Nuclino", + "domains": ["nuclino.com"], + "source_decision_keys": ["nuclino.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Apparaten.reMarkable", + "domains": ["remarkable.com"], + "source_decision_keys": ["remarkable.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.Olares", + "domains": ["olares.com"], + "source_decision_keys": ["olares.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Schrijven.Schrijf.be", + "domains": ["schrijf.be"], + "source_decision_keys": ["schrijf.be"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.VMware", + "domains": ["connect.vmware.com"], + "source_decision_keys": ["connect.vmware.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.Audiogest", + "domains": ["loops.audiogest.app"], + "source_decision_keys": ["loops.audiogest.app"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.SmallCubed", + "domains": ["smallcubed.com"], + "source_decision_keys": ["smallcubed.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Energie.Energie Collectief Nederland", + "domains": ["titanomon.com"], + "source_decision_keys": ["titanomon.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Telecom.Vodafone", + "domains": ["vodafone.nl"], + "source_decision_keys": ["vodafone.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Mobiliteit.Fulli", + "domains": ["fulli.com"], + "source_decision_keys": ["fulli.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Advertenties.Google Ads", + "domains": ["google.com"], + "source_decision_keys": ["google.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.Saga", + "domains": ["saga.so"], + "source_decision_keys": ["saga.so"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Financieel.Triodos", + "domains": ["triodos.nl"], + "source_decision_keys": ["triodos.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.TurboScribe", + "domains": ["turboscribe.ai"], + "source_decision_keys": ["turboscribe.ai"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Financieel.bunq", + "domains": ["bunq.com"], + "source_decision_keys": ["bunq.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Winkels.Airo Paradise", + "domains": ["ekwb.nl"], + "source_decision_keys": ["ekwb.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.Parallels", + "domains": ["parallels-universe.com"], + "source_decision_keys": ["parallels-universe.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Leren.OReilly", + "domains": ["et.oreilly.com"], + "source_decision_keys": ["et.oreilly.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.Visme", + "domains": ["accounts.visme.com", "visme.co"], + "source_decision_keys": ["accounts.visme.com", "visme.co"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Winkels.De Vulpenwereld", + "domains": ["devulpenwereld.nl"], + "source_decision_keys": ["devulpenwereld.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Privacy.Incogni", + "domains": ["incogni.com"], + "source_decision_keys": ["incogni.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.Smartsheet", + "domains": ["smartsheet.com"], + "source_decision_keys": ["smartsheet.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Netwerken.YourConnector", + "domains": ["yourconnector.nl"], + "source_decision_keys": ["yourconnector.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.Zapier", + "domains": ["zapier.com"], + "source_decision_keys": ["zapier.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.ClickUp", + "domains": ["clickup.com"], + "source_decision_keys": ["clickup.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Presenteren.Duarte", + "domains": ["duarte.com"], + "source_decision_keys": ["duarte.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Winkels.PrintAbout", + "domains": ["printabout.nl"], + "source_decision_keys": ["printabout.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Cloud.Microsoft Azure", + "domains": ["promomail.microsoft.com"], + "source_decision_keys": ["promomail.microsoft.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Productiviteit.Tana", + "domains": ["tana.inc"], + "source_decision_keys": ["tana.inc"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Leren.TIAS", + "domains": ["tias.edu"], + "source_decision_keys": ["tias.edu"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Media.VALUE IPTV VOD", + "domains": ["weblinux.de"], + "source_decision_keys": ["weblinux.de"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Productiviteit.Capacities", + "domains": ["capacities.io"], + "source_decision_keys": ["capacities.io"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Nieuws.Computable Enigma Research", + "domains": ["enigmaresearch.nl"], + "source_decision_keys": ["enigmaresearch.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Winkels.Euroflorist", + "domains": ["euroflorist.nl"], + "source_decision_keys": ["euroflorist.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Financieel.IEX Cloud", + "domains": ["iexcloud.io"], + "source_decision_keys": ["iexcloud.io"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Diensten.Jetpack", + "domains": ["jetpack.com"], + "source_decision_keys": ["jetpack.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.Linearity", + "domains": ["linearity.io"], + "source_decision_keys": ["linearity.io"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.LiquidText", + "domains": ["liquidtext.net"], + "source_decision_keys": ["liquidtext.net"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Software.MURAL", + "domains": ["mural.co"], + "source_decision_keys": ["mural.co"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Winkels.Office Noord", + "domains": ["officenoord.nl"], + "source_decision_keys": ["officenoord.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Presenteren.Prezent", + "domains": ["prezent.ai"], + "source_decision_keys": ["prezent.ai"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Projectmanagement.Wrike", + "domains": ["team.wrike.com"], + "source_decision_keys": ["team.wrike.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Mobiliteit.Alfen", + "domains": ["alfen.com"], + "source_decision_keys": ["alfen.com"], + "notes": "Conflicts with old EV keep route; decisions.json takes precedence." + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Productiviteit.GRID", + "domains": ["gridnewsletter.com"], + "source_decision_keys": ["gridnewsletter.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Legal.Henchman", + "domains": ["henchman.io"], + "source_decision_keys": ["henchman.io"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Presenteren.Pitch", + "domains": ["hi.pitch.com"], + "source_decision_keys": ["hi.pitch.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Wonen.How To Buy A Home", + "domains": ["howtobuyahome.com"], + "source_decision_keys": ["howtobuyahome.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Leren.Kodeco", + "domains": ["kodeco.com"], + "source_decision_keys": ["kodeco.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Nieuws.Medium", + "domains": ["medium.com"], + "source_decision_keys": ["medium.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Productiviteit.MindMeister", + "domains": ["mindmeister.com"], + "source_decision_keys": ["mindmeister.com"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Fitness.NEXT Personal Gym", + "domains": ["next-sports.nl"], + "source_decision_keys": ["next-sports.nl"] + }, + { + "action": "afmelden", + "mailbox": "Afmelden.Projectmanagement.TeamGantt", + "domains": ["teamgantt.com"], + "source_decision_keys": ["teamgantt.com"] + }, + { + "action": "review", + "mailbox": "Werk.Opdrachten.Circle8", + "domains": ["circle8.nl"], + "source_decision_keys": ["circle8.nl"], + "notes": "Decision value is s; keep as review until the user confirms the meaning." + } +] diff --git a/reports/decisions_mailinglist_routing_report.md b/reports/decisions_mailinglist_routing_report.md index 6e28cac..ab2934a 100644 --- a/reports/decisions_mailinglist_routing_report.md +++ b/reports/decisions_mailinglist_routing_report.md @@ -14,7 +14,9 @@ Purpose: interpret `decisions.json` as the user intent for mailing-list handling - `a` ("afmelden"): 89 entries, 2,683 messages. - `s`: 1 entry, 26 messages. -The current sorter does not read `decisions.json`. It only reads the hardcoded routes in `mail_routes.py`. Therefore these decisions are currently advisory data, not active routing behavior. +The sorter still does not read `decisions.json` directly. The decisions have been normalized into `mailinglist_routes.json`, which is now loaded by `mail_routes.py` for routing and destination-folder generation. + +The normalized policy contains 153 grouped rules and covers every key in `decisions.json` through `source_decision_keys`. ## Interpretation @@ -247,21 +249,21 @@ These decisions conflict with the current hardcoded route table or expose incomp ## Plan To Add This Structure -1. Introduce a structured policy file generated from `decisions.json`. +1. Introduce a structured policy file generated from `decisions.json`. Done. - Suggested file: `mailinglist_routes.json`. - - Fields: `action`, `mailbox`, `domains`, `sender_patterns`, `list_id_patterns`, `source_decision_keys`, `notes`. + - Fields: `action`, `mailbox`, `domains`, `from_contains`, `source_decision_keys`, `notes`. - Do not make `decisions.json` itself the runtime file; it contains unsubscribe URLs and analysis artifacts. -2. Extend `mail_routes.py` to load mailing-list policy. +2. Extend `mail_routes.py` to load mailing-list policy. Done for domain and `From` substring matching. - Keep `DOMAIN_ROUTES` for stable functional/business mail. - Add a runtime lookup for generated mailing-list rules. - Support domain matching, exact sender address matching, display-name matching, and List-ID/header matching. -3. Add `Afmelden` to destination folder creation. +3. Add `Afmelden` to destination folder creation. Done through `destination_folders()`. - `maak_mappen.py` should create all `INBOX.Afmelden.*` folders from the generated policy. - Existing `destination_folders()` should include both functional keep folders and unsubscribe review folders. -4. Update routing precedence. +4. Update routing precedence. Done for invoice, mailing-list policy, then legacy domain fallback. - Invoice keyword routing should remain first. - Exact/list-specific mailing-list rules should run before broad domain rules, especially for shared senders such as `substack.com`. - Domain rules should remain the fallback for normal service/vendor mail. @@ -280,4 +282,3 @@ These decisions conflict with the current hardcoded route table or expose incomp - Run dry-run sorter on backup. - Review planned moves. - Run actual sorter only after explicit confirmation. - diff --git a/restart_prompt.md b/restart_prompt.md index 2177c6c..51f2966 100644 --- a/restart_prompt.md +++ b/restart_prompt.md @@ -161,7 +161,11 @@ That report interprets `decisions.json` as advisory intent: - `keuze: "a"` means route below `INBOX.Afmelden.*` for unsubscribe/review. - `keuze: "s"` is ambiguous and should be confirmed before automation. - Obvious aliases from the same sender or organization should be combined, including Coursera, MIT Technology Review, Eva Keiffenheim/Substack, PostNL, STRATO, Forte Labs, CIONET, Gusti, Nord, Proton, Vonage/Nexmo, and Visme. -- The report is not active code yet; `verplaats_bestaand.py` still routes through `mail_routes.py`. +- The normalized policy is now in `mailinglist_routes.json`; `mail_routes.py` loads it before falling back to legacy `DOMAIN_ROUTES`. +- `mailinglist_routes.json` has 153 grouped rules and covers every key from `decisions.json` through `source_decision_keys`. +- `destination_folders()` now includes functional keep folders plus `Afmelden.*` folders from `mailinglist_routes.json`. +- Invoice keyword routing still takes precedence over mailing-list routing. +- Do not run mailbox-affecting scripts yourself in this project. The user wants to run scripts personally when instructed. Key earlier findings: @@ -174,6 +178,9 @@ Known remaining risk: - `kopieer_naar_backup.py` should be hardened so append failures cannot be silently marked as folder completion. - `verplaats_bestaand.py` should log failures with source folder, UID, destination, and server response. +- Mailing-list routing currently supports domain and `From` substring policy matching. It does not yet fetch or match `List-ID` headers. +- `circle8.nl` still has ambiguous decision value `s`; keep it as review/functional routing until the user confirms the meaning. +- `alfen.com`, `vodafone.nl`, `dhlecommerce.nl`, and Proton splits are implemented according to `decisions.json` precedence, but remain notable policy changes compared with older hardcoded domain routes. ## What To Do Next