# BreakPilot Unified Inbox - Erweiterte Spezifikation **Version:** 2.0.0 **Datum:** 2026-01-10 **Status:** Entwicklungsspezifikation **Priorität:** P0 - Kernprodukt --- ## 1. Executive Summary ### 1.1 Das Problem Eine Schulleiterin in Niedersachsen muss **4 verschiedene dienstliche E-Mail-Adressen** verwalten: ``` ┌─────────────────────────────────────────────────────────────────┐ │ AKTUELLE SITUATION │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 📧 schulleitung@grundschule-xy.de │ │ └── Landesschulbehörde, Schulträger │ │ │ │ 📧 vorname.nachname@schule.niedersachsen.de │ │ └── Bildungsportal Niedersachsen │ │ │ │ 📧 personal@grundschule-xy.de │ │ └── Personalverwaltung, Vertretungsplanung │ │ │ │ 📧 verwaltung@grundschule-xy.de │ │ └── Schulträger (Kommune), Haushalt │ │ │ │ PROBLEME: │ │ ❌ 4 verschiedene Logins │ │ ❌ Fristen werden übersehen │ │ ❌ Keine einheitliche Übersicht │ │ ❌ Keine KI-Unterstützung │ │ ❌ Kein gemeinsamer Kalender │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 1.2 Die Lösung: BreakPilot Unified Inbox ``` ┌─────────────────────────────────────────────────────────────────┐ │ BREAKPILOT UNIFIED INBOX │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ EINHEITLICHES FRONTEND │ │ │ │ │ │ │ │ 📥 Posteingang (alle Konten) │ │ │ │ 📋 Arbeitsvorrat (KI-extrahierte Aufgaben) │ │ │ │ 📅 Kalender (Jitsi-integriert) │ │ │ │ 💬 Chat (Matrix E2EE) │ │ │ │ 📹 Meetings (Jitsi) │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ KI-ASSISTENZ-LAYER │ │ │ │ │ │ │ │ 🤖 Absender-Erkennung (welche Behörde?) │ │ │ │ 📆 Fristen-Extraktion (Deadline-Tracking) │ │ │ │ ✍️ Antwort-Vorschläge (vorformuliert) │ │ │ │ 🏷️ Automatische Kategorisierung │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ MULTI-ACCOUNT AGGREGATOR │ │ │ │ │ │ │ │ IMAP/SMTP ──► schulleitung@grundschule-xy.de │ │ │ │ IMAP/SMTP ──► vorname.nachname@schule.nds.de │ │ │ │ IMAP/SMTP ──► personal@grundschule-xy.de │ │ │ │ IMAP/SMTP ──► verwaltung@grundschule-xy.de │ │ │ │ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 1.3 Zielgruppen | Zielgruppe | Nutzung | Priorität | |------------|---------|-----------| | **BreakPilot GmbH intern** | Interne Kommunikation, Proof-of-Concept | P0 | | **Schulleitungen** | Multi-Account-Aggregation, KI-Assistenz | P0 | | **Lehrkräfte** | Optionales Angebot, funktionale Mailboxen | P1 | | **Schulträger** | White-Label-Lösung für ihre Schulen | P2 | --- ## 2. Kern-Features ### 2.1 Multi-Account Aggregation ``` ┌─────────────────────────────────────────────────────────────────┐ │ ACCOUNT-AGGREGATION │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ UNTERSTÜTZTE PROTOKOLLE: │ │ ├── IMAP/IMAPS (Lesen) │ │ ├── SMTP/SMTPS (Senden) │ │ ├── OAuth2 (Microsoft 365, Google) │ │ └── Exchange Web Services (EWS) │ │ │ │ PRO KONTO GESPEICHERT: │ │ ├── Server-Konfiguration (verschlüsselt) │ │ ├── Credentials (Vault-verschlüsselt) │ │ ├── Display-Name und Farbe │ │ ├── Absender-Identität │ │ └── Signatur (HTML/Text) │ │ │ │ SYNCHRONISATION: │ │ ├── Polling-Intervall: 1-5 Minuten (konfigurierbar) │ │ ├── Push: IMAP IDLE (wenn unterstützt) │ │ └── Full-Sync: 1x täglich (Konsistenzprüfung) │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 2.2 KI-Assistenz-Features ``` ┌─────────────────────────────────────────────────────────────────┐ │ KI-ASSISTENZ │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. ABSENDER-ERKENNUNG │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Von: info@landesschulbehoerde.niedersachsen.de │ │ │ │ │ │ │ │ 🏛️ Erkannt als: Landesschulbehörde Niedersachsen │ │ │ │ 📁 Kategorie: Behördliche Mitteilung │ │ │ │ ⚠️ Priorität: Hoch (enthält Fristsetzung) │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ 2. FRISTEN-EXTRAKTION │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ "...bitten wir um Rückmeldung bis zum 15.02.2026" │ │ │ │ │ │ │ │ 📆 Erkannte Frist: 15. Februar 2026 │ │ │ │ ⏰ Erinnerung: 3 Tage vorher │ │ │ │ ✅ Zum Arbeitsvorrat hinzufügen │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ 3. ANTWORT-VORSCHLÄGE │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Betreff: Bestätigung Terminvorschlag │ │ │ │ │ │ │ │ 💡 Vorschlag 1: "Hiermit bestätige ich den..." │ │ │ │ 💡 Vorschlag 2: "Ich bitte um einen Alternativ..." │ │ │ │ 💡 Vorschlag 3: "Aufgrund von ... ist mir ..." │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ 4. KATEGORISIERUNG │ │ ├── 🏛️ Behörde (Landesschulbehörde, Kultusministerium) │ │ ├── 🏢 Schulträger (Kommune, Kreis) │ │ ├── 👥 Personal (Vertretung, Krankheit, Fortbildung) │ │ ├── 📚 Pädagogik (Curricula, Prüfungen) │ │ ├── 💰 Haushalt (Budget, Beschaffung) │ │ └── 📋 Sonstiges │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 2.3 Arbeitsvorrat (Task-Management) ``` ┌─────────────────────────────────────────────────────────────────┐ │ ARBEITSVORRAT │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 🔴 ÜBERFÄLLIG (2) │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ ☐ Statistik-Meldung Schülerzahlen Frist: 05.01. │ │ │ │ └── Von: Landesschulbehörde ⚠️ 5 Tage über │ │ │ │ ☐ Haushaltsentwurf 2026 Frist: 08.01. │ │ │ │ └── Von: Stadt Musterstadt ⚠️ 2 Tage über │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 🟡 DIESE WOCHE (3) │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ ☐ Fortbildungsanträge genehmigen Frist: 12.01. │ │ │ │ ☐ Elternbrief Halbjahreszeugnis Frist: 14.01. │ │ │ │ ☐ Rückmeldung Prüfungstermine Frist: 15.01. │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 🟢 SPÄTER (5) │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ ☐ Jahresbericht erstellen Frist: 31.01. │ │ │ │ ☐ Medienkonzept aktualisieren Frist: 28.02. │ │ │ │ ... │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ FEATURES: │ │ ├── Automatische Frist-Erkennung aus E-Mails │ │ ├── Manuelle Aufgaben hinzufügen │ │ ├── Erinnerungen (E-Mail, Push, Matrix) │ │ ├── Delegation an Kollegium │ │ ├── Verknüpfung mit Original-E-Mail │ │ └── Export nach iCal/Outlook │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 2.4 Integrationen ``` ┌─────────────────────────────────────────────────────────────────┐ │ INTEGRATIONEN │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 📅 KALENDER (CalDAV) │ │ ├── Termine aus E-Mails extrahieren │ │ ├── Meeting-Einladungen erstellen │ │ ├── Jitsi-Link automatisch hinzufügen │ │ ├── Sync mit externen Kalendern │ │ └── Fristübersicht im Kalender │ │ │ │ 📹 JITSI (Video-Meetings) │ │ ├── Ein-Klick-Meeting aus E-Mail │ │ ├── Einladung an alle Beteiligten │ │ ├── Warteraum für externe Teilnehmer │ │ └── Aufzeichnung (optional, mit Consent) │ │ │ │ 💬 MATRIX (E2EE Chat) │ │ ├── Schnelle Rückfragen an Kollegium │ │ ├── Kanal pro Thema/Fachbereich │ │ ├── E-Mail zu Chat: "Dazu besprechen wir uns kurz" │ │ └── Verschlüsselung für sensible Themen │ │ │ │ 🤖 KI-SERVICES │ │ ├── Fristen-Extraktion (NLP) │ │ ├── Absender-Klassifikation │ │ ├── Antwort-Generierung (optional) │ │ └── Zusammenfassung langer E-Mails │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## 3. Architektur ### 3.1 System-Übersicht ``` ┌────────────────────────────────────────────────────────────────────────┐ │ BREAKPILOT UNIFIED INBOX │ ├────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ FRONTEND LAYER │ │ │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ │ │ │ │ Web App │ │ Mobile PWA │ │ Desktop App │ │ │ │ │ │ (Next.js) │ │ (React) │ │ (Electron) │ │ │ │ │ │ :3000 │ │ PWA │ │ Optional │ │ │ │ │ └────────┬───────┘ └────────┬───────┘ └────────┬───────┘ │ │ │ └───────────┼───────────────────┼───────────────────┼──────────────┘ │ │ │ │ │ │ │ └───────────────────┼───────────────────┘ │ │ │ │ │ ┌──────────────────────────────┴───────────────────────────────────┐ │ │ │ API GATEWAY (Go) │ │ │ │ :8088 │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ Auth │ │ Rate Limit │ │ Logging │ │ │ │ │ │ (JWT/OIDC) │ │ │ │ (Audit) │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └──────────────────────────────┬───────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────────┴───────────────────────────────────┐ │ │ │ APPLICATION LAYER │ │ │ ├───────────────────┬───────────────────┬──────────────────────────┤ │ │ │ │ │ │ │ │ │ ┌─────────────┐ │ ┌─────────────┐ │ ┌─────────────┐ │ │ │ │ │ Mail │ │ │ AI │ │ │ Task │ │ │ │ │ │ Aggregator │ │ │ Service │ │ │ Service │ │ │ │ │ │ (Python) │ │ │ (Python) │ │ │ (Python) │ │ │ │ │ │ :8089 │ │ │ :8090 │ │ │ :8091 │ │ │ │ │ └──────┬──────┘ │ └──────┬──────┘ │ └──────┬──────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌──────┴──────┐ │ ┌──────┴──────┐ │ ┌──────┴──────┐ │ │ │ │ │ IMAP/SMTP │ │ │ LLM │ │ │ PostgreSQL │ │ │ │ │ │ Connectors │ │ │ Gateway │ │ │ Tasks DB │ │ │ │ │ └─────────────┘ │ └─────────────┘ │ └─────────────┘ │ │ │ │ │ │ │ │ │ └───────────────────┴───────────────────┴──────────────────────────┘ │ │ │ │ │ ┌──────────────────────────────┴───────────────────────────────────┐ │ │ │ DATA LAYER │ │ │ ├───────────────────┬───────────────────┬──────────────────────────┤ │ │ │ ┌─────────────┐ │ ┌─────────────┐ │ ┌─────────────┐ │ │ │ │ │ PostgreSQL │ │ │ Redis │ │ │ MinIO │ │ │ │ │ │ (Main DB) │ │ │ (Cache) │ │ │ (Attachm.) │ │ │ │ │ │ :5432 │ │ │ :6379 │ │ │ :9000 │ │ │ │ │ └─────────────┘ │ └─────────────┘ │ └─────────────┘ │ │ │ │ │ │ │ │ │ │ ┌─────────────┐ │ ┌─────────────┐ │ ┌─────────────┐ │ │ │ │ │ Vault │ │ │ Qdrant │ │ │ Stalwart │ │ │ │ │ │ (Secrets) │ │ │ (Vectors) │ │ │ (Own Mail) │ │ │ │ │ │ :8200 │ │ │ :6333 │ │ │ :25/143 │ │ │ │ │ └─────────────┘ │ └─────────────┘ │ └─────────────┘ │ │ │ └───────────────────┴───────────────────┴──────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ EXTERNAL CONNECTIONS │ │ │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ Externe │ │ Jitsi │ │ Matrix │ │ │ │ │ │ IMAP/SMTP │ │ :8443 │ │ :8008 │ │ │ │ │ │ Server │ │ │ │ │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────────────────────────┘ ``` ### 3.2 Mail Aggregator Service ```python # unified_inbox/services/mail_aggregator.py """ Mail Aggregator Service Aggregiert E-Mails aus mehreren externen IMAP-Konten und stellt sie in einer einheitlichen Inbox bereit. WICHTIG: Credentials werden niemals im Klartext gespeichert! Alle Zugangsdaten liegen verschlüsselt in Vault. """ from dataclasses import dataclass from datetime import datetime from typing import List, Optional, Dict, Any from enum import Enum import asyncio import aioimaplib import aiosmtplib class AccountType(str, Enum): IMAP = "imap" OAUTH_MICROSOFT = "oauth_microsoft" OAUTH_GOOGLE = "oauth_google" EWS = "ews" @dataclass class ExternalAccount: """Externes E-Mail-Konto""" id: str user_id: str # BreakPilot User # Identität email_address: str display_name: str color: str # Für UI-Unterscheidung # Server-Konfiguration account_type: AccountType imap_host: str imap_port: int smtp_host: str smtp_port: int use_ssl: bool # Credentials (Vault-Referenz, nicht Klartext!) vault_secret_path: str # Sync-Status last_sync: Optional[datetime] sync_state: str # UIDVALIDITY, HIGHESTMODSEQ # Features supports_idle: bool signature_html: Optional[str] signature_text: Optional[str] @dataclass class AggregatedEmail: """E-Mail aus einem externen Konto""" id: str account_id: str # Welches Konto # E-Mail-Daten message_id: str subject: str from_address: str from_name: str to_addresses: List[str] cc_addresses: List[str] date: datetime body_text: str body_html: Optional[str] has_attachments: bool # Aggregator-Metadaten fetched_at: datetime is_read: bool is_flagged: bool folder: str # KI-Analyse (später befüllt) ai_category: Optional[str] ai_priority: Optional[str] ai_sender_type: Optional[str] ai_extracted_deadline: Optional[datetime] ai_summary: Optional[str] class MailAggregator: """ Aggregiert E-Mails aus mehreren externen Konten. Prinzipien: 1. Credentials niemals im Speicher halten (nur kurz während Verbindung) 2. E-Mail-Inhalte werden verschlüsselt gecacht 3. Vollständiger Audit-Trail """ def __init__( self, db: AsyncSession, vault_client: VaultClient, cache: RedisClient, ai_service: AIService ): self.db = db self.vault = vault_client self.cache = cache self.ai = ai_service self._connections: Dict[str, aioimaplib.IMAP4_SSL] = {} async def add_account( self, user_id: str, account_config: ExternalAccountConfig ) -> ExternalAccount: """ Fügt ein externes E-Mail-Konto hinzu. 1. Validiert Credentials (Test-Verbindung) 2. Speichert Credentials in Vault 3. Erstellt Account-Eintrag in DB """ # 1. Test-Verbindung await self._test_connection(account_config) # 2. Credentials in Vault speichern vault_path = f"secret/mail-accounts/{user_id}/{account_config.email_address}" await self.vault.write(vault_path, { "username": account_config.username, "password": account_config.password, "oauth_token": account_config.oauth_token, }) # 3. Account in DB (ohne Credentials!) account = ExternalAccount( id=str(uuid.uuid4()), user_id=user_id, email_address=account_config.email_address, display_name=account_config.display_name, color=account_config.color or self._generate_color(), account_type=account_config.account_type, imap_host=account_config.imap_host, imap_port=account_config.imap_port, smtp_host=account_config.smtp_host, smtp_port=account_config.smtp_port, use_ssl=account_config.use_ssl, vault_secret_path=vault_path, last_sync=None, sync_state="", supports_idle=False, # Wird beim ersten Sync ermittelt signature_html=account_config.signature_html, signature_text=account_config.signature_text, ) await self._save_account(account) # Initial-Sync anstoßen asyncio.create_task(self.sync_account(account.id)) return account async def sync_account(self, account_id: str) -> SyncResult: """ Synchronisiert ein E-Mail-Konto. Verwendet IMAP IDLE wenn verfügbar, sonst Polling. """ account = await self._get_account(account_id) # Credentials aus Vault holen (nur für diese Operation) credentials = await self.vault.read(account.vault_secret_path) try: async with self._get_imap_connection(account, credentials) as imap: # Neue E-Mails abrufen new_emails = await self._fetch_new_emails(imap, account) # KI-Analyse für jede neue E-Mail for email in new_emails: email.ai_analysis = await self.ai.analyze_email(email) # In lokale DB speichern await self._save_emails(new_emails) # Sync-Status aktualisieren account.last_sync = datetime.utcnow() await self._save_account(account) return SyncResult( success=True, new_count=len(new_emails), account_id=account_id ) finally: # Credentials sofort aus Speicher löschen credentials = None async def send_email( self, account_id: str, to: List[str], subject: str, body_html: str, body_text: str, cc: Optional[List[str]] = None, attachments: Optional[List[Attachment]] = None ) -> SendResult: """ Sendet eine E-Mail über das angegebene Konto. Die E-Mail wird im Namen des externen Kontos gesendet, nicht über den BreakPilot Mail-Server. """ account = await self._get_account(account_id) credentials = await self.vault.read(account.vault_secret_path) try: message = self._build_message( account=account, to=to, subject=subject, body_html=body_html, body_text=body_text, cc=cc, attachments=attachments ) async with aiosmtplib.SMTP( hostname=account.smtp_host, port=account.smtp_port, use_tls=account.use_ssl ) as smtp: await smtp.login(credentials["username"], credentials["password"]) await smtp.send_message(message) # Audit-Log await self._log_sent_email(account_id, to, subject) return SendResult(success=True, message_id=message["Message-ID"]) finally: credentials = None async def get_unified_inbox( self, user_id: str, filters: Optional[InboxFilters] = None, page: int = 1, per_page: int = 50 ) -> UnifiedInboxResponse: """ Gibt die vereinheitlichte Inbox zurück. Alle Konten werden zusammengeführt, aber das Quell-Konto ist immer erkennbar (Farbe, Icon). """ accounts = await self._get_user_accounts(user_id) account_ids = [a.id for a in accounts] # E-Mails aus allen Konten laden query = """ SELECT * FROM aggregated_emails WHERE account_id = ANY($1) """ if filters: if filters.unread_only: query += " AND is_read = false" if filters.account_id: query += f" AND account_id = '{filters.account_id}'" if filters.category: query += f" AND ai_category = '{filters.category}'" if filters.has_deadline: query += " AND ai_extracted_deadline IS NOT NULL" query += " ORDER BY date DESC" query += f" LIMIT {per_page} OFFSET {(page - 1) * per_page}" emails = await self.db.fetch_all(query, account_ids) # Account-Info hinzufügen account_map = {a.id: a for a in accounts} for email in emails: email.account = account_map[email.account_id] return UnifiedInboxResponse( emails=emails, total=await self._count_emails(user_id, filters), page=page, per_page=per_page ) ``` ### 3.3 KI-Analyse-Service ```python # unified_inbox/services/ai_service.py """ AI Service für E-Mail-Analyse Funktionen: 1. Absender-Erkennung (Behörde, Schulträger, etc.) 2. Fristen-Extraktion 3. Antwort-Vorschläge 4. Kategorisierung """ from dataclasses import dataclass from datetime import datetime from typing import List, Optional, Dict import re @dataclass class EmailAnalysis: """Ergebnis der KI-Analyse""" # Absender-Klassifikation sender_type: str # "behoerde", "schultraeger", "eltern", "sonstige" sender_organization: Optional[str] # z.B. "Landesschulbehörde Niedersachsen" # Kategorisierung category: str # "personal", "haushalt", "paedagogik", "verwaltung" priority: str # "hoch", "mittel", "niedrig" # Fristen has_deadline: bool deadline_date: Optional[datetime] deadline_text: Optional[str] # Original-Text mit Frist # Zusammenfassung summary: str # 1-2 Sätze action_required: bool suggested_action: Optional[str] # Antwort-Vorschläge response_suggestions: List[str] # Confidence confidence: float # 0.0 - 1.0 class AIEmailService: """ KI-gestützte E-Mail-Analyse. Verwendet lokales LLM oder API (konfigurierbar). """ # Bekannte Absender-Muster für Niedersachsen SENDER_PATTERNS = { "landesschulbehoerde": { "domains": ["nlschb.niedersachsen.de", "landesschulbehoerde.niedersachsen.de"], "type": "behoerde", "organization": "Landesschulbehörde Niedersachsen" }, "kultusministerium": { "domains": ["mk.niedersachsen.de"], "type": "behoerde", "organization": "Kultusministerium Niedersachsen" }, "bildungsportal": { "domains": ["schule.niedersachsen.de", "nibis.de"], "type": "behoerde", "organization": "Bildungsportal Niedersachsen" }, # Schulträger werden dynamisch aus Domain erkannt } # Frist-Erkennungsmuster DEADLINE_PATTERNS = [ r"bis zum (\d{1,2}\.\d{1,2}\.\d{4})", r"bis spätestens (\d{1,2}\.\d{1,2}\.\d{4})", r"Frist[:\s]+(\d{1,2}\.\d{1,2}\.\d{4})", r"Rückmeldung bis (\d{1,2}\.\d{1,2}\.\d{4})", r"Abgabetermin[:\s]+(\d{1,2}\.\d{1,2}\.\d{4})", r"innerhalb von (\d+) (Tagen|Wochen)", ] def __init__(self, llm_client: LLMClient): self.llm = llm_client async def analyze_email(self, email: AggregatedEmail) -> EmailAnalysis: """ Analysiert eine E-Mail vollständig. """ # 1. Absender-Erkennung (regelbasiert + LLM) sender_info = await self._classify_sender(email) # 2. Fristen-Extraktion deadline_info = await self._extract_deadline(email) # 3. Kategorisierung und Priorität category_info = await self._categorize(email, sender_info) # 4. Zusammenfassung und Aktions-Vorschlag summary_info = await self._summarize(email) # 5. Antwort-Vorschläge generieren responses = await self._generate_responses(email, sender_info, summary_info) return EmailAnalysis( sender_type=sender_info["type"], sender_organization=sender_info.get("organization"), category=category_info["category"], priority=category_info["priority"], has_deadline=deadline_info["has_deadline"], deadline_date=deadline_info.get("date"), deadline_text=deadline_info.get("text"), summary=summary_info["summary"], action_required=summary_info["action_required"], suggested_action=summary_info.get("action"), response_suggestions=responses, confidence=self._calculate_confidence(sender_info, deadline_info) ) async def _classify_sender(self, email: AggregatedEmail) -> Dict: """Klassifiziert den Absender.""" from_domain = email.from_address.split("@")[1].lower() # Regelbasierte Erkennung for key, pattern in self.SENDER_PATTERNS.items(): if from_domain in pattern["domains"]: return { "type": pattern["type"], "organization": pattern["organization"], "confidence": 0.95 } # Fallback: LLM-basierte Erkennung prompt = f""" Klassifiziere den Absender dieser E-Mail: Von: {email.from_name} <{email.from_address}> Betreff: {email.subject} Mögliche Kategorien: - behoerde (Schulbehörde, Ministerium, etc.) - schultraeger (Kommune, Kreis, Stadt) - eltern (Eltern, Elternvertreter) - kollegium (andere Lehrkräfte) - sonstige Antworte nur mit der Kategorie und optional dem Organisationsnamen. Format: kategorie|organisationsname """ response = await self.llm.complete(prompt, max_tokens=50) parts = response.strip().split("|") return { "type": parts[0] if parts else "sonstige", "organization": parts[1] if len(parts) > 1 else None, "confidence": 0.7 } async def _extract_deadline(self, email: AggregatedEmail) -> Dict: """Extrahiert Fristen aus der E-Mail.""" text = email.body_text or "" for pattern in self.DEADLINE_PATTERNS: match = re.search(pattern, text, re.IGNORECASE) if match: deadline_str = match.group(1) # Versuche Datum zu parsen try: if "Tagen" in deadline_str or "Wochen" in deadline_str: # Relative Frist num = int(re.search(r"\d+", deadline_str).group()) unit = "weeks" if "Wochen" in deadline_str else "days" deadline_date = email.date + timedelta(**{unit: num}) else: # Absolutes Datum deadline_date = datetime.strptime(deadline_str, "%d.%m.%Y") return { "has_deadline": True, "date": deadline_date, "text": match.group(0), "confidence": 0.9 } except: pass # LLM-Fallback für komplexere Fristen prompt = f""" Enthält diese E-Mail eine Frist oder einen Abgabetermin? Betreff: {email.subject} Text: {email.body_text[:1000]} Wenn ja, antworte mit: JA|DATUM|ORIGINALTEXT Wenn nein, antworte mit: NEIN """ response = await self.llm.complete(prompt, max_tokens=100) if response.startswith("JA"): parts = response.split("|") if len(parts) >= 2: try: return { "has_deadline": True, "date": datetime.strptime(parts[1].strip(), "%d.%m.%Y"), "text": parts[2] if len(parts) > 2 else None, "confidence": 0.7 } except: pass return {"has_deadline": False} async def _generate_responses( self, email: AggregatedEmail, sender_info: Dict, summary_info: Dict ) -> List[str]: """Generiert Antwort-Vorschläge.""" prompt = f""" Generiere 3 kurze, professionelle Antwort-Vorschläge für diese E-Mail. Kontext: - Absender: {sender_info.get('organization', email.from_name)} ({sender_info['type']}) - Betreff: {email.subject} - Zusammenfassung: {summary_info['summary']} Antworte im folgenden Format: 1. [Erste Antwort-Option] 2. [Zweite Antwort-Option] 3. [Dritte Antwort-Option] Die Antworten sollten: - Formell und respektvoll sein - Zur Situation passen - Unterschiedliche Optionen bieten (zustimmen, nachfragen, ablehnen) """ response = await self.llm.complete(prompt, max_tokens=500) # Parse die drei Vorschläge suggestions = [] for line in response.split("\n"): if line.strip().startswith(("1.", "2.", "3.")): suggestion = line.split(".", 1)[1].strip() if suggestion: suggestions.append(suggestion) return suggestions[:3] ``` --- ## 4. Datenmodell ### 4.1 SQL Schema ```sql -- ============================================================ -- UNIFIED INBOX SCHEMA -- Version: 2.0.0 -- ============================================================ -- Externe E-Mail-Konten CREATE TABLE external_email_accounts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, -- Identität email_address VARCHAR(255) NOT NULL, display_name VARCHAR(255) NOT NULL, color VARCHAR(7) NOT NULL DEFAULT '#3B82F6', -- Hex-Farbe -- Server-Konfiguration account_type VARCHAR(50) NOT NULL DEFAULT 'imap', imap_host VARCHAR(255) NOT NULL, imap_port INTEGER NOT NULL DEFAULT 993, smtp_host VARCHAR(255) NOT NULL, smtp_port INTEGER NOT NULL DEFAULT 587, use_ssl BOOLEAN NOT NULL DEFAULT true, -- Credentials (Vault-Referenz!) vault_secret_path VARCHAR(500) NOT NULL, -- Sync-Status last_sync TIMESTAMP, sync_state JSONB DEFAULT '{}', sync_error VARCHAR(500), supports_idle BOOLEAN DEFAULT false, -- Signaturen signature_html TEXT, signature_text TEXT, -- Status is_active BOOLEAN DEFAULT true, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), -- Constraints CONSTRAINT unique_user_email UNIQUE (user_id, email_address) ); CREATE INDEX idx_eea_user ON external_email_accounts(user_id); -- Aggregierte E-Mails (Cache) CREATE TABLE aggregated_emails ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), account_id UUID NOT NULL REFERENCES external_email_accounts(id) ON DELETE CASCADE, -- E-Mail-Identifikation message_id VARCHAR(500) NOT NULL, uid INTEGER, -- IMAP UID -- E-Mail-Daten subject VARCHAR(1000), from_address VARCHAR(255) NOT NULL, from_name VARCHAR(255), to_addresses JSONB NOT NULL DEFAULT '[]', cc_addresses JSONB DEFAULT '[]', date TIMESTAMP NOT NULL, -- Inhalt (verschlüsselt gespeichert) body_text_encrypted BYTEA, body_html_encrypted BYTEA, has_attachments BOOLEAN DEFAULT false, attachment_info JSONB DEFAULT '[]', -- Status is_read BOOLEAN DEFAULT false, is_flagged BOOLEAN DEFAULT false, is_deleted BOOLEAN DEFAULT false, folder VARCHAR(255) DEFAULT 'INBOX', -- KI-Analyse ai_sender_type VARCHAR(50), ai_sender_organization VARCHAR(255), ai_category VARCHAR(50), ai_priority VARCHAR(20), ai_has_deadline BOOLEAN DEFAULT false, ai_deadline_date TIMESTAMP, ai_deadline_text VARCHAR(500), ai_summary TEXT, ai_action_required BOOLEAN DEFAULT false, ai_suggested_action TEXT, ai_response_suggestions JSONB DEFAULT '[]', ai_analyzed_at TIMESTAMP, ai_confidence FLOAT, -- Metadaten fetched_at TIMESTAMP DEFAULT NOW(), raw_headers JSONB, -- Constraints CONSTRAINT unique_message UNIQUE (account_id, message_id) ); CREATE INDEX idx_ae_account ON aggregated_emails(account_id); CREATE INDEX idx_ae_date ON aggregated_emails(date DESC); CREATE INDEX idx_ae_unread ON aggregated_emails(is_read) WHERE is_read = false; CREATE INDEX idx_ae_deadline ON aggregated_emails(ai_deadline_date) WHERE ai_has_deadline = true; CREATE INDEX idx_ae_category ON aggregated_emails(ai_category); -- Arbeitsvorrat (Tasks) CREATE TABLE inbox_tasks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, -- Quelle source_type VARCHAR(50) NOT NULL DEFAULT 'email', -- email, manual, calendar source_email_id UUID REFERENCES aggregated_emails(id), -- Task-Daten title VARCHAR(500) NOT NULL, description TEXT, category VARCHAR(50), priority VARCHAR(20) DEFAULT 'mittel', -- Frist deadline TIMESTAMP, reminder_at TIMESTAMP, reminder_sent BOOLEAN DEFAULT false, -- Status status VARCHAR(20) DEFAULT 'offen', -- offen, in_bearbeitung, erledigt, delegiert completed_at TIMESTAMP, -- Delegation delegated_to UUID REFERENCES users(id), delegated_at TIMESTAMP, delegation_note TEXT, -- Metadaten created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), created_by VARCHAR(50) DEFAULT 'ai', -- ai, manual -- Ursprungs-Account (für Kontext) account_id UUID REFERENCES external_email_accounts(id) ); CREATE INDEX idx_it_user ON inbox_tasks(user_id); CREATE INDEX idx_it_deadline ON inbox_tasks(deadline); CREATE INDEX idx_it_status ON inbox_tasks(status); CREATE INDEX idx_it_source ON inbox_tasks(source_email_id); -- Absender-Klassifikation (für schnellere Erkennung) CREATE TABLE sender_classifications ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Identifikation email_domain VARCHAR(255) NOT NULL, email_pattern VARCHAR(255), -- z.B. "*@landesschulbehoerde.niedersachsen.de" -- Klassifikation sender_type VARCHAR(50) NOT NULL, organization_name VARCHAR(255), -- Region (für länderspezifische Behörden) bundesland VARCHAR(50), -- Vertrauen confidence FLOAT DEFAULT 1.0, verified BOOLEAN DEFAULT false, verified_by UUID, created_at TIMESTAMP DEFAULT NOW(), CONSTRAINT unique_domain UNIQUE (email_domain) ); -- Seed-Daten für Niedersachsen INSERT INTO sender_classifications (email_domain, sender_type, organization_name, bundesland, verified) VALUES ('nlschb.niedersachsen.de', 'behoerde', 'Landesschulbehörde Niedersachsen', 'niedersachsen', true), ('landesschulbehoerde.niedersachsen.de', 'behoerde', 'Landesschulbehörde Niedersachsen', 'niedersachsen', true), ('mk.niedersachsen.de', 'behoerde', 'Kultusministerium Niedersachsen', 'niedersachsen', true), ('schule.niedersachsen.de', 'behoerde', 'Bildungsportal Niedersachsen', 'niedersachsen', true), ('nibis.de', 'behoerde', 'NiBiS - Niedersächsischer Bildungsserver', 'niedersachsen', true), ('rlsb.de', 'behoerde', 'Regionales Landesamt für Schule und Bildung', 'niedersachsen', true); -- Antwort-Vorlagen CREATE TABLE response_templates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Zuordnung user_id UUID REFERENCES users(id), -- NULL = global sender_type VARCHAR(50), -- Für welchen Absender-Typ category VARCHAR(50), -- Für welche Kategorie -- Vorlage name VARCHAR(255) NOT NULL, subject_template VARCHAR(500), body_template TEXT NOT NULL, -- Platzhalter-Info placeholders JSONB DEFAULT '[]', -- ["{{anrede}}", "{{datum}}"] -- Status is_active BOOLEAN DEFAULT true, usage_count INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT NOW() ); -- Audit-Log für E-Mail-Aktionen CREATE TABLE email_audit_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id), account_id UUID REFERENCES external_email_accounts(id), email_id UUID REFERENCES aggregated_emails(id), action VARCHAR(50) NOT NULL, -- read, sent, deleted, delegated details JSONB, ip_address INET, user_agent VARCHAR(500), created_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_eal_user ON email_audit_log(user_id); CREATE INDEX idx_eal_created ON email_audit_log(created_at); ``` --- ## 5. API-Endpunkte ### 5.1 Accounts API ```yaml /api/v1/unified-inbox/accounts: get: summary: Liste aller verbundenen E-Mail-Konten responses: 200: description: Account-Liste mit Sync-Status post: summary: Neues E-Mail-Konto verbinden requestBody: content: application/json: schema: type: object required: [email_address, imap_host, smtp_host, username, password] properties: email_address: type: string format: email display_name: type: string color: type: string imap_host: type: string imap_port: type: integer default: 993 smtp_host: type: string smtp_port: type: integer default: 587 username: type: string password: type: string format: password responses: 201: description: Account erfolgreich verbunden /api/v1/unified-inbox/accounts/{account_id}: delete: summary: E-Mail-Konto trennen description: Entfernt das Konto und alle gecachten E-Mails responses: 200: description: Account getrennt post: summary: Konto synchronisieren parameters: - name: action in: query schema: type: string enum: [sync, test] responses: 200: description: Sync gestartet oder Test erfolgreich ``` ### 5.2 Inbox API ```yaml /api/v1/unified-inbox/emails: get: summary: Vereinheitlichte Inbox parameters: - name: account_id in: query description: Filter nach Konto (optional) schema: type: string format: uuid - name: category in: query schema: type: string enum: [behoerde, schultraeger, personal, haushalt, paedagogik, sonstige] - name: priority in: query schema: type: string enum: [hoch, mittel, niedrig] - name: has_deadline in: query schema: type: boolean - name: unread_only in: query schema: type: boolean - name: page in: query schema: type: integer default: 1 responses: 200: description: E-Mail-Liste mit KI-Analyse /api/v1/unified-inbox/emails/{email_id}: get: summary: E-Mail-Details responses: 200: description: Vollständige E-Mail mit Analyse patch: summary: E-Mail aktualisieren (lesen, markieren) requestBody: content: application/json: schema: type: object properties: is_read: type: boolean is_flagged: type: boolean /api/v1/unified-inbox/emails/{email_id}/reply: post: summary: Auf E-Mail antworten requestBody: content: application/json: schema: type: object required: [body_text] properties: body_text: type: string body_html: type: string use_suggestion: type: integer description: Index des Antwort-Vorschlags (0-2) ``` ### 5.3 Tasks API ```yaml /api/v1/unified-inbox/tasks: get: summary: Arbeitsvorrat parameters: - name: status in: query schema: type: string enum: [offen, in_bearbeitung, erledigt, delegiert, ueberfaellig] - name: priority in: query schema: type: string - name: deadline_before in: query schema: type: string format: date responses: 200: description: Task-Liste post: summary: Manuelle Aufgabe erstellen requestBody: content: application/json: schema: type: object required: [title] properties: title: type: string description: type: string deadline: type: string format: date-time priority: type: string link_to_email: type: string format: uuid /api/v1/unified-inbox/tasks/{task_id}: patch: summary: Task aktualisieren requestBody: content: application/json: schema: type: object properties: status: type: string enum: [offen, in_bearbeitung, erledigt] priority: type: string deadline: type: string format: date-time /api/v1/unified-inbox/tasks/{task_id}/delegate: post: summary: Task delegieren requestBody: content: application/json: schema: type: object required: [delegate_to] properties: delegate_to: type: string format: uuid note: type: string ``` --- ## 6. Sicherheit ### 6.1 Credential-Management ``` ┌─────────────────────────────────────────────────────────────────┐ │ CREDENTIAL SECURITY │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ NIEMALS IM KLARTEXT: │ │ ├── Passwörter werden direkt nach Eingabe an Vault gesendet │ │ ├── Im Backend nur als Vault-Referenz gespeichert │ │ ├── Credentials werden bei jeder Verbindung neu aus Vault │ │ │ gelesen und sofort nach Nutzung verworfen │ │ └── Audit-Log für jeden Vault-Zugriff │ │ │ │ VAULT-STRUKTUR: │ │ secret/ │ │ └── mail-accounts/ │ │ └── {user_id}/ │ │ └── {email_address}/ │ │ ├── username │ │ ├── password (verschlüsselt) │ │ └── oauth_token (wenn OAuth) │ │ │ │ ROTATION: │ │ ├── OAuth-Tokens: Automatisch via Refresh-Token │ │ └── Passwörter: Bei Änderung durch Nutzer │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 6.2 E-Mail-Inhalt-Verschlüsselung ```python # E-Mail-Inhalte werden at-rest verschlüsselt class EmailEncryption: """ Verschlüsselt E-Mail-Inhalte vor der Speicherung. - Jeder User hat einen eigenen Encryption Key - Keys liegen in Vault, nicht in der DB - Bei User-Löschung: Key vernichten = Daten unlesbar """ async def encrypt_email_content( self, user_id: str, body_text: str, body_html: Optional[str] ) -> Tuple[bytes, Optional[bytes]]: # User-spezifischen Key aus Vault holen key = await self.vault.read(f"secret/user-keys/{user_id}/email-key") # Verschlüsseln encrypted_text = self._encrypt(body_text, key) encrypted_html = self._encrypt(body_html, key) if body_html else None return encrypted_text, encrypted_html async def decrypt_email_content( self, user_id: str, encrypted_text: bytes, encrypted_html: Optional[bytes] ) -> Tuple[str, Optional[str]]: key = await self.vault.read(f"secret/user-keys/{user_id}/email-key") text = self._decrypt(encrypted_text, key) html = self._decrypt(encrypted_html, key) if encrypted_html else None return text, html ``` --- ## 7. BreakPilot GmbH Interne Nutzung ### 7.1 Dual-Use-Konzept ``` ┌─────────────────────────────────────────────────────────────────┐ │ DUAL-USE ARCHITEKTUR │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ BREAKPILOT GMBH (Intern) │ │ ├── Eigene Domain: @breakpilot.de │ │ ├── Eigener Mail-Server (Stalwart) │ │ ├── Funktionale Mailboxen für Rollen │ │ │ ├── support@breakpilot.de │ │ │ ├── sales@breakpilot.de │ │ │ ├── datenschutz@breakpilot.de │ │ │ └── buchhaltung@breakpilot.de │ │ ├── Mitarbeiter-Anonymisierung bei Ausscheiden │ │ └── Volle Matrix/Jitsi/Kalender-Integration │ │ │ │ SCHULEN (Kunden) │ │ ├── Option A: Unified Inbox │ │ │ └── Aggregiert externe Konten (IMAP) │ │ │ ├── schulleitung@grundschule-xy.de │ │ │ ├── vorname.nachname@schule.nds.de │ │ │ └── weitere... │ │ │ │ │ ├── Option B: Gehostete Mailboxen │ │ │ └── @schule.breakpilot.app │ │ │ ├── schulleitung@grundschule.breakpilot.app │ │ │ ├── sekretariat@grundschule.breakpilot.app │ │ │ └── weitere... │ │ │ │ │ └── Option C: Hybrid │ │ ├── Gehostete BreakPilot-Mailbox für Alltag │ │ └── + Aggregation externer Pflicht-Konten │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 7.2 Internes Dogfooding ``` BreakPilot GmbH nutzt das System selbst: 1. Alle Mitarbeiter haben @breakpilot.de Mailboxen 2. Funktionale Adressen für Abteilungen 3. Vollständige KI-Assistenz im Alltag 4. Beweis der Funktionalität für Kunden 5. Kontinuierliche Verbesserung durch eigene Nutzung ``` --- ## 8. Roadmap ### Phase 1: Foundation (6-8 Wochen) - [ ] Mail Aggregator Service (IMAP/SMTP) - [ ] Account-Management (hinzufügen, entfernen, sync) - [ ] Unified Inbox UI (Basic) - [ ] Credential-Management (Vault) - [ ] BreakPilot interne Nutzung ### Phase 2: KI-Integration (4-6 Wochen) - [ ] Absender-Erkennung (regelbasiert + LLM) - [ ] Fristen-Extraktion - [ ] Kategorisierung - [ ] Prioritäts-Scoring - [ ] Niedersachsen-spezifische Behörden-DB ### Phase 3: Arbeitsvorrat (3-4 Wochen) - [ ] Task-Extraktion aus E-Mails - [ ] Deadline-Tracking - [ ] Erinnerungen (E-Mail, Push) - [ ] Kalender-Integration - [ ] Delegation ### Phase 4: Antwort-Assistenz (3-4 Wochen) - [ ] Antwort-Vorschläge (LLM) - [ ] Vorlagen-Management - [ ] Ein-Klick-Antworten - [ ] Signatur-Management ### Phase 5: Integrationen (4-6 Wochen) - [ ] Jitsi-Integration (Meetings aus E-Mail) - [ ] Matrix-Integration (Chat-Verknüpfung) - [ ] CalDAV-Sync - [ ] Mobile PWA ### Phase 6: Erweiterung (Ongoing) - [ ] Weitere Bundesländer - [ ] OAuth-Support (Microsoft 365, Google) - [ ] Exchange Web Services - [ ] Desktop-App (Electron) --- ## 9. Referenzdokumente Diese Spezifikation ergänzt: 1. **Mail-RBAC Architektur:** `/docs/architecture/mail-rbac-architecture.md` 2. **Mail-RBAC Developer Spec:** `/docs/klausur-modul/MAIL-RBAC-DEVELOPER-SPECIFICATION.md` 3. **DSGVO-Konzept:** `/docs/architecture/dsgvo-compliance.md` --- ## Anhang: Niedersachsen-spezifische Informationen ### Behörden und ihre Domains | Behörde | Domain(s) | Typische Inhalte | |---------|-----------|------------------| | Landesschulbehörde | nlschb.niedersachsen.de | Statistiken, Personal, Genehmigungen | | Kultusministerium | mk.niedersachsen.de | Erlasse, Verordnungen | | Bildungsportal | schule.niedersachsen.de | Account-Info, Fortbildungen | | NiBiS | nibis.de | Curricula, Materialien | | RLSB | rlsb.de | Regionale Verwaltung | ### Typische Fristen | Anlass | Übliche Frist | Häufigkeit | |--------|---------------|------------| | Schülerzahlen-Statistik | September/März | 2x jährlich | | Haushaltsentwurf | November | 1x jährlich | | Prüfungstermine | Dezember/Januar | 1x jährlich | | Personalplanung | Februar | 1x jährlich | | Fortbildungsanträge | Laufend | Variabel |