Files
imap-mail-filter-service/README.md
T
duffyduckandClaude Opus 5.5 d5cb0a83f3 new action: attach the message as PDF and file it
For senders whose mail body is the receipt (e.g. GoCardless payouts)
there is no attachment to file. The new action "Als PDF anhängen +
nach <Ordner>" renders the mail like a print from a mail client
(header block From/To/Date/Subject, then the content) and replaces it
in the target folder with: original content + PDF + the original mail
byte-for-byte as Original.eml, so the unaltered original is retained
(GoBD).

- Rendering with WeasyPrint. Images are fetched by us and inlined as
  data: URIs; WeasyPrint itself may only read data:. Fetching is
  restricted to http(s), public addresses (private, loopback,
  link-local and PDF_BLOCKED_NETWORKS, default 172.0.0.0/12, are
  refused; redirects are checked hop by hop), with time and size
  limits. cid: images come from the MIME parts.
- The replacement is APPENDed with the original flags and INTERNALDATE,
  verified in the target via its new Message-ID, and only then is the
  original deleted. Already converted mails are only moved.
- "PDF-Vorschau" in the rule dialog renders the newest matching mail
  read-only and opens it in the browser.
- List/log show 📄 targets; warnings for PDF + move/delete and for
  PDF + "Hat Anhang = Ja"; log folder search covers PDF targets.
- Docker image gets Pango and fonts (~316 MB total).

Tested with the real GoCardless mail (3 remote images, rendered both
locally and inside the built image) and a simulated IMAP server for
success, rejected APPEND, missing copy and already converted mails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:51:26 +02:00

6.3 KiB

IMAP Mail Filter Service

Ein Docker-basierter Service, der E-Mail-Filterregeln unabhängig vom Mail-Client ausführt. Egal ob du Evolution, Thunderbird oder einen anderen Client nutzt — die Filter laufen einfach weiter.

Features

  • Mehrere IMAP-Konten verwalten
  • Filterregeln mit flexiblen Bedingungen (Von, An, Betreff, Text)
  • Matching: enthält, exakt, Regex — jeweils mit Negierung
  • Aktionen: Verschieben, Weiterleiten, Löschen, Als gelesen markieren
  • Web-UI zur Verwaltung von Konten und Regeln
  • YAML Import/Export für portable Konfiguration
  • Passwort-Verschlüsselung mit Fernet
  • Konfigurierbares Polling-Intervall pro Konto

Schnellstart

1. .env erstellen

cp .env.example .env

Encryption-Key generieren und in die .env eintragen:

python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

Falls cryptography noch nicht installiert ist:

pip install cryptography
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

Den ausgegebenen Key in die .env eintragen:

ENCRYPTION_KEY=dein-generierter-key-hier

2. Mit Docker starten

docker-compose up --build

Der Service ist dann unter http://localhost:8080 erreichbar.

3. Ohne Docker (lokal)

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload

Erreichbar unter http://localhost:8000.

Benutzung

Web-UI

  • Dashboard (/) — Übersicht aller Konten mit Status und Quick-Actions
  • Konten (/accounts) — IMAP-Konten anlegen, bearbeiten, Verbindungstest
  • Filter (/filters) — Filterregeln pro Konto erstellen und verwalten
  • YAML (/yaml) — Konfiguration exportieren und importieren

REST-API

Endpunkt Beschreibung
GET /api/accounts/ Alle Konten auflisten
POST /api/accounts/ Neues Konto anlegen
PUT /api/accounts/{id} Konto bearbeiten
DELETE /api/accounts/{id} Konto löschen
POST /api/accounts/{id}/test IMAP-Verbindungstest
POST /api/accounts/{id}/poll-now Sofort nach neuen Mails prüfen
GET /api/filters/account/{id} Filterregeln eines Kontos
POST /api/filters/ Neue Filterregel
PUT /api/filters/{id} Filterregel bearbeiten
DELETE /api/filters/{id} Filterregel löschen
GET /api/yaml/export Konfiguration als YAML
POST /api/yaml/import YAML-Datei importieren

YAML-Konfiguration

Filterregeln können auch direkt als YAML definiert werden. Passwörter lassen sich als Umgebungsvariablen referenzieren:

accounts:
  - name: "Arbeit"
    imap_host: "imap.example.com"
    imap_port: 993
    use_ssl: true
    username: "user@example.com"
    password: "${WORK_IMAP_PW}"
    poll_interval_seconds: 120
    filters:
      - name: "Newsletter sortieren"
        priority: 10
        source_folder: "INBOX"
        stop_processing: true
        conditions:
          - field: "from"
            match_type: "contains"
            value: "newsletter@"
        actions:
          - action_type: "move"
            parameter: "Newsletter"

Beim Start wird config/filters.yaml automatisch importiert (konfigurierbar via YAML_SYNC_ON_STARTUP).

Umgebungsvariablen

Variable Standard Beschreibung
ENCRYPTION_KEY (leer) Fernet-Key für Passwort-Verschlüsselung
LOG_LEVEL INFO Log-Level (DEBUG, INFO, WARNING, ERROR)
YAML_SYNC_ON_STARTUP true YAML-Datei beim Start importieren
LOG_RETENTION_DAYS 30 Verarbeitungslog: Einträge älter als N Tage werden gelöscht
LOG_MAX_ENTRIES 200000 Verarbeitungslog: höchstens so viele (neueste) Einträge behalten
MAINTENANCE_HOUR_UTC 2 Stunde (UTC) der täglichen DB-Wartung; läuft zusätzlich 5 Min. nach jedem Start
PDF_BLOCKED_NETWORKS 172.0.0.0/12 „Als PDF anhängen": aus diesen Netzen werden keine Bilder geladen (zusätzlich zu privaten Netzen, die immer gesperrt sind)
LOG_VERBOSE false Jede Nicht-Übereinstimmung loggen — erzeugt Millionen Einträge, nur zur Fehlersuche

Die tägliche Wartung löscht alte Log-Einträge, entfernt Verarbeitungsmarker gelöschter Regeln und verkleinert die SQLite-Datei per VACUUM, sobald mehr als 25 % ungenutzt sind (nur wenn genug Plattenplatz frei ist). Manuell auslösbar im Log unter ⋯ → „Datenbank aufräumen & verkleinern". | DATABASE_URL | sqlite:///data/mailfilter.db | Datenbank-Pfad |

Datenbank-Migrationen

Die Datenbank wird automatisch beim Start per Alembic migriert — Konten und Filterregeln bleiben bei Updates erhalten.

Falls du lokal entwickelst und das Schema änderst:

# Neue Migration erstellen (nach Änderung an db_models.py)
.venv/bin/alembic revision --autogenerate -m "beschreibung der änderung"

# Migration anwenden
.venv/bin/alembic upgrade head

# Migrationsstatus prüfen
.venv/bin/alembic current

Im Docker passiert das automatisch beim Container-Start.

Projektstruktur

├── app/
│   ├── main.py              # FastAPI App + Web-Routen
│   ├── config.py             # Konfiguration via Umgebungsvariablen
│   ├── database.py           # SQLAlchemy Setup
│   ├── models/db_models.py   # Datenbank-Modelle
│   ├── schemas/schemas.py    # API Request/Response Schemas
│   ├── routers/              # REST-API Endpunkte
│   ├── services/
│   │   ├── imap_client.py    # IMAP-Verbindung und Mail-Aktionen
│   │   ├── filter_engine.py  # Regelauswertung
│   │   ├── scheduler.py      # Polling-Scheduler
│   │   ├── yaml_service.py   # YAML Import/Export
│   │   └── encryption.py     # Passwort-Verschlüsselung
│   ├── templates/            # Jinja2 HTML-Templates
│   └── static/               # CSS + JS
├── alembic/                   # Datenbank-Migrationen
│   ├── env.py
│   └── versions/              # Migrations-Skripte
├── config/filters.yaml       # YAML-Filterkonfiguration
├── data/                     # SQLite-Datenbank (Docker Volume)
├── docker-compose.yml
├── Dockerfile
└── requirements.txt

Tests

source .venv/bin/activate
python -m pytest tests/ -v