KI-E-Mail Sortierer

Automatische E-Mail-Kategorisierung mit lokalem LLM (Ollama), IMAP und SQLite. Keine Cloud, volle Kontrolle.

🐍 Python 🤖 KI 🔒 Local-First

Das Problem

Manuelle E-Mail-Sortierung ist Zeitverschwendung. Regeln basieren auf starren Kriterien (Absender, Betreff), missen aber Kontext. Newsletter-Filter erwischen Werbung, aber nicht "wichtige" Updates. Resultat: tägliches Durchforsten des Postfachs – oder verpasste E-Mails.

Zeitfresser
15-30 Minuten täglich nur zum Sortieren und Kategorisieren
🤷
Kontext-blind
Starre Filter verstehen keine Inhalte und Intentionen
☁️
Cloud-Abhängigkeit
Gmail/Microsoft haben Zugriff auf alle E-Mails

Architektur

Der Datenfluss: IMAP holt E-Mails → LLM kategorisiert → SQLite trackt Zustand → IMAP verschiebt in Ordner.

📧 IMAP Server
🤖 Ollama LLM
🗄️ SQLite
📂 IMAP Ordner
📧
IMAP-Verbindung
SSL/TLS, Retry-Logik, Verbindungstest. Robust gegen instabile Provider.
🤖
Ollama LLM
Lokales Modell (Gemma 3 1B) zur Inhaltsanalyse. Keine Datenlecks.
🗄️
SQLite Tracking
MD5-Hash-basierte Duplikaterkennung, Verarbeitungsstatus.

Code-Deep-Dive

IMAP-Verbindung mit Retry-Logik

IONOS-Server sind instabil. Exponentieller Backoff, SSL-Kontext-Optimierung, sauberes State-Management.

Python
def connect_to_account(self, account: Dict, max_retries: int = 3):
    for attempt in range(max_retries):
        try:
            socket.setdefaulttimeout(60)
            
            if account['use_ssl']:
                ssl_context = ssl.create_default_context()
                ssl_context.check_hostname = False
                ssl_context.verify_mode = ssl.CERT_NONE
                ssl_context.set_ciphers(
                    'HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA'
                )
                imap = imaplib.IMAP4_SSL(
                    account['server'], 
                    account['port'], 
                    ssl_context=ssl_context
                )
            else:
                imap = imaplib.IMAP4(account['server'], account['port'])
            
            imap.login(account['username'], account['password'])
            
            # Verbindungstest
            result = imap.capability()
            if result[0] != 'OK':
                raise Exception(f"IMAP-Capability-Test fehlgeschlagen")
            
            return imap
            
        except (socket.error, socket.timeout, imaplib.IMAP4.error) as e:
            wait_time = (2 ** attempt) * 5  # 5, 10, 20 Sekunden
            time.sleep(wait_time)
        
    return None

LLM-Prompt für Kategorisierung

Der Prompt enthält persönlichen Kontext, detaillierte Regeln pro Kategorie, und strikte Format-Anweisungen.

YAML Template
prompt_template: "Du bist ein E-Mail-Kategorisierer. 
Deine Aufgabe ist es, E-Mails in eine der folgenden 
Kategorien einzuordnen:
{categories_with_rules}

!!!! Antworte NUR mit dem Namen der Kategorie, 
ohne weitere Erklärungen !!!!

PERSÖNLICHER KONTEXT:
{personal_context}

Regeln:
1. Antworte NUR mit dem Namen der Kategorie
2. Normale E-Mails bleiben im Hauptpostfach
3. Werbung von großen Tech-Firmen ist 'Werbung'
4. Werbung von anderen Absendern ist 'Spam'
5. Wenn Links verdächtig sind → 'Spam'"

Content-Hash für Duplikate

MD5 über Betreff + Absender + Content (erste 500 Zeichen). Schnell, zuverlässig, kollisions-resistent für diesen Use-Case.

Python
def create_email_hash(self, subject: str, from_address: str, 
                      content: str) -> str:
    """Erstellt einen Hash zur Duplikat-Erkennung."""
    email_content = f"{subject}|{from_address}|{content[:500]}"
    return hashlib.md5(
        email_content.encode('utf-8')
    ).hexdigest()

def is_email_duplicate(self, email_hash: str) -> Optional[dict]:
    with sqlite3.connect(self.db_path) as conn:
        cursor = conn.cursor()
        cursor.execute('''
            SELECT account_name, subject, from_address, 
                   category, processed_at 
            FROM processed_emails 
            WHERE email_hash = ?
        ''', (email_hash,))
        result = cursor.fetchone()
        # ... parse to dict

SQLite Schema

SQL
CREATE TABLE IF NOT EXISTS processed_emails (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    account_name TEXT NOT NULL,
    email_uid TEXT NOT NULL,
    email_hash TEXT NOT NULL,
    subject TEXT,
    from_address TEXT,
    category TEXT,
    processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE(account_name, email_uid)
);

CREATE INDEX idx_account_uid 
ON processed_emails(account_name, email_uid);

CREATE INDEX idx_email_hash 
ON processed_emails(email_hash);

Kategorien-Konfiguration

YAML
categories:
  - name: Wichtig
    description: Wichtige E-Mails, sofortige Aufmerksamkeit
  
  - name: Projekte
    description: E-Mails bezüglich laufender Projekte
  
  - name: Newsletter
    description: Newsletter und regelmäßige Updates
  
  - name: Werbung
    description: Werbemails und Angebote
    detailed_rules: |
      Newsletter von Google, Microsoft, Apple, Amazon
      Seriöse Angebote von bekannten Marken
      Produktupdates von etablierten Services
  
  - name: Spam
    description: Verdächtige oder unerwünschte E-Mails
  
  - name: Rechnungen
    description: Rechnungen und Zahlungsbestätigungen
  
  - name: Termine
    description: Termine und Einladungen
  
  - name: Archiv
    description: E-Mails, die archiviert werden sollen
  
  - name: Volt-Herzogenrath
    description: Wahlkampftermine, Anfragen etc

9 Kategorien

Jede Kategorie ist trainiert mit spezifischen Regeln. Das LLM bekommt den vollständigen Kontext.

🔴
Wichtig
Dringende E-Mails, die sofortige Aufmerksamkeit erfordern. Priorität hoch.
📋
Projekte
Alles rund um laufende Projekte, Tasks, Updates von Team-Mitgliedern.
📰
Newsletter
Regelmäßige Updates, Magazine, Abonnements – gewollte Informationen.
📢
Werbung
Seriöse Angebote von Google, Microsoft, Amazon & Co. Ordentliche Absender.
🚫
Spam
Verdächtige Links, unbekannte Absender, Phishing-Versuche, dubiose Angebote.
💰
Rechnungen
Rechnungen, Zahlungsbestätigungen, Mahnungen, Finanzdokumente.
📅
Termine
Einladungen, Kalender-Updates, Meeting-Reminders, Events.
📦
Archiv
E-Mails, die nicht unbedingt gelesen, aber aufbewahrt werden müssen.
Volt-Herzogenrath
Wahlkampf, politische Termine, lokale Anfragen – personenspezifisch.

Lessons Learned