A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1556 lines
68 KiB
Markdown
1556 lines
68 KiB
Markdown
# 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 |
|