#!/usr/bin/env python3 """ Dagelijks overzicht van automatisch gesorteerde mail. Zoekt in alle IMAP-mappen (behalve Inbox, Sent, Trash, Spam, Drafts) naar berichten die gisteren zijn binnengekomen, en stuurt een HTML-samenvatting naar hans@australius.nl. Eenmalige setup: python3 dagelijks_overzicht.py --setup Handmatig testen: python3 dagelijks_overzicht.py [--gisteren | --datum 2026-07-01] Plannen (macOS): zie dagelijks_overzicht.plist """ import imaplib import email import email.header import email.utils import json import sys import ssl import smtplib import re import config from datetime import date, timedelta, datetime from pathlib import Path from collections import defaultdict from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText # Mappen die NIET in het overzicht komen (systeem + inbox zelf) UITSLUITINGEN = { "INBOX", "INBOX.Sent", "INBOX.Trash", "INBOX.Spam", "INBOX.Drafts", "INBOX.Spam", "INBOX.Archief", "INBOX.Archief.2020", "INBOX.Archief.2021", "INBOX.Archief.2022", "INBOX.Archief.2023", "INBOX.Archief.2024", "INBOX.Archief.2025", "INBOX.Archief.2026", } # Patroon: archief-mappen en technisch-interne mappen overslaan UITSLUIT_PATRONEN = re.compile( r"^INBOX\.(Archief|Trash|Spam|Sent|Drafts|Technisch)", re.I ) # ── config ──────────────────────────────────────────────────────────────────── def load_config(): """Laad account via centrale config.py. Gebruik --account om te wisselen.""" cfg = config.load() return { "username": cfg.username, "password": cfg.password, "imap_host": cfg.imap_host, "imap_port": cfg.imap_port, "smtp_host": cfg.smtp_host, "smtp_port": cfg.smtp_port, "from": cfg.username, "to": cfg.username, # stuur digest naar zichzelf } # ── helpers ─────────────────────────────────────────────────────────────────── def decode_hdr(value) -> str: if not value: return "" try: parts = email.header.decode_header(value) out = [] for raw, cs in parts: if isinstance(raw, bytes): out.append(raw.decode(cs or "utf-8", errors="replace")) else: out.append(str(raw)) return " ".join(out).strip() except Exception: return str(value or "") def imap_date(d: date) -> str: """Zet date om naar IMAP-zoekformaat: 02-Jul-2026""" return d.strftime("%d-%b-%Y") def leesbare_map(imap_naam: str) -> str: """Maak de mapnaam leesbaar door INBOX. te verwijderen.""" return imap_naam.removeprefix("INBOX.").replace(".", " › ") def parse_folder_name(raw: bytes) -> str | None: decoded = raw.decode("utf-8", errors="replace") m = re.match(r'\(.*?\)\s+".*?"\s+(.*)', decoded) if m: return m.group(1).strip().strip('"') return None # ── IMAP ophalen ────────────────────────────────────────────────────────────── def haal_berichten_op(mail: imaplib.IMAP4_SSL, zoekdatum: date) -> dict[str, list[dict]]: """ Doorzoek alle relevante mappen op berichten van zoekdatum. Geeft {mapnaam: [{from, subject, time}, ...]} terug. """ since = imap_date(zoekdatum) before = imap_date(zoekdatum + timedelta(days=1)) status, raw_folders = mail.list() if status != "OK": return {} alle_mappen = [parse_folder_name(f) for f in raw_folders if f] alle_mappen = [f for f in alle_mappen if f] resultaat: dict[str, list[dict]] = defaultdict(list) for folder in sorted(alle_mappen): if folder in UITSLUITINGEN: continue if UITSLUIT_PATRONEN.match(folder): continue status, data = mail.select(f'"{folder}"', readonly=True) if status != "OK": continue # Zoek op INTERNALDATE (serverontvangst, niet Date-header) status, data = mail.search( None, f'SINCE {since} BEFORE {before}' ) if status != "OK" or not data[0]: continue ids = data[0].split() if not ids: continue for uid in ids: status, msg_data = mail.fetch( uid, "(BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)])" ) if status != "OK" or not msg_data or not msg_data[0]: continue msg = email.message_from_bytes(msg_data[0][1]) from_raw = decode_hdr(msg.get("From", "")) subject = decode_hdr(msg.get("Subject", "(geen onderwerp)")) date_str = msg.get("Date", "") # Tijdstip opmaken try: dt = email.utils.parsedate_to_datetime(date_str) tijdstip = dt.strftime("%H:%M") except Exception: tijdstip = "?" # Afzendernaam uit From-header m = re.match(r'^"?([^"<@\n]{2,})"?\s*<', from_raw) afzender = m.group(1).strip() if m else from_raw.split("@")[0] resultaat[folder].append({ "from": afzender[:40], "subject": subject[:80], "time": tijdstip, }) return resultaat # ── HTML opbouwen ───────────────────────────────────────────────────────────── def bouw_html(berichten: dict[str, list[dict]], zoekdatum: date) -> str: datum_nl = zoekdatum.strftime("%-d %B %Y") totaal = sum(len(v) for v in berichten.values()) html = f"""

Automatisch gesorteerde mail — {datum_nl}

{totaal} bericht{'en' if totaal != 1 else ''} in {len(berichten)} map{'pen' if len(berichten) != 1 else ''}

""" for folder in sorted(berichten.keys()): items = berichten[folder] label = leesbare_map(folder) html += f'

{label} ({len(items)})

\n' html += '\n' for item in items: html += ( f'' f'' f'' f'' f'\n' ) html += '
{item["time"]}{item["from"]}{item["subject"]}
\n' html += f'' html += '' return html # ── e-mail versturen ────────────────────────────────────────────────────────── def stuur_overzicht(html: str, zoekdatum: date, config: dict): datum_nl = zoekdatum.strftime("%-d %B %Y") msg = MIMEMultipart("alternative") msg["Subject"] = f"Mailbak {datum_nl}" msg["From"] = config["from"] msg["To"] = config["to"] msg.attach(MIMEText(html, "html", "utf-8")) context = ssl.create_default_context() with smtplib.SMTP(config["smtp_host"], config["smtp_port"]) as server: server.starttls(context=context) server.login(config["username"], config["password"]) server.sendmail(config["from"], [config["to"]], msg.as_string()) print(f"Overzicht verstuurd naar {config['to']}") # ── main ────────────────────────────────────────────────────────────────────── def run(): args = sys.argv[1:] cfg_dict = load_config() print(f"Account: {cfg_dict['username']}\n") # Datum bepalen if "--datum" in args: idx = args.index("--datum") zoekdatum = date.fromisoformat(args[idx + 1]) else: zoekdatum = date.today() - timedelta(days=1) # standaard: gisteren print(f"Ophalen berichten van {zoekdatum} ...") try: mail = imaplib.IMAP4_SSL(cfg_dict["imap_host"], cfg_dict["imap_port"]) mail.login(cfg_dict["username"], cfg_dict["password"]) except Exception as e: print(f"IMAP verbinding mislukt: {e}") sys.exit(1) berichten = haal_berichten_op(mail, zoekdatum) mail.logout() totaal = sum(len(v) for v in berichten.values()) print(f"{totaal} berichten gevonden in {len(berichten)} mappen.") if totaal == 0: print("Niets te sturen.") return html = bouw_html(berichten, zoekdatum) stuur_overzicht(html, zoekdatum, cfg_dict) if __name__ == "__main__": run()