The existing daily cleanup never effectively ran: it was an interval job counted from container start (reset on every deploy), it called asyncio.get_event_loop() from a worker thread, and even when deleting, SQLite never returns freed pages to the filesystem. Live DB: 4.1 GB, of which 2.6 GB log and 0.6 GB unused pages. New maintenance service, scheduled daily at MAINTENANCE_HOUR_UTC:30 and once 5 minutes after every start: - delete log entries older than LOG_RETENTION_DAYS (30) and keep at most LOG_MAX_ENTRIES (200k) newest ones - delete processed_mails markers of deleted rules/accounts (FKs are not enforced by SQLite) - VACUUM when >25% of the file is unused, only if free disk space covers the copy; never runs twice in parallel GET/POST /api/logs/maintenance expose status and a manual run; the log menu shows DB size, entry count and last run, with "Datenbank aufräumen & verkleinern". Removed the dead cleanup_old_logs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6.1 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 |
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