Compare commits
13 Commits
ae0813d519
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d70112d766 | |||
| 0b4e702bac | |||
| 337378a362 | |||
| 0af27236e5 | |||
| da481678b6 | |||
| 7b345b8795 | |||
| 12a82f570a | |||
| db209ac246 | |||
| 35d78a27aa | |||
| 0811a6ca5a | |||
| edcad4560c | |||
| d0e78e728a | |||
| 99cd891b1e |
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(python *)",
|
||||
"Bash(python3 *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -7,3 +7,5 @@ web_config.json
|
||||
**/*.eml
|
||||
sort_mail_daemon_state.json
|
||||
verplaats_log_*.json
|
||||
unsubscribe_log.json
|
||||
codex.session
|
||||
|
||||
Executable
+12
@@ -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
|
||||
Executable
+26
@@ -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"
|
||||
Executable
+32
@@ -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"
|
||||
Executable
+18
@@ -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
|
||||
Executable
+21
@@ -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
|
||||
Executable
+41
@@ -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"
|
||||
Executable
+41
@@ -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"
|
||||
+68
-10
@@ -15,19 +15,21 @@ import imaplib
|
||||
import email
|
||||
import email.header
|
||||
import email.utils
|
||||
import json
|
||||
import sys
|
||||
import ssl
|
||||
import smtplib
|
||||
import re
|
||||
import html as html_lib
|
||||
import urllib.parse
|
||||
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
|
||||
from imap_utils import list_folders, quote_mailbox
|
||||
from mail_imap_ops import fetch_body
|
||||
from babel.dates import format_date
|
||||
import babel
|
||||
import random
|
||||
|
||||
# Mappen die NIET in het overzicht komen (systeem + inbox zelf)
|
||||
UITSLUITINGEN = {
|
||||
@@ -99,6 +101,49 @@ def leesbare_map(imap_naam: str) -> str:
|
||||
return imap_naam.removeprefix("INBOX.").replace(".", " › ")
|
||||
|
||||
|
||||
def apple_mail_url(message_id: str) -> str:
|
||||
"""Return an Apple Mail message:// URL for a Message-ID header."""
|
||||
clean = (message_id or "").strip()
|
||||
if not clean:
|
||||
return ""
|
||||
if not (clean.startswith("<") and clean.endswith(">")):
|
||||
clean = f"<{clean.strip('<>')}>"
|
||||
return "message://" + urllib.parse.quote(clean, safe="")
|
||||
|
||||
|
||||
def fetch_body(data) -> bytes | None:
|
||||
"""Return the first bytes payload from an IMAP FETCH response."""
|
||||
if not data:
|
||||
return None
|
||||
for item in data:
|
||||
if isinstance(item, tuple) and len(item) >= 2 and isinstance(item[1], bytes):
|
||||
return item[1]
|
||||
return None
|
||||
|
||||
def invalid_locale(code):
|
||||
return babel.Locale(code).language_name is None
|
||||
|
||||
def get_random_locale() -> (str, str, str):
|
||||
"""Return the locale code, the local name and the english name of a language."""
|
||||
all_ids = babel.localedata.locale_identifiers()
|
||||
code = all_ids[random.randrange(0, len(all_ids))]
|
||||
while (invalid_locale(code)):
|
||||
code = all_ids[random.randrange(0, len(all_ids))]
|
||||
return (code, babel.Locale(code).language_name, babel.Locale(code).english_name)
|
||||
|
||||
def create_mail_subject(zoekdatum, random = False) -> str:
|
||||
|
||||
# Als random = true, toon dan de datum ook in een willekeurige andere taal
|
||||
# Anders alleen in het Nederlands
|
||||
|
||||
datum_nl = format_date(zoekdatum, "EEEE d MMMM yyyy", locale="nl")
|
||||
if random:
|
||||
random_locale, random_lang, random_elang = get_random_locale()
|
||||
datum_bl = format_date(zoekdatum, locale=random_locale)
|
||||
return f"Mailoverzicht van {datum_bl} ({random_lang} - {random_elang}) of {datum_nl}"
|
||||
else:
|
||||
return f"Mailoverzicht van {datum_nl}"
|
||||
|
||||
# ── IMAP ophalen ──────────────────────────────────────────────────────────────
|
||||
|
||||
def haal_berichten_op(mail: imaplib.IMAP4_SSL, zoekdatum: date) -> dict[str, list[dict]]:
|
||||
@@ -138,7 +183,7 @@ def haal_berichten_op(mail: imaplib.IMAP4_SSL, zoekdatum: date) -> dict[str, lis
|
||||
|
||||
for uid in ids:
|
||||
status, msg_data = mail.fetch(
|
||||
uid, "(BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)])"
|
||||
uid, "(BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE MESSAGE-ID)])"
|
||||
)
|
||||
raw_header = fetch_body(msg_data)
|
||||
if status != "OK" or raw_header is None:
|
||||
@@ -148,6 +193,7 @@ def haal_berichten_op(mail: imaplib.IMAP4_SSL, zoekdatum: date) -> dict[str, lis
|
||||
from_raw = decode_hdr(msg.get("From", ""))
|
||||
subject = decode_hdr(msg.get("Subject", "(geen onderwerp)"))
|
||||
date_str = msg.get("Date", "")
|
||||
message_id = (msg.get("Message-ID") or "").strip()
|
||||
|
||||
# Tijdstip opmaken
|
||||
try:
|
||||
@@ -164,6 +210,7 @@ def haal_berichten_op(mail: imaplib.IMAP4_SSL, zoekdatum: date) -> dict[str, lis
|
||||
"from": afzender[:40],
|
||||
"subject": subject[:80],
|
||||
"time": tijdstip,
|
||||
"message_id": message_id,
|
||||
})
|
||||
|
||||
return resultaat
|
||||
@@ -203,15 +250,23 @@ def bouw_html(berichten: dict[str, list[dict]], zoekdatum: date) -> str:
|
||||
|
||||
for folder in sorted(berichten.keys()):
|
||||
items = berichten[folder]
|
||||
label = leesbare_map(folder)
|
||||
label = html_lib.escape(leesbare_map(folder))
|
||||
html += f'<h2>{label} <span style="font-weight:normal;color:#888">({len(items)})</span></h2>\n'
|
||||
html += '<table>\n'
|
||||
for item in items:
|
||||
time = html_lib.escape(item["time"])
|
||||
sender = html_lib.escape(item["from"])
|
||||
subject = html_lib.escape(item["subject"])
|
||||
url = apple_mail_url(item.get("message_id", ""))
|
||||
if url:
|
||||
subject_html = f'<a href="{html_lib.escape(url, quote=True)}">{subject}</a>'
|
||||
else:
|
||||
subject_html = subject
|
||||
html += (
|
||||
f'<tr>'
|
||||
f'<td class="time">{item["time"]}</td>'
|
||||
f'<td class="from">{item["from"]}</td>'
|
||||
f'<td class="subject">{item["subject"]}</td>'
|
||||
f'<td class="time">{time}</td>'
|
||||
f'<td class="from">{sender}</td>'
|
||||
f'<td class="subject">{subject_html}</td>'
|
||||
f'</tr>\n'
|
||||
)
|
||||
html += '</table>\n'
|
||||
@@ -224,9 +279,12 @@ def bouw_html(berichten: dict[str, list[dict]], zoekdatum: date) -> str:
|
||||
# ── e-mail versturen ──────────────────────────────────────────────────────────
|
||||
|
||||
def stuur_overzicht(html: str, zoekdatum: date, config: dict):
|
||||
datum_nl = zoekdatum.strftime("%-d %B %Y")
|
||||
# datum_nl = format_date(zoekdatum, "EEEE d MMMM yyyy", locale="nl") # zoekdatum.strftime("%-d %B %Y")
|
||||
# random_locale, random_lang, random_elang = get_random_locale()
|
||||
# datum_bl = format_date(zoekdatum, locale=random_locale)
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["Subject"] = f"Mailbak {datum_nl}"
|
||||
# msg["Subject"] = f"Mailoverzicht van {datum_bl} ({random_lang} - {random_elang}) of {datum_nl}"
|
||||
msg["Subject"] = create_mail_subject(zoekdatum, False)
|
||||
msg["From"] = config["from"]
|
||||
msg["To"] = config["to"]
|
||||
msg.attach(MIMEText(html, "html", "utf-8"))
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
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
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-20 ...
|
||||
7 berichten gevonden in 5 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-21 ...
|
||||
6 berichten gevonden in 6 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-22 ...
|
||||
7 berichten gevonden in 5 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-23 ...
|
||||
5 berichten gevonden in 5 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-24 ...
|
||||
8 berichten gevonden in 6 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-25 ...
|
||||
5 berichten gevonden in 4 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-26 ...
|
||||
1 berichten gevonden in 1 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-27 ...
|
||||
7 berichten gevonden in 5 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-28 ...
|
||||
6 berichten gevonden in 4 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-29 ...
|
||||
3 berichten gevonden in 2 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-30 ...
|
||||
9 berichten gevonden in 8 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-31 ...
|
||||
4 berichten gevonden in 3 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-01 ...
|
||||
2 berichten gevonden in 2 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-02 ...
|
||||
4 berichten gevonden in 2 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-03 ...
|
||||
5 berichten gevonden in 4 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-04 ...
|
||||
4 berichten gevonden in 4 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-05 ...
|
||||
4 berichten gevonden in 3 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-06 ...
|
||||
7 berichten gevonden in 6 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-07 ...
|
||||
5 berichten gevonden in 4 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-08 ...
|
||||
6 berichten gevonden in 5 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-09 ...
|
||||
3 berichten gevonden in 3 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-10 ...
|
||||
4 berichten gevonden in 3 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-11 ...
|
||||
5 berichten gevonden in 4 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-12 ...
|
||||
7 berichten gevonden in 6 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-13 ...
|
||||
7 berichten gevonden in 5 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-14 ...
|
||||
6 berichten gevonden in 3 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-15 ...
|
||||
1 berichten gevonden in 1 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: hans@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-08-16 ...
|
||||
3 berichten gevonden in 2 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
+30
-4
@@ -184,12 +184,25 @@ fi
|
||||
|
||||
if kill -0 "\$pid" 2>/dev/null; then
|
||||
kill "\$pid"
|
||||
sleep 2
|
||||
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: \$pid"
|
||||
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"
|
||||
STOP_SCRIPT
|
||||
@@ -286,12 +299,25 @@ fi
|
||||
|
||||
if kill -0 "\$pid" 2>/dev/null; then
|
||||
kill "\$pid"
|
||||
sleep 2
|
||||
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: \$pid"
|
||||
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"
|
||||
WEB_STOP_SCRIPT
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
Account: hans@australius.nl
|
||||
|
||||
2026-07-08 10:50:02.099347: 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:09.360753: Ophalen berichten van 2026-07-07 ...
|
||||
12 berichten gevonden in 7 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
@@ -0,0 +1,26 @@
|
||||
# mailcat_roundcube.sieve
|
||||
# Generated from domain_routes.json and mailinglist_routes.json.
|
||||
# Intended for Roundcube/Managesieve import for hans@australius.nl.
|
||||
# Do not use :copy: these rules sort incoming mail into destination folders.
|
||||
# Historical mailbox reordering still requires verplaats_bestaand.py.
|
||||
|
||||
require ["fileinto", "date", "variables"];
|
||||
|
||||
# Financial rules are first. The invoice rule stays dynamic, so individual quarters are not separately ordered.
|
||||
# rule:[Financieel / Facturen / Dynamisch kwartaal]
|
||||
if header :contains "Subject" ["factuur", "invoice", "rekening", "nota", "betaalverzoek", "receipt", "orderbevestiging", "order confirmation", "betaling ontvangen", "payment received", "uw factuur", "your invoice", "je factuur"] {
|
||||
if currentdate :matches "year" "*" {
|
||||
set "jaar" "${1}";
|
||||
}
|
||||
if anyof (currentdate :is "month" "1", currentdate :is "month" "2", currentdate :is "month" "3") {
|
||||
fileinto "INBOX.Financieel.Facturen.${jaar}.Q1";
|
||||
} elsif anyof (currentdate :is "month" "4", currentdate :is "month" "5", currentdate :is "month" "6") {
|
||||
fileinto "INBOX.Financieel.Facturen.${jaar}.Q2";
|
||||
} elsif anyof (currentdate :is "month" "7", currentdate :is "month" "8", currentdate :is "month" "9") {
|
||||
fileinto "INBOX.Financieel.Facturen.${jaar}.Q3";
|
||||
} else {
|
||||
fileinto "INBOX.Financieel.Facturen.${jaar}.Q4";
|
||||
}
|
||||
stop;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# invoice_dynamic_quarter.sieve
|
||||
# Single Roundcube/Managesieve rule for dynamic invoice quarter routing.
|
||||
# Import this as a standalone filter set or copy the rule into an existing set.
|
||||
|
||||
require ["fileinto", "date", "variables", "regex"];
|
||||
|
||||
# rule:[Financieel / Facturen / Dynamisch kwartaal]
|
||||
if allof (
|
||||
header :contains "Subject" [
|
||||
"factuur",
|
||||
"invoice",
|
||||
"rekening",
|
||||
"nota",
|
||||
"betaalverzoek",
|
||||
"receipt",
|
||||
"orderbevestiging",
|
||||
"order confirmation",
|
||||
"betaling ontvangen",
|
||||
"payment received",
|
||||
"uw factuur",
|
||||
"your invoice",
|
||||
"je factuur"
|
||||
],
|
||||
date :matches "Date" "year" "*"
|
||||
) {
|
||||
set "jaar" "${1}";
|
||||
|
||||
if date :regex "Date" "month" "^(0?[123])$" {
|
||||
set "kwartaal" "Q1";
|
||||
} elsif date :regex "Date" "month" "^(0?[456])$" {
|
||||
set "kwartaal" "Q2";
|
||||
} elsif date :regex "Date" "month" "^(0?[789])$" {
|
||||
set "kwartaal" "Q3";
|
||||
} else {
|
||||
set "kwartaal" "Q4";
|
||||
}
|
||||
|
||||
fileinto "INBOX.Financieel.Facturen.${jaar}.${kwartaal}";
|
||||
stop;
|
||||
}
|
||||
+1593
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
Account: backup@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-03 ...
|
||||
10 berichten gevonden in 7 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
Account: backup@australius.nl
|
||||
|
||||
Ophalen berichten van 2026-07-04 ...
|
||||
3 berichten gevonden in 3 mappen.
|
||||
Overzicht verstuurd naar hans@australius.nl
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
---
|
||||
Plan
|
||||
|
||||
Fase 0 — Download (jij voert uit)
|
||||
|
||||
Draai python download_mailbox.py in de projectmap. Het script:
|
||||
- vraagt om je e-mailadres + wachtwoord (lokaal, gaat niet via mij)
|
||||
- downloadt alle mappen en mails naar mailbox/<mapnaam>/<id>.eml
|
||||
- is herstart-safe: al gedownloade mails worden overgeslagen
|
||||
:q
|
||||
Fase 1 — Overzichten (ik maak na download)
|
||||
|
||||
1. Mailinglijsten — via List-ID / List-Unsubscribe headers; per lijst: volume, laatste mail, advies (afmelden / houden)
|
||||
2. Facturen in mailbox — PDF-bijlagen + mails met factuurtermen; per afzender gesorteerd
|
||||
3. Factuurlinks — mails die verwijzen naar een portaal ("bekijk je factuur op..."); per website
|
||||
4. Prullenbak-kandidaten — auto-notificaties, lege threads, doublures, oude nieuwsbrieven
|
||||
|
||||
Fase 2 — Mappenindeling + mailrules (ik stel voor na analyse)
|
||||
|
||||
Op basis van wat ik in de mailbox aantref, maak ik een voorstel voor:
|
||||
- een logische, intuïtieve mappenstructuur (bijv. Financieel / Projecten / Abonnementen / Archief)
|
||||
- server-side mailrules (Sieve-formaat, het meest gangbare bij IMAP-providers) om inkomende mail automatisch te sorteren
|
||||
|
||||
Fase 3 — Scripts (ik schrijf, jij voert uit op echte mailbox)
|
||||
|
||||
- Unsubscribe-script — per mailinglijst: mailto of HTTP List-Unsubscribe aanroepen
|
||||
- Opruim-script — geselecteerde mails via IMAP naar Trash verplaatsen
|
||||
- Factuur-plan — per website een aanpak voor geautomatiseerd ophalen (login-flow, download-URL)
|
||||
- Mailrules-implementatie — instructies of script om de Sieve-rules op de server te zetten
|
||||
|
||||
---
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+133
-7
@@ -2,6 +2,17 @@
|
||||
|
||||
You are continuing the `mailcat` project in `/Users/hanswienen/Documents/Development/Vibes/mailcat`.
|
||||
|
||||
## Current Goal
|
||||
|
||||
The real `hans` mailbox has been completely reordered, and Roundcube/Managesieve filters have been uploaded for ongoing incoming-mail sorting. The Python live sorter and web route-management interface are no longer needed.
|
||||
|
||||
The repository is intentionally reduced to the minimum tooling needed to:
|
||||
|
||||
1. Repeat folder creation and historical reordering if needed.
|
||||
2. Keep/generate the Roundcube Sieve policy.
|
||||
3. Continue unsubscribe work.
|
||||
4. Run the daily report.
|
||||
|
||||
## Project Goal
|
||||
|
||||
Build a reliable mailbox cleanup and automation toolkit for `australius.nl`.
|
||||
@@ -13,10 +24,69 @@ Do not run mailbox-affecting scripts yourself unless the user explicitly authori
|
||||
## Collaboration Rules
|
||||
|
||||
- Follow the git workflow from `AGENTS.md`.
|
||||
- Before making file, code, or configuration changes, present a plan and wait for explicit approval.
|
||||
- Work on branch `codex` unless the user says otherwise.
|
||||
- Before every commit, rewrite this file from scratch as the current reconstruction prompt.
|
||||
- Commit after each user-prompted code change using the required three-section commit message format.
|
||||
- The user asked: first provide a plan and wait for approval before doing future work.
|
||||
- Keep runtime logs/state files unless the user explicitly says to delete them.
|
||||
- Do not commit credentials.
|
||||
|
||||
## Kept Files
|
||||
|
||||
Core configuration and IMAP helpers:
|
||||
|
||||
- `config.py`
|
||||
- `imap_utils.py`
|
||||
- `config.json` is local-only and ignored; it contains account credentials.
|
||||
|
||||
Routing/reorder policy:
|
||||
|
||||
- `mail_routes.py`
|
||||
- `domain_routes.json`
|
||||
- `mailinglist_routes.json`
|
||||
- `maak_mappen.py`
|
||||
- `verplaats_bestaand.py`
|
||||
|
||||
Roundcube/Sieve:
|
||||
|
||||
- `mailcat_roundcube.sieve`
|
||||
- `roundcube.txt` is local-only user export/test evidence and should not be treated as production policy.
|
||||
|
||||
Unsubscribe:
|
||||
|
||||
- `unsubscribe.py`
|
||||
- `decisions.json`
|
||||
- `unsubscribe_log.json` is runtime state and should not be committed unless the user explicitly asks.
|
||||
|
||||
Daily report:
|
||||
|
||||
- `dagelijks_overzicht.py`
|
||||
- `tests/test_dagelijks_overzicht.py`
|
||||
|
||||
Logs/state kept:
|
||||
|
||||
- `backup_log.json`
|
||||
- `verplaats_log.json`
|
||||
- `verplaats_log_voor_aiwijzigingen.json`
|
||||
- `verplaats_bestaand.log`
|
||||
- `unsubscribe_log.json`
|
||||
|
||||
## Current Behavior
|
||||
|
||||
Roundcube/Sieve:
|
||||
|
||||
- `mailcat_roundcube.sieve` is the generated Roundcube/Managesieve Sieve file for the current routing setup.
|
||||
- `invoice_dynamic_quarter.sieve` is a standalone Roundcube/Managesieve Sieve file containing only the dynamic invoice-quarter rule. It uses the message `Date` header to set `jaar`, regex month tests to set `kwartaal`, then files into `INBOX.Financieel.Facturen.${jaar}.${kwartaal}`.
|
||||
- It uses `INBOX.`-prefixed mailbox names because the user's Roundcube export used `fileinto :copy "INBOX.Notes";`.
|
||||
- It includes `Afmelden.*` routes and uses `fileinto` without `:copy`, so matching incoming messages are sorted instead of duplicated.
|
||||
- Rules have Roundcube-compatible `# rule:[...]` names.
|
||||
- Financial rules are first; the invoice rule remains one dynamic quarter rule, then remaining financial rules and all other rules are alphabetically ordered by rule name.
|
||||
- Duplicate destination names are disambiguated with `/ Mailing-list` or `/ Domain` suffixes.
|
||||
=======
|
||||
- Work on branch `codex` unless the user says otherwise.
|
||||
- Before every commit, rewrite this file from scratch as the current reconstruction prompt.
|
||||
- Commit after each user-prompted code change using the required three-section commit message format.
|
||||
- The user asked: first provide a plan and wait for approval before future broad work. For narrow requested edits, implement directly when the request is explicit.
|
||||
- Do not stage local runtime output such as `verplaats_log.json` unless the user explicitly asks.
|
||||
|
||||
## Local Data And Secrets
|
||||
@@ -108,8 +178,58 @@ Daily report:
|
||||
|
||||
- `dagelijks_overzicht.py` builds an HTML digest for a selected date, defaulting to yesterday.
|
||||
- It supports `--account` and `--mail-to`.
|
||||
- It searches sorted folders, excluding inbox/system/archive/technical folders.
|
||||
- It sends an HTML email via SMTP.
|
||||
- Report rows fetch `Message-ID` and render the subject as an Apple Mail `message://` link when a `Message-ID` is present.
|
||||
- Apple Mail links are macOS-oriented and depend on Apple Mail having the message synced/indexed locally.
|
||||
- Report HTML escapes folder labels, senders, subjects, and link attributes.
|
||||
|
||||
Historical reorder:
|
||||
|
||||
- `verplaats_bestaand.py` defaults to dry-run. Real moves require `--uitvoeren` and interactive `JA`.
|
||||
- Account selection is explicit with `--account <naam>`, for example `--account hans`.
|
||||
- `--audit` is available in dry-run mode.
|
||||
- It logs to `verplaats_log.json`.
|
||||
|
||||
Unsubscribe:
|
||||
|
||||
- `unsubscribe.py` reads `decisions.json`.
|
||||
- It processes entries with `keuze == "a"` and a `List-Unsubscribe` value.
|
||||
- It logs progress to `unsubscribe_log.json`.
|
||||
- It may perform HTTP unsubscribe requests or send mailto unsubscribe messages via SMTP, so the user should run it personally.
|
||||
|
||||
## NAS Daily Report
|
||||
|
||||
For the current NAS setup, only the daily report needs scheduling. A simple cron entry is enough:
|
||||
|
||||
```cron
|
||||
0 6 * * * cd /share/homes/mailcat/mailcat && /opt/bin/python3 dagelijks_overzicht.py --account hans --mail-to hans@australius.nl >> /share/homes/mailcat/mailcat/daily_report.out 2>&1
|
||||
```
|
||||
|
||||
Use the NAS-specific crontab mechanism if QNAP overwrites normal crontabs after reboot.
|
||||
|
||||
## Verification Commands
|
||||
|
||||
Latest local verification should include:
|
||||
|
||||
```sh
|
||||
python3 -m py_compile config.py imap_utils.py mail_routes.py maak_mappen.py verplaats_bestaand.py dagelijks_overzicht.py unsubscribe.py
|
||||
python3 -m unittest discover -s tests
|
||||
sievec -c /tmp/mailcat-sieve-test/dovecot.conf mailcat_roundcube.sieve /tmp/mailcat_roundcube.svbin
|
||||
git diff --check
|
||||
```
|
||||
|
||||
If `/tmp/mailcat-sieve-test/dovecot.conf` does not exist, recreate it for Dovecot 2.4.0:
|
||||
|
||||
```sh
|
||||
mkdir -p /tmp/mailcat-sieve-test
|
||||
printf 'dovecot_config_version = 2.4.0\ndovecot_storage_version = 2.4.0\nssl = no\n' > /tmp/mailcat-sieve-test/dovecot.conf
|
||||
=======
|
||||
- The NAS daily report target is `hans@australius.nl`.
|
||||
- It sends a report even when there are zero messages.
|
||||
- Report rows fetch `Message-ID` and render the subject as an Apple Mail `message://` link when a `Message-ID` is present.
|
||||
- Apple Mail links are macOS-oriented. They depend on Apple Mail having the message synced/indexed locally; iOS Mail should not be treated as reliable for these links.
|
||||
- Report HTML escapes folder labels, senders, subjects, and link attributes.
|
||||
|
||||
## Web Route Management
|
||||
|
||||
@@ -145,6 +265,8 @@ Security note:
|
||||
|
||||
## NAS Deployment
|
||||
|
||||
The files have already been deployed to the NAS and the webserver is running.
|
||||
|
||||
`deploy_vps.sh` is host-neutral despite its historical filename.
|
||||
|
||||
For the QNAP-like NAS, use:
|
||||
@@ -188,6 +310,7 @@ In `SERVICE_MANAGER=plain` mode:
|
||||
- The sorter appends wrapper stdout/stderr to `$LOG_DIR/daemon.out`.
|
||||
- The web UI appends stdout/stderr to `$LOG_DIR/web.out`.
|
||||
- The daily report wrapper appends stdout/stderr to `$LOG_DIR/daily_report.out`.
|
||||
- `stop_mailcat.sh` and `stop_web.sh` in plain mode now send SIGTERM, wait up to 20 seconds, then fall back to SIGKILL and wait up to 5 seconds before reporting failure.
|
||||
|
||||
Before starting services on the NAS:
|
||||
|
||||
@@ -224,15 +347,18 @@ Treat that as a successful backup historical-sorter run with no immediate recove
|
||||
|
||||
## Verification Commands
|
||||
|
||||
Latest local verification should include:
|
||||
Latest local verification included:
|
||||
|
||||
```sh
|
||||
python3 -m unittest tests.test_dagelijks_overzicht
|
||||
python3 -m py_compile *.py tests/*.py
|
||||
bash -n deploy_vps.sh
|
||||
python3 -m unittest discover -s tests
|
||||
git diff --check
|
||||
```
|
||||
|
||||
After the plain-mode stop wrapper change, `bash -n deploy_vps.sh` was rerun successfully.
|
||||
|
||||
Useful routing sanity check:
|
||||
|
||||
```sh
|
||||
@@ -249,8 +375,8 @@ PY
|
||||
|
||||
Likely next steps:
|
||||
|
||||
1. Deploy the updated code to the NAS in `SERVICE_MANAGER=plain` mode.
|
||||
2. Create remote `web_config.json`.
|
||||
3. Start the web UI with `/share/homes/mailcat/mailcat/bin/start_web.sh`.
|
||||
4. Open `http://<nas-host-or-ip>:4321/` from the internal network and verify Basic Auth.
|
||||
5. Make a harmless route edit, verify `domain_routes.json` and `domain_routes.json.bak`, then restart the sorter if the live daemon is running.
|
||||
1. Deploy the Apple Mail link update and strengthened stop wrappers to the NAS.
|
||||
2. If an already-running sorter will not stop with the old wrapper, stop it manually by reading `$RUN_DIR/mailcat.pid` and sending TERM/KILL to that PID, then remove the stale PID file.
|
||||
3. Run `/share/homes/mailcat/mailcat/bin/run_daily_report.sh` manually on the NAS to send a test digest.
|
||||
4. Open the digest on macOS Mail and verify subject links open local Apple Mail messages.
|
||||
5. If iOS/web reliability is needed later, add Roundcube links using mailbox plus IMAP UID, or add a read-only Mailcat message viewer.
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
require ["copy","date","fileinto","regex"];
|
||||
# rule:[kwartaalmail]
|
||||
if allof (date :regex "Date" "month" "[789]", date :is "Date" "year" "2026")
|
||||
{
|
||||
fileinto :copy "INBOX.Notes";
|
||||
}
|
||||
# rule:[zuiderzee]
|
||||
if allof (header :contains "from" "zuiderzee")
|
||||
{
|
||||
fileinto :copy "INBOX.Notes";
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
12747
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,41 @@
|
||||
import unittest
|
||||
|
||||
from dagelijks_overzicht import apple_mail_url, bouw_html
|
||||
|
||||
|
||||
class DagelijksOverzichtTests(unittest.TestCase):
|
||||
def test_apple_mail_url_encodes_message_id(self):
|
||||
self.assertEqual(
|
||||
apple_mail_url("<abc.123@example.com>"),
|
||||
"message://%3Cabc.123%40example.com%3E",
|
||||
)
|
||||
|
||||
def test_apple_mail_url_wraps_bare_message_id(self):
|
||||
self.assertEqual(
|
||||
apple_mail_url("abc.123@example.com"),
|
||||
"message://%3Cabc.123%40example.com%3E",
|
||||
)
|
||||
|
||||
def test_bouw_html_links_subject_and_escapes_headers(self):
|
||||
html = bouw_html(
|
||||
{
|
||||
"INBOX.Test & Folder": [
|
||||
{
|
||||
"time": "08:15",
|
||||
"from": "Alice <admin>",
|
||||
"subject": "Factuur <juli>",
|
||||
"message_id": "<msg@example.com>",
|
||||
}
|
||||
]
|
||||
},
|
||||
__import__("datetime").date(2026, 7, 5),
|
||||
)
|
||||
|
||||
self.assertIn("message://%3Cmsg%40example.com%3E", html)
|
||||
self.assertIn("Alice <admin>", html)
|
||||
self.assertIn("Factuur <juli>", html)
|
||||
self.assertIn("Test & Folder", html)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+7977
-3182
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user