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

186 lines
6.3 KiB
Markdown

# 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
```bash
cp .env.example .env
```
Encryption-Key generieren und in die `.env` eintragen:
```bash
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
```
Falls `cryptography` noch nicht installiert ist:
```bash
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
```bash
docker-compose up --build
```
Der Service ist dann unter **http://localhost:8080** erreichbar.
### 3. Ohne Docker (lokal)
```bash
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:
```yaml
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:
```bash
# 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
```bash
source .venv/bin/activate
python -m pytest tests/ -v
```