diff --git a/._. b/._.
new file mode 100755
index 0000000..ff33f06
Binary files /dev/null and b/._. differ
diff --git a/._.claude b/._.claude
new file mode 100755
index 0000000..ff33f06
Binary files /dev/null and b/._.claude differ
diff --git a/._.gitignore b/._.gitignore
new file mode 100644
index 0000000..cc665df
Binary files /dev/null and b/._.gitignore differ
diff --git a/._AGENTS.md b/._AGENTS.md
new file mode 100644
index 0000000..cc665df
Binary files /dev/null and b/._AGENTS.md differ
diff --git a/._analyse_mailbox.py b/._analyse_mailbox.py
new file mode 100644
index 0000000..ff33f06
Binary files /dev/null and b/._analyse_mailbox.py differ
diff --git a/._config.py b/._config.py
new file mode 100644
index 0000000..ff33f06
Binary files /dev/null and b/._config.py differ
diff --git a/._dagelijks_overzicht.plist b/._dagelijks_overzicht.plist
new file mode 100644
index 0000000..ff33f06
Binary files /dev/null and b/._dagelijks_overzicht.plist differ
diff --git a/._dagelijks_overzicht.py b/._dagelijks_overzicht.py
new file mode 100644
index 0000000..838a630
Binary files /dev/null and b/._dagelijks_overzicht.py differ
diff --git a/._decisions.json b/._decisions.json
new file mode 100644
index 0000000..ff33f06
Binary files /dev/null and b/._decisions.json differ
diff --git a/._decisions.json.bak b/._decisions.json.bak
new file mode 100644
index 0000000..ff33f06
Binary files /dev/null and b/._decisions.json.bak differ
diff --git a/._deploy_vps.sh b/._deploy_vps.sh
new file mode 100755
index 0000000..ff33f06
Binary files /dev/null and b/._deploy_vps.sh differ
diff --git a/._domain_routes.json b/._domain_routes.json
new file mode 100644
index 0000000..cc665df
Binary files /dev/null and b/._domain_routes.json differ
diff --git a/._download_mailbox.py b/._download_mailbox.py
new file mode 100644
index 0000000..bd9b2ab
Binary files /dev/null and b/._download_mailbox.py differ
diff --git a/._imap_utils.py b/._imap_utils.py
new file mode 100644
index 0000000..cc665df
Binary files /dev/null and b/._imap_utils.py differ
diff --git a/._kopieer_naar_backup.py b/._kopieer_naar_backup.py
new file mode 100644
index 0000000..bd9b2ab
Binary files /dev/null and b/._kopieer_naar_backup.py differ
diff --git a/._maak_mappen.py b/._maak_mappen.py
new file mode 100644
index 0000000..bd9b2ab
Binary files /dev/null and b/._maak_mappen.py differ
diff --git a/._mail_imap_ops.py b/._mail_imap_ops.py
new file mode 100644
index 0000000..cc665df
Binary files /dev/null and b/._mail_imap_ops.py differ
diff --git a/._mail_routes.py b/._mail_routes.py
new file mode 100644
index 0000000..cc665df
Binary files /dev/null and b/._mail_routes.py differ
diff --git a/._mailing_lists_cache.json.bak b/._mailing_lists_cache.json.bak
new file mode 100644
index 0000000..838a630
Binary files /dev/null and b/._mailing_lists_cache.json.bak differ
diff --git a/._mailinglist_routes.json b/._mailinglist_routes.json
new file mode 100644
index 0000000..cc665df
Binary files /dev/null and b/._mailinglist_routes.json differ
diff --git a/._mailrules.sieve b/._mailrules.sieve
new file mode 100644
index 0000000..ff33f06
Binary files /dev/null and b/._mailrules.sieve differ
diff --git a/._manage_routes_web.py b/._manage_routes_web.py
new file mode 100644
index 0000000..cc665df
Binary files /dev/null and b/._manage_routes_web.py differ
diff --git a/._plan.md b/._plan.md
new file mode 100644
index 0000000..ff33f06
Binary files /dev/null and b/._plan.md differ
diff --git a/._prompt.md b/._prompt.md
new file mode 100644
index 0000000..ff33f06
Binary files /dev/null and b/._prompt.md differ
diff --git a/._reports b/._reports
new file mode 100755
index 0000000..ff33f06
Binary files /dev/null and b/._reports differ
diff --git a/._restart_prompt.md b/._restart_prompt.md
new file mode 100644
index 0000000..ff33f06
Binary files /dev/null and b/._restart_prompt.md differ
diff --git a/._review_mailinglists.py b/._review_mailinglists.py
new file mode 100644
index 0000000..838a630
Binary files /dev/null and b/._review_mailinglists.py differ
diff --git a/._sort_mail_daemon.py b/._sort_mail_daemon.py
new file mode 100755
index 0000000..cc665df
Binary files /dev/null and b/._sort_mail_daemon.py differ
diff --git a/._systemd b/._systemd
new file mode 100755
index 0000000..cc665df
Binary files /dev/null and b/._systemd differ
diff --git a/._temp.md b/._temp.md
new file mode 100644
index 0000000..ff33f06
Binary files /dev/null and b/._temp.md differ
diff --git a/._tests b/._tests
new file mode 100755
index 0000000..cc665df
Binary files /dev/null and b/._tests differ
diff --git a/._unsubscribe.py b/._unsubscribe.py
new file mode 100644
index 0000000..838a630
Binary files /dev/null and b/._unsubscribe.py differ
diff --git a/._upload_sieve.py b/._upload_sieve.py
new file mode 100644
index 0000000..838a630
Binary files /dev/null and b/._upload_sieve.py differ
diff --git a/._verplaats_bestaand.py b/._verplaats_bestaand.py
new file mode 100644
index 0000000..bd9b2ab
Binary files /dev/null and b/._verplaats_bestaand.py differ
diff --git a/._web_config.example.json b/._web_config.example.json
new file mode 100644
index 0000000..cc665df
Binary files /dev/null and b/._web_config.example.json differ
diff --git a/.claude/._settings.local.json b/.claude/._settings.local.json
new file mode 100644
index 0000000..ff33f06
Binary files /dev/null and b/.claude/._settings.local.json differ
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..68d8ddb
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,8 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(python *)",
+ "Bash(python3 *)"
+ ]
+ }
+}
diff --git a/analyse_mailbox.py b/analyse_mailbox.py
new file mode 100644
index 0000000..31dab3e
--- /dev/null
+++ b/analyse_mailbox.py
@@ -0,0 +1,399 @@
+#!/usr/bin/env python3
+"""
+Fase 1: Analyseer lokale mailbox-kopie en genereer overzichten.
+Output: reports/mailinglijsten.md, facturen_mailbox.md, facturen_links.md, prullenbak.md
+"""
+
+import email
+import email.header
+import email.utils
+import re
+import sys
+from pathlib import Path
+from collections import defaultdict
+from datetime import datetime, timezone
+
+MAILBOX_DIR = Path(__file__).parent / "mailbox"
+REPORTS_DIR = Path(__file__).parent / "reports"
+
+INVOICE_SUBJECT_KW = [
+ "factuur", "invoice", "rekening", "nota", "kwitantie", "receipt",
+ "betaalverzoek", "betalingsverzoek", "betaling ontvangen",
+ "payment received", "order confirmation", "orderbevestiging",
+ "your order", "je bestelling", "proforma",
+]
+INVOICE_ATTACH_KW = [
+ "factuur", "invoice", "rekening", "nota", "receipt", "bill",
+]
+INVOICE_LINK_KW = [
+ "bekijk je factuur", "download je factuur", "download your invoice",
+ "view your invoice", "your invoice is ready", "je factuur staat klaar",
+ "factuur is beschikbaar", "invoice is available", "factuur online",
+ "invoice online", "mijn facturen", "my invoices", "portaal", "portal",
+ "mijn account", "my account", "log in to view", "inloggen om te bekijken",
+]
+
+TRASH_SENDER_RE = re.compile(
+ r"(noreply|no-reply|donotreply|do-not-reply|notifications?|newsletter|"
+ r"nieuwsbrief|alerts?|updates?|mailer-daemon|postmaster|bounce|"
+ r"auto-?reply|automatisch)@",
+ re.I,
+)
+TRASH_SUBJECT_RE = re.compile(
+ r"(out of office|automatisch antwoord|afwezig|buiten kantoor|"
+ r"delivery (status notification|failed|failure)|undeliverable|"
+ r"mail delivery|mailer-daemon|bounced|returned mail)",
+ re.I,
+)
+
+
+# ── helpers ─────────────────────────────────────────────────────────────────
+
+def decode_hdr(value: str | None) -> 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 parse_dt(date_str: str | None) -> datetime | None:
+ if not date_str:
+ return None
+ try:
+ return email.utils.parsedate_to_datetime(date_str)
+ except Exception:
+ return None
+
+
+def sender_domain(from_addr: str) -> str:
+ m = re.search(r"@([\w.\-]+)", from_addr)
+ return m.group(1).lower() if m else "onbekend"
+
+
+def get_body(msg) -> str:
+ parts = []
+ try:
+ for part in (msg.walk() if msg.is_multipart() else [msg]):
+ if part.get_content_type() in ("text/plain", "text/html"):
+ cs = part.get_content_charset() or "utf-8"
+ try:
+ parts.append(part.get_payload(decode=True).decode(cs, errors="replace"))
+ except Exception:
+ pass
+ except Exception:
+ pass
+ return "\n".join(parts)[:4000]
+
+
+def get_attachments(msg) -> list[dict]:
+ result = []
+ try:
+ for part in msg.walk():
+ disp = (part.get("Content-Disposition") or "").lower()
+ ct = part.get_content_type() or ""
+ fn = decode_hdr(part.get_filename() or "")
+ if "attachment" in disp or (fn and ct == "application/pdf"):
+ result.append({"filename": fn, "type": ct})
+ except Exception:
+ pass
+ return result
+
+
+def extract_urls(text: str) -> list[str]:
+ return list(dict.fromkeys(re.findall(r"https?://[^\s<>\"')]+", text)))
+
+
+def url_domain(url: str) -> str:
+ m = re.match(r"https?://([^/]+)", url)
+ return m.group(1).lower() if m else ""
+
+
+# ── main analysis ────────────────────────────────────────────────────────────
+
+def analyze():
+ REPORTS_DIR.mkdir(exist_ok=True)
+
+ eml_files = list(MAILBOX_DIR.rglob("*.eml"))
+ total = len(eml_files)
+ print(f"Analyseren van {total} e-mails...\n")
+
+ # mailinglijsten: key → {count, list_id, unsubscribe, senders, dates, folder}
+ ml: dict[str, dict] = defaultdict(lambda: {
+ "list_id": "", "unsubscribe": [], "senders": set(),
+ "count": 0, "dates": [], "folder": "",
+ })
+
+ invoices_in_mb: list[dict] = []
+ invoice_links: list[dict] = []
+ trash: list[dict] = []
+
+ now_utc = datetime.now(timezone.utc)
+
+ for i, path in enumerate(eml_files, 1):
+ if i % 1000 == 0:
+ print(f" {i}/{total}...", flush=True)
+
+ try:
+ with open(path, "rb") as f:
+ msg = email.message_from_bytes(f.read())
+ except Exception:
+ continue
+
+ folder = path.parent.name
+ from_raw = decode_hdr(msg.get("From", ""))
+ subject = decode_hdr(msg.get("Subject", ""))
+ date = parse_dt(msg.get("Date"))
+ list_id = (msg.get("List-ID") or "").strip()
+ list_us = (msg.get("List-Unsubscribe") or "").strip()
+ prec = (msg.get("Precedence") or "").lower()
+ from_lo = from_raw.lower()
+ subj_lo = subject.lower()
+ domain = sender_domain(from_raw)
+
+ attachments = get_attachments(msg)
+ body = get_body(msg)
+ body_lo = body.lower()
+
+ # ── mailinglijsten ────────────────────────────────────────────────
+ ml_key = None
+ if list_id:
+ ml_key = re.sub(r"[<>]", "", list_id).strip()
+ elif "mailinglists" in folder.lower() or "mailinglist" in folder.lower():
+ ml_key = folder # use the subfolder name as key
+
+ if ml_key:
+ entry = ml[ml_key]
+ entry["list_id"] = list_id or ml_key
+ entry["count"] += 1
+ entry["folder"] = folder
+ entry["senders"].add(from_raw)
+ if date:
+ entry["dates"].append(date)
+ if list_us and list_us not in entry["unsubscribe"]:
+ entry["unsubscribe"].append(list_us)
+
+ # ── facturen in mailbox ───────────────────────────────────────────
+ invoice_reasons: list[str] = []
+
+ for att in attachments:
+ fn_lo = att["filename"].lower()
+ if att["type"] == "application/pdf" and any(kw in fn_lo for kw in INVOICE_ATTACH_KW):
+ invoice_reasons.append(f"PDF: {att['filename']}")
+
+ if any(kw in subj_lo for kw in INVOICE_SUBJECT_KW):
+ invoice_reasons.append("factuurtermen in onderwerp")
+
+ # always include what's already in the Facturen folders
+ if "facturen" in folder.lower():
+ invoice_reasons.append(f"staat in map {folder}")
+
+ if invoice_reasons:
+ invoices_in_mb.append({
+ "from": from_raw,
+ "domain": domain,
+ "subject": subject,
+ "date": date,
+ "folder": folder,
+ "reasons": invoice_reasons,
+ "attachments": [a["filename"] for a in attachments if a["type"] == "application/pdf"],
+ })
+
+ # ── factuurlinks ──────────────────────────────────────────────────
+ has_link_kw = any(kw in body_lo for kw in INVOICE_LINK_KW)
+ has_no_pdf = not any(a["type"] == "application/pdf" for a in attachments)
+
+ if has_link_kw and has_no_pdf:
+ urls = extract_urls(body)
+ if urls:
+ invoice_links.append({
+ "from": from_raw,
+ "domain": domain,
+ "subject": subject,
+ "date": date,
+ "folder": folder,
+ "urls": urls[:5],
+ "url_domains": list(dict.fromkeys(url_domain(u) for u in urls[:5])),
+ })
+
+ # ── prullenbak ────────────────────────────────────────────────────
+ trash_reasons: list[str] = []
+
+ if TRASH_SENDER_RE.search(from_lo):
+ trash_reasons.append("automatisch afzenderadres")
+
+ if TRASH_SUBJECT_RE.search(subj_lo):
+ trash_reasons.append("automatisch/fout-onderwerp")
+
+ if prec in ("bulk", "junk"):
+ trash_reasons.append(f"Precedence: {prec}")
+
+ # Old bulk mail sitting in INBOX
+ if folder == "INBOX" and prec in ("bulk", "list", "junk") and date:
+ age = (now_utc - date.astimezone(timezone.utc)).days
+ if age > 180:
+ trash_reasons.append(f"bulk in INBOX, {age} dagen oud")
+
+ if trash_reasons:
+ trash.append({
+ "from": from_raw,
+ "domain": domain,
+ "subject": subject,
+ "date": date,
+ "folder": folder,
+ "reasons": trash_reasons,
+ "path": str(path),
+ })
+
+ print(f" {total}/{total} klaar.\n")
+
+ write_mailinglist_report(ml)
+ write_invoice_report(invoices_in_mb)
+ write_invoice_links_report(invoice_links)
+ write_trash_report(trash)
+
+ print(f"\nRapporten opgeslagen in: {REPORTS_DIR.resolve()}")
+
+
+# ── report writers ───────────────────────────────────────────────────────────
+
+def fmt_date(d: datetime | None) -> str:
+ return d.strftime("%Y-%m-%d") if d else "?"
+
+
+def write_mailinglist_report(ml: dict):
+ lines = [
+ "# Mailinglijsten\n",
+ f"Totaal gevonden: **{len(ml)}** lijsten.\n",
+ "_Advies: > 30 mails = hoog volume, overweeg afmelden._\n",
+ ]
+
+ for key, entry in sorted(ml.items(), key=lambda x: -x[1]["count"]):
+ dates = sorted(entry["dates"])
+ oldest = fmt_date(dates[0]) if dates else "?"
+ newest = fmt_date(dates[-1]) if dates else "?"
+ senders = sorted(entry["senders"])[:2]
+ unsub = entry["unsubscribe"]
+
+ if entry["count"] > 30:
+ advice = "⚠️ Hoog volume – overweeg afmelden"
+ elif entry["count"] > 10:
+ advice = "Gemiddeld volume – beoordeel zelf"
+ else:
+ advice = "Laag volume – waarschijnlijk houden"
+
+ can_unsub = "Ja" if unsub else "Nee (handmatig opzoeken)"
+
+ lines += [
+ f"## {key}",
+ f"- **Mails:** {entry['count']} | **Periode:** {oldest} – {newest}",
+ f"- **Map:** {entry['folder']}",
+ f"- **Afzender:** {', '.join(senders)}",
+ f"- **Afmelden mogelijk:** {can_unsub}",
+ ]
+ if unsub:
+ lines.append(f"- **Afmeldlink:** `{unsub[0][:120]}`")
+ lines += [f"- **Advies:** {advice}", ""]
+
+ path = REPORTS_DIR / "mailinglijsten.md"
+ path.write_text("\n".join(lines), encoding="utf-8")
+ print(f"✓ mailinglijsten.md ({len(ml)} lijsten)")
+
+
+def write_invoice_report(invoices: list[dict]):
+ lines = [
+ "# Facturen in mailbox\n",
+ f"Totaal: **{len(invoices)}** factuurmails gevonden.\n",
+ ]
+
+ by_domain: dict[str, list] = defaultdict(list)
+ for inv in invoices:
+ by_domain[inv["domain"]].append(inv)
+
+ for domain, items in sorted(by_domain.items(), key=lambda x: -len(x[1])):
+ items_sorted = sorted(items, key=lambda x: x["date"] or datetime.min.replace(tzinfo=timezone.utc))
+ lines.append(f"## {domain} ({len(items)} mails)")
+ for item in items_sorted[:25]:
+ atts = f" → {', '.join(item['attachments'])}" if item["attachments"] else ""
+ lines.append(f"- {fmt_date(item['date'])} | {item['subject'][:80]}{atts}")
+ if len(items) > 25:
+ lines.append(f" _...en {len(items) - 25} meer_")
+ lines.append("")
+
+ (REPORTS_DIR / "facturen_mailbox.md").write_text("\n".join(lines), encoding="utf-8")
+ print(f"✓ facturen_mailbox.md ({len(invoices)} facturen)")
+
+
+def write_invoice_links_report(invoice_links: list[dict]):
+ lines = [
+ "# Factuurlinks – verwijzingen naar portalen\n",
+ f"Totaal: **{len(invoice_links)}** mails met factuurverwijzingen (zonder PDF-bijlage).\n",
+ "_Dit zijn mails die zeggen 'bekijk je factuur online' – de factuur staat op een website._\n",
+ ]
+
+ by_url_domain: dict[str, list] = defaultdict(list)
+ for item in invoice_links:
+ for ud in (item["url_domains"] or ["onbekend"]):
+ by_url_domain[ud].append(item)
+
+ for ud, items in sorted(by_url_domain.items(), key=lambda x: -len(x[1])):
+ unique_senders = sorted({i["domain"] for i in items})
+ lines.append(f"## {ud} ({len(items)} mails)")
+ lines.append(f" _Afzenders: {', '.join(unique_senders[:5])}_")
+ for item in sorted(items, key=lambda x: x["date"] or datetime.min.replace(tzinfo=timezone.utc))[:10]:
+ lines.append(f"- {fmt_date(item['date'])} | {item['subject'][:80]}")
+ for url in item["urls"][:2]:
+ lines.append(f" `{url[:120]}`")
+ if len(items) > 10:
+ lines.append(f" _...en {len(items) - 10} meer_")
+ lines.append("")
+
+ (REPORTS_DIR / "facturen_links.md").write_text("\n".join(lines), encoding="utf-8")
+ print(f"✓ facturen_links.md ({len(invoice_links)} mails)")
+
+
+def write_trash_report(trash: list[dict]):
+ lines = [
+ "# Prullenbak-kandidaten\n",
+ f"Totaal: **{len(trash)}** kandidaten gevonden.\n",
+ ]
+
+ by_reason: dict[str, list] = defaultdict(list)
+ for item in trash:
+ by_reason[item["reasons"][0]].append(item)
+
+ for reason, items in sorted(by_reason.items(), key=lambda x: -len(x[1])):
+ lines.append(f"## {reason} ({len(items)} mails)")
+
+ by_domain: dict[str, list] = defaultdict(list)
+ for item in items:
+ by_domain[item["domain"]].append(item)
+
+ def _sort_key(x):
+ d = x["date"]
+ if d is None:
+ return datetime.min.replace(tzinfo=timezone.utc)
+ return d if d.tzinfo else d.replace(tzinfo=timezone.utc)
+
+ for domain, ditems in sorted(by_domain.items(), key=lambda x: -len(x[1])):
+ lines.append(f"### {domain} ({len(ditems)})")
+ for item in sorted(ditems, key=_sort_key)[:10]:
+ lines.append(f"- {fmt_date(item['date'])} | {item['folder']} | {item['subject'][:70]}")
+ if len(ditems) > 10:
+ lines.append(f" _...en {len(ditems) - 10} meer_")
+ lines.append("")
+
+ (REPORTS_DIR / "prullenbak.md").write_text("\n".join(lines), encoding="utf-8")
+ print(f"✓ prullenbak.md ({len(trash)} kandidaten)")
+
+
+if __name__ == "__main__":
+ analyze()
diff --git a/bin/run_daily_report.sh b/bin/run_daily_report.sh
new file mode 100755
index 0000000..61aecc9
--- /dev/null
+++ b/bin/run_daily_report.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+set -eu
+APP_DIR="/share/homes/mailcat/mailcat"
+ACCOUNT="backup"
+PYTHON="/opt/bin/python3"
+LOG_DIR="/share/homes/mailcat/mailcat/log"
+MAIL_TO="hans@australius.nl"
+MKDIR="/bin/mkdir"
+
+"$MKDIR" -p "$LOG_DIR"
+cd "$APP_DIR"
+"$PYTHON" "$APP_DIR/dagelijks_overzicht.py" --account "$ACCOUNT" --mail-to "$MAIL_TO" >> "$LOG_DIR/daily_report.out" 2>&1
diff --git a/bin/start_mailcat.sh b/bin/start_mailcat.sh
new file mode 100755
index 0000000..c2249db
--- /dev/null
+++ b/bin/start_mailcat.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+set -eu
+APP_DIR="/share/homes/mailcat/mailcat"
+ACCOUNT="backup"
+PYTHON="/opt/bin/python3"
+STATE_DIR="/share/homes/mailcat/mailcat/state"
+LOG_DIR="/share/homes/mailcat/mailcat/log"
+RUN_DIR="/share/homes/mailcat/mailcat/run"
+PID_FILE="/share/homes/mailcat/mailcat/run/mailcat.pid"
+MKDIR="/bin/mkdir"
+
+"$MKDIR" -p "$STATE_DIR" "$LOG_DIR" "$RUN_DIR"
+if [ -f "$PID_FILE" ]; then
+ old_pid=""
+ IFS= read -r old_pid < "$PID_FILE" || true
+ if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
+ echo "mailcat already running: $old_pid"
+ exit 0
+ fi
+fi
+
+cd "$APP_DIR"
+"$PYTHON" "$APP_DIR/sort_mail_daemon.py" --account "$ACCOUNT" --folder INBOX --state-file "$STATE_DIR/sort_mail_daemon_state.json" --log-file "$LOG_DIR/sort_mail_daemon.log" >> "$LOG_DIR/daemon.out" 2>&1 &
+pid="$!"
+echo "$pid" > "$PID_FILE"
+echo "mailcat started: $pid"
diff --git a/bin/start_web.sh b/bin/start_web.sh
new file mode 100755
index 0000000..2d8ae13
--- /dev/null
+++ b/bin/start_web.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+set -eu
+APP_DIR="/share/homes/mailcat/mailcat"
+PYTHON="/opt/bin/python3"
+LOG_DIR="/share/homes/mailcat/mailcat/log"
+RUN_DIR="/share/homes/mailcat/mailcat/run"
+WEB_PID_FILE="/share/homes/mailcat/mailcat/run/mailcat_web.pid"
+WEB_HOST="0.0.0.0"
+WEB_PORT="4321"
+WEB_CONFIG="/share/homes/mailcat/mailcat/web_config.json"
+MKDIR="/bin/mkdir"
+
+if [ ! -f "$WEB_CONFIG" ]; then
+ echo "missing web config: $WEB_CONFIG"
+ exit 1
+fi
+
+"$MKDIR" -p "$LOG_DIR" "$RUN_DIR"
+if [ -f "$WEB_PID_FILE" ]; then
+ old_pid=""
+ IFS= read -r old_pid < "$WEB_PID_FILE" || true
+ if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
+ echo "mailcat web already running: $old_pid"
+ exit 0
+ fi
+fi
+
+cd "$APP_DIR"
+"$PYTHON" "$APP_DIR/manage_routes_web.py" --host "$WEB_HOST" --port "$WEB_PORT" --config "$WEB_CONFIG" >> "$LOG_DIR/web.out" 2>&1 &
+pid="$!"
+echo "$pid" > "$WEB_PID_FILE"
+echo "mailcat web started: $pid"
diff --git a/bin/status_mailcat.sh b/bin/status_mailcat.sh
new file mode 100755
index 0000000..429b959
--- /dev/null
+++ b/bin/status_mailcat.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+set -eu
+PID_FILE="/share/homes/mailcat/mailcat/run/mailcat.pid"
+LOG_DIR="/share/homes/mailcat/mailcat/log"
+
+if [ -f "$PID_FILE" ]; then
+ pid=""
+ IFS= read -r pid < "$PID_FILE" || true
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
+ echo "mailcat running: $pid"
+ exit 0
+ fi
+ echo "mailcat not running: stale pid file ($pid)"
+ exit 1
+fi
+echo "mailcat not running"
+echo "logs: $LOG_DIR"
+exit 1
diff --git a/bin/status_web.sh b/bin/status_web.sh
new file mode 100755
index 0000000..9713484
--- /dev/null
+++ b/bin/status_web.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+set -eu
+WEB_PID_FILE="/share/homes/mailcat/mailcat/run/mailcat_web.pid"
+WEB_HOST="0.0.0.0"
+WEB_PORT="4321"
+LOG_DIR="/share/homes/mailcat/mailcat/log"
+
+if [ -f "$WEB_PID_FILE" ]; then
+ pid=""
+ IFS= read -r pid < "$WEB_PID_FILE" || true
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
+ echo "mailcat web running: $pid"
+ echo "url: http://$WEB_HOST:$WEB_PORT/"
+ exit 0
+ fi
+ echo "mailcat web not running: stale pid file ($pid)"
+ exit 1
+fi
+echo "mailcat web not running"
+echo "logs: $LOG_DIR/web.out"
+exit 1
diff --git a/bin/stop_mailcat.sh b/bin/stop_mailcat.sh
new file mode 100755
index 0000000..0dfde8d
--- /dev/null
+++ b/bin/stop_mailcat.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+set -eu
+PID_FILE="/share/homes/mailcat/mailcat/run/mailcat.pid"
+RM="/bin/rm"
+
+if [ ! -f "$PID_FILE" ]; then
+ echo "mailcat not running: no pid file"
+ exit 0
+fi
+
+pid=""
+IFS= read -r pid < "$PID_FILE" || true
+if [ -z "$pid" ]; then
+ "$RM" -f "$PID_FILE"
+ echo "mailcat not running: empty pid file removed"
+ exit 0
+fi
+
+if kill -0 "$pid" 2>/dev/null; then
+ kill "$pid"
+ timeout=20
+ while [ "$timeout" -gt 0 ] && kill -0 "$pid" 2>/dev/null; do
+ sleep 1
+ timeout=$((timeout - 1))
+ done
+ if kill -0 "$pid" 2>/dev/null; then
+ echo "mailcat still running after SIGTERM, sending SIGKILL: $pid"
+ kill -KILL "$pid" 2>/dev/null || true
+ timeout=5
+ while [ "$timeout" -gt 0 ] && kill -0 "$pid" 2>/dev/null; do
+ sleep 1
+ timeout=$((timeout - 1))
+ done
+ if kill -0 "$pid" 2>/dev/null; then
+ echo "mailcat still running after SIGKILL: $pid"
+ exit 1
+ fi
+ fi
+fi
+"$RM" -f "$PID_FILE"
+echo "mailcat stopped"
diff --git a/bin/stop_web.sh b/bin/stop_web.sh
new file mode 100755
index 0000000..9b11652
--- /dev/null
+++ b/bin/stop_web.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+set -eu
+WEB_PID_FILE="/share/homes/mailcat/mailcat/run/mailcat_web.pid"
+RM="/bin/rm"
+
+if [ ! -f "$WEB_PID_FILE" ]; then
+ echo "mailcat web not running: no pid file"
+ exit 0
+fi
+
+pid=""
+IFS= read -r pid < "$WEB_PID_FILE" || true
+if [ -z "$pid" ]; then
+ "$RM" -f "$WEB_PID_FILE"
+ echo "mailcat web not running: empty pid file removed"
+ exit 0
+fi
+
+if kill -0 "$pid" 2>/dev/null; then
+ kill "$pid"
+ timeout=20
+ while [ "$timeout" -gt 0 ] && kill -0 "$pid" 2>/dev/null; do
+ sleep 1
+ timeout=$((timeout - 1))
+ done
+ if kill -0 "$pid" 2>/dev/null; then
+ echo "mailcat web still running after SIGTERM, sending SIGKILL: $pid"
+ kill -KILL "$pid" 2>/dev/null || true
+ timeout=5
+ while [ "$timeout" -gt 0 ] && kill -0 "$pid" 2>/dev/null; do
+ sleep 1
+ timeout=$((timeout - 1))
+ done
+ if kill -0 "$pid" 2>/dev/null; then
+ echo "mailcat web still running after SIGKILL: $pid"
+ exit 1
+ fi
+ fi
+fi
+"$RM" -f "$WEB_PID_FILE"
+echo "mailcat web stopped"
diff --git a/dagelijks_overzicht.plist b/dagelijks_overzicht.plist
new file mode 100644
index 0000000..8908ec6
--- /dev/null
+++ b/dagelijks_overzicht.plist
@@ -0,0 +1,34 @@
+
+
+
+
+ Label
+ nl.australius.dagelijks-overzicht
+
+ ProgramArguments
+
+ /usr/bin/python3
+ /Users/hanswienen/Documents/Development/Vibes/mailcat/dagelijks_overzicht.py
+
+
+
+ StartCalendarInterval
+
+ Hour
+ 7
+ Minute
+ 0
+
+
+
+ StandardOutPath
+ /Users/hanswienen/Library/Logs/dagelijks_overzicht.log
+ StandardErrorPath
+ /Users/hanswienen/Library/Logs/dagelijks_overzicht.log
+
+
+ RunAtLoad
+
+
+
diff --git a/daily_report.out b/daily_report.out
new file mode 100644
index 0000000..e3cc0a7
--- /dev/null
+++ b/daily_report.out
@@ -0,0 +1,117 @@
+sh: line 1: hans: command not found
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-04 ...
+2 berichten gevonden in 2 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-04 ...
+2 berichten gevonden in 2 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-05 ...
+4 berichten gevonden in 3 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-05 ...
+4 berichten gevonden in 3 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-06 ...
+6 berichten gevonden in 5 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-06 ...
+6 berichten gevonden in 5 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account 'hand' niet gevonden. Beschikbaar: backup, hans
+Account: hans@australius.nl
+
+2026-07-08 10:49:01.846690: Ophalen berichten van 2026-07-07 ...
+12 berichten gevonden in 7 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+2026-07-08 13:35:02.222744: Ophalen berichten van 2026-07-07 ...
+12 berichten gevonden in 7 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+2026-07-08 13:36:01.725869: Ophalen berichten van 2026-07-07 ...
+12 berichten gevonden in 7 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+2026-07-08 13:38:01.565000: Ophalen berichten van 2026-07-07 ...
+12 berichten gevonden in 7 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+2026-07-08 13:39:01.933965: Ophalen berichten van 2026-07-07 ...
+12 berichten gevonden in 7 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+2026-07-09 06:00:02.179821: Ophalen berichten van 2026-07-08 ...
+10 berichten gevonden in 9 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+2026-07-10 06:00:02.302178: Ophalen berichten van 2026-07-09 ...
+10 berichten gevonden in 8 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+2026-07-11 06:00:01.530567: Ophalen berichten van 2026-07-10 ...
+11 berichten gevonden in 8 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-11 ...
+4 berichten gevonden in 4 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-12 ...
+3 berichten gevonden in 2 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-13 ...
+12 berichten gevonden in 10 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-14 ...
+12 berichten gevonden in 5 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-15 ...
+10 berichten gevonden in 8 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-16 ...
+4 berichten gevonden in 4 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-17 ...
+4 berichten gevonden in 3 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-18 ...
+1 berichten gevonden in 1 mappen.
+Overzicht verstuurd naar hans@australius.nl
+Account: hans@australius.nl
+
+Ophalen berichten van 2026-07-19 ...
+2 berichten gevonden in 2 mappen.
+Overzicht verstuurd naar hans@australius.nl
diff --git a/decisions.json.bak b/decisions.json.bak
new file mode 100644
index 0000000..2cb487c
--- /dev/null
+++ b/decisions.json.bak
@@ -0,0 +1,6240 @@
+{
+ "researchgatemail.net": {
+ "keuze": "a",
+ "naam": "ResearchGate",
+ "count": 674,
+ "domains": [
+ "researchgatemail.net"
+ ],
+ "unsubscribe": [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "