KI-gestützte E-Mail-Automatisierung: 90% weniger manuelle Sortierung

Von manueller Regelpflege zur intelligenten KI-Kategorisierung

Python Ollama IMAP SQLite

🚀 Ihr Posteingang. Automatisiert. Heute.

📞 02406 803 7603 ✉️ info@computerkumpel.de

💰 Warum sich KI-E-Mail-Sortierung rechnet

Ein durchschnittlicher Mitarbeiter verbringt 18–25 Minuten pro Tag mit manuellem E-Mail-Sortieren — das sind 3.300–4.600 € pro Jahr an verlorener Arbeitszeit (bei 80 € Stundensatz). Die Automatisierung via lokalem LLM amortisiert sich innerhalb weniger Wochen.

⏱️
Zeitersparnis
Reduktion der manuellen Sortierzeit um 90%. Statt 25 Minuten täglich nur noch 2–3 Minuten zur Qualitätskontrolle.
💶
Kostensenkung
Lokale KI (Ollama) statt Cloud-API-Kosten. Laufende Kosten nahe Null — die GPU haben Sie ohnehin im Büro.
🎯
Qualität
Keine vergessenen Rechnungen mehr. Keine übersehenen Projekt-Mails. Konsistente Kategorisierung, 24/7.
📈
Skalierung
Funktioniert für 1 oder 100 Postfächer. Neue Kategorien per YAML-Konfiguration — kein Code-Änderungsbedarf.

⚙️ So funktioniert's

Vier Schritte vom Posteingang zum sortierten Ordner — vollautomatisch im Hintergrund.

📥
1. IMAP holen
Python-Script verbindet sich per IMAP mit Ihrem Postfach und holt neue, unverarbeitete E-Mails ab.
🧠
2. LLM analysieren
Betreff, Absender, Inhalt und Links werden an ein lokales LLM (Ollama) zur Klassifikation gesendet.
🏷️
3. Kategorisieren
9 konfigurierbare Kategorien — von Wichtig über Rechnungen bis Werbung. Plus persönlicher Kontext.
📂
4. Sortieren
E-Mails werden automatisch in IMAP-Ordner verschoben. SQLite-Tracking verhindert Doppelverarbeitung.

💻 Code-Einblicke

Ein Blick unter die Haube — so analysiert und sortiert die KI Ihre E-Mails:

🏗️ Architektur

email_sorter.py
Einstiegspunkt — initialisiert Handler und orchestriert den gesamten Durchlauf.
email_handler.py
IMAP-Verbindungen, E-Mail-Parsing, Ordner-Management, Verschiebe-Logik mit Retry.
llm_handler.py
Prompt-Engineering, Ollama-API-Kommunikation, Kategorie-Validierung, persönlicher Kontext.
db_handler.py
SQLite-Verwaltung, Duplikat-Erkennung via Hash, Tracking verarbeiteter E-Mails.

🤖 LLM-Handler: Prompt-Engineering & Ollama

class LLMHandler:
    def __init__(self, config_path='config.yaml'):
        with open(config_path, 'r', encoding='utf-8') as f:
            self.config = yaml.safe_load(f)
        self.ollama_config = self.config['ollama']
        self.categories = self.config['categories']
        self.prompt_template = self.config['prompt_template']
        self.personal_context = self.config.get('personal_context', {})

    def _format_categories_with_rules(self) -> str:
        categories_text = []
        categories_text.append("HAUPTPOSTFACH:")
        categories_text.append("    Normale, persönliche oder geschäftliche Kommunikation.")
        for category in self.categories:
            category_name = category.get('name', 'Unbekannt').upper()
            description = category.get('description', '')
            detailed_rules = category.get('detailed_rules', '')
            categories_text.append(f"{category_name}:")
            if description:
                categories_text.append(f"    Beschreibung: {description}")
            if detailed_rules:
                rules_lines = detailed_rules.split('\n')
                for rule in rules_lines:
                    if rule.strip():
                        categories_text.append(f"    - {rule.strip()}")
        return "\n".join(categories_text)

    def categorize_email(self, subject, content, from_address, links):
        prompt = self._format_prompt(subject, content, from_address, links)
        response = requests.post(
            f"{self.ollama_config['base_url']}/api/generate",
            json={
                "model": self.ollama_config['model'],
                "prompt": prompt,
                "temperature": self.ollama_config['temperature'],
                "max_tokens": self.ollama_config['max_tokens'],
                "stream": False
            }
        )
        if response.status_code == 200:
            category = response.json().get('response', '').strip().strip('"\' ')
            valid_categories = [cat['name'] for cat in self.categories] + ["Hauptpostfach"]
            return category if category in valid_categories else "Archiv"

📧 E-Mail-Handler: IMAP & Content-Extraktion

class EmailHandler:
    def __init__(self, config_path='config.yaml'):
        with open(config_path, 'r', encoding='utf-8') as f:
            self.config = yaml.safe_load(f)
        self.accounts = self.config['email_accounts']
        self.categories = [self.sanitize_folder_name(cat['name'])
                          for cat in self.config['categories']]
        self.db_handler = DatabaseHandler()

    def get_email_content(self, email_message):
        subject = self.decode_email_header(email_message.get('subject', ''))
        from_address = self.decode_email_header(email_message.get('from', ''))
        text_content, html_content, links = [], [], []

        if email_message.is_multipart():
            for part in email_message.walk():
                if part.get_content_type() == "text/plain":
                    payload = part.get_payload(decode=True)
                    if payload:
                        charset = part.get_content_charset() or 'utf-8'
                        text_content.append(payload.decode(charset, errors='replace'))
                elif part.get_content_type() == "text/html":
                    payload = part.get_payload(decode=True)
                    if payload:
                        html = payload.decode(
                            part.get_content_charset() or 'utf-8', errors='replace')
                        parser = LinkExtractor()
                        parser.feed(html)
                        links.extend([f"{text} -> {href}"
                                     for text, href in parser.links])

        return subject or "Kein Betreff", content, from_address, links

🔄 Verarbeitungs-Pipeline mit Duplikat-Erkennung

def process_emails(self, llm_callback) -> None:
    for account in self.accounts:
        imap = self.connect_to_account(account)
        self.ensure_folders_exist(imap)
        imap.select('INBOX')
        _, messages = imap.uid('search', None, 'ALL')

        for email_uid in messages[0].split():
            email_uid_str = email_uid.decode('utf-8')

            if self.db_handler.is_email_processed(account['name'], email_uid_str):
                continue  # Bereits verarbeitet → überspringen

            _, msg_data = imap.uid('fetch', email_uid, '(RFC822)')
            email_message = BytesParser().parsebytes(msg_data[0][1])
            subject, content, from_address, links = self.get_email_content(email_message)

            # Duplikat-Check via Content-Hash
            email_hash = self.db_handler.create_email_hash(subject, from_address, content)
            duplicate_info = self.db_handler.is_email_duplicate(email_hash)
            if duplicate_info:
                category = duplicate_info['category']
            else:
                category = llm_callback(subject, content[:5000], from_address, links)

            self.db_handler.mark_email_processed(
                account['name'], email_uid_str, subject, from_address, content, category)
            if category.lower() != "hauptpostfach":
                self.move_email(imap, email_uid, category)

📂 IMAP-Ordner & Robustheit

def sanitize_folder_name(self, name: str) -> str:
    sanitized = re.sub(r'[^\w\s\-_]', '_', name)
    if len(sanitized) > 30:
        sanitized = sanitized[:27] + "..."
    return sanitized.strip(' _') or "Kategorie"

def move_email(self, imap, email_uid, category):
    self.safe_imap_operation(imap, f"Kopiere UID {email_uid}",
        lambda: imap.uid('copy', email_uid, category))
    time.sleep(0.5)
    self.safe_imap_operation(imap, f"Markiere UID {email_uid} gelöscht",
        lambda: imap.uid('store', email_uid, '+FLAGS', '\\Deleted'))
    time.sleep(0.5)
    self.safe_imap_operation(imap, f"Expunge",
        lambda: imap.expunge())
    return True

def safe_imap_operation(self, imap, operation_name, operation_func, max_retries=3):
    for attempt in range(max_retries):
        try:
            return operation_func()
        except (socket.error, socket.timeout, imaplib.IMAP4.abort) as e:
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)  # 1, 2, 4 Sekunden Exponential Backoff

⚡ In 5–7 Tagen zum MVP — nicht in 8–13 Wochen.

📞 02406 803 7603 ✉️ info@computerkumpel.de

🚀 Gebaut mit Vibecoding — in 5–7 Tagen statt 8–13 Wochen

👴 Klassische Entwicklung
  • 📋 2–3 Wochen Requirements Engineering
  • 🏗️ 2–3 Wochen Architektur & Design
  • 💻 3–4 Wochen Implementierung
  • 🧪 1–2 Wochen Testing
  • 🚢 1 Woche Deployment
  • ⏱️ Gesamt: 8–13 Wochen
🤖 Vibecoding-Ansatz
  • 🗣️ 0.5 Tage Prompt-Engineering
  • ⚡ 2–3 Tage iterative Generierung
  • 🔧 1–2 Tage Refinement & Debugging
  • ✅ 1 Tag Integration & Testing
  • 🚀 0.5 Tage Deployment
  • ⏱️ Gesamt: 5–7 Tage

🗣️ Der System-Prompt hinter diesem Projekt

Dieser konkrete, deutsche System-Prompt wurde für Coding-Agenten (Claude, ChatGPT, Copilot) optimiert — so entstand der gesamte Code in wenigen Iterationen:

Du bist ein Python-Experte für E-Mail-Automatisierung mit IMAP und lokaler KI.

Aufgabe: Erstelle ein Python-Script, das E-Mails aus einem IMAP-Postfach 
liest, mit einem lokalen LLM (Ollama) kategorisiert und automatisch in 
entsprechende IMAP-Ordner verschiebt.

Technische Anforderungen:
- Python 3.10+, ausschließlich Standard-Bibliotheken wo möglich
- IMAP-Verbindung über imaplib mit SSL
- Kategorisierung über Ollama API (lokale Instanz, Modell: gemma3:1b)
- Kategorien aus YAML-Konfigurationsdatei lesbar
- SQLite-Datenbank zur Vermeidung von Doppelverarbeitung
- Robustes Error-Handling mit Retry-Logik (Exponential Backoff)
- Duplikat-Erkennung via Content-Hashing (MD5)
- Logging mit verschiedenen Log-Leveln

Kategorien:
1. Wichtig — sofortige Aufmerksamkeit
2. Projekte — laufende Kundenprojekte
3. Newsletter — abonnierte Inhalte
4. Werbung — Marketing großer Tech-Firmen
5. Spam — verdächtige/unerwünschte Mails
6. Rechnungen — Zahlungsverkehr
7. Termine — Kalender & Meetings
8. Archiv — Fallback-Kategorie
9. Volt-Herzogenrath — politische Kommunikation

Prompt-Template für die Kategorisierung:
"Du bist ein E-Mail-Kategorisierer. Deine Aufgabe ist es, E-Mails in eine 
der folgenden Kategorien einzuordnen: [KATEGORIEN]. Antworte NUR mit dem 
Namen der Kategorie, ohne weitere Erklärungen."

Wichtig:
- Normale Geschäftskommunikation bleibt im Hauptpostfach
- Persönlicher Kontext (Name, Firma, wichtige Kontakte) einbeziehen
- Links in E-Mails auf verdächtige Muster prüfen (Linktext ≠ Ziel-URL = Spam)
- IONOS-IMAP-Limit: Ordnernamen max. 30 Zeichen

Output: Mehrere .py-Dateien — modular, testbar, produktionsreif.

🎯 Strategische Erkenntnisse aus diesem Projekt

Die Entwicklung dieses Systems hat wertvolle Einsichten geliefert — nicht nur technisch, sondern vor allem strategisch. Diese Erkenntnisse sind direkt auf andere Automatisierungsprojekte übertragbar.

🔒
Datenschutz als Wettbewerbsvorteil
Lokale KI (Ollama on-premise) statt Cloud-APIs (OpenAI, Anthropic) bedeutet: Keine E-Mail-Daten verlassen das Unternehmensnetzwerk. Für DSGVO-sensible Branchen (Anwälte, Ärzte, Steuerberater) ist das der entscheidende Hebel — sie dürfen E-Mails gar nicht in die Cloud senden. Lokale LLMs lösen dieses Compliance-Problem elegant.
📈
Bottom-up-Skalierung schlägt Big Bang
Statt einer monatelangen „Digitalisierungsinitiative" startet das System mit einem Postfach und einer GPU. Wenn es funktioniert, skaliert es organisch — weitere Postfächer kommen per YAML-Eintrag dazu. Kein Projektantrag, kein Budget-Meeting, keine Change-Management-Kaskade. Erst beweisen, dann ausrollen.
🏗️
Infrastruktur: Das unterschätzte Risiko
Die größte technische Hürde war nicht das LLM oder die Kategorisierungslogik — sondern IMAP-Verbindungsstabilität bei IONOS. Der Server beendet Sessions nach kurzer Inaktivität, SSL-Handshakes scheitern sporadisch. Lösung: Verbindungs-Rotation alle 10 E-Mails, Exponential Backoff, separates Ordner- vs. Nachrichten-Handling. Lesson: Die „letzte Meile" (Protokoll-Eigenheiten, Provider-Limits) frisst oft 40% der Entwicklungszeit.
👥
Change Management: Vertrauen in KI-Entscheidungen
Menschen vertrauen ihrer eigenen E-Mail-Sortierung blind — einer KI nicht. Deshalb: Transparenz durch Logging, keine Black Box. Das System loggt jede Entscheidung mit Begründung. Die ersten 2–3 Wochen läuft es im „Schattenmodus" (sortiert, aber nichts wird gelöscht). Erst wenn die Trefferquote bei >95% liegt, übernimmt es autonom. Change Management bedeutet hier: Beweise liefern, nicht überreden.

Bereit für die digitale Transformation?

Jedes Unternehmen hat repetitive E-Mail-Workflows. Lassen Sie uns gemeinsam herausfinden, wo KI bei Ihnen den größten Hebel hat — unverbindlich und pragmatisch.

🔍
Workshop & Analyse
Wir analysieren Ihre E-Mail-Workflows, identifizieren Automatisierungspotenziale und skizzieren eine Roadmap. Dauer: 1–2 Tage vor Ort oder remote.
🧪
Proof-of-Concept
Ein funktionierender Prototyp mit Ihren echten Daten und Kategorien — innerhalb von 5–7 Tagen. Sie sehen das Ergebnis, bevor Sie sich committen.
🤝
Projektbegleitung
Von der Konfiguration über das Training bis zum Go-Live — ich begleite Ihr Team, bis das System stabil und autonom läuft.
📞 02406 803 7603 ✉️ info@computerkumpel.de