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>
68 KiB
68 KiB
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
# 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
# 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
-- ============================================================
-- 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
/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
/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
/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
# 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:
- Mail-RBAC Architektur:
/docs/architecture/mail-rbac-architecture.md - Mail-RBAC Developer Spec:
/docs/klausur-modul/MAIL-RBAC-DEVELOPER-SPECIFICATION.md - 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 |