feat(P2): Notruf verschickt E-Mail an Kontaktperson (Testphase)

Beim Notruf-Knopf wird – sofern SMTP konfiguriert ist – eine E-Mail an die
Kontaktperson(en) des Nutzers geschickt (Default: dieter.schlueter@linix.de).
Der Nutzer-Hinweis spiegelt ehrlich wider, ob wirklich etwas rausging:
"Kontaktperson per E-Mail benachrichtigt" vs. "noch keine Benachrichtigung".

- config.py: EMERGENCY_CONTACT_EMAIL + SMTP_* (ohne SMTP_HOST kein Versand);
  veralteten LLM-Klassifikator-Kommentar entfernt.
- emergency.py: SMTP-Versand (best effort, im Thread), lokalisierte Hinweise
  für "gesendet"/"nicht gesendet" (10 Sprachen). Pro-Nutzer-Kontakte via
  prefs.emergency_contacts vorbereitet, Fallback auf Config-Default.
- Test mit gemocktem smtplib. env.example + DEPLOYMENT.md 2.5 dokumentiert.

SMS/Anruf-Eskalation folgt später (eigene Planung). 210 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-25 03:37:18 +02:00
commit c35fe6e187
6 changed files with 141 additions and 20 deletions

View file

@ -188,10 +188,16 @@ class Settings(BaseSettings):
llm_fallback: str = ""
tts_fallback: str = ""
daily_request_limit: int = 0 # 0 = unbegrenzt; Anfragen pro Nutzer pro Tag
emergency_webhook_url: str = "" # optionaler Eskalations-Webhook
# LLM-Notfall-Klassifikation (zweite Stufe, faengt was die Stichwoerter verpassen).
# Laeuft als Hintergrund-Task NUR wenn der Keyword-Filter nichts fand -> keine
# zusaetzliche Antwortlatenz. Leerer Provider = Default-LLM-Provider.
emergency_webhook_url: str = "" # optionaler Eskalations-Webhook (später)
# Notruf-Benachrichtigung per E-Mail (Testphase, bis SMS/Anruf gebaut sind).
emergency_contact_email: str = "dieter.schlueter@linix.de" # Default-Kontaktperson
smtp_host: str = "" # ohne Host wird KEINE E-Mail versendet
smtp_port: int = 587
smtp_user: str = ""
smtp_password: str = ""
smtp_from: str = "" # Absenderadresse; leer -> smtp_user
smtp_starttls: bool = True
model_config = SettingsConfigDict(
env_file=ENV_FILE, case_sensitive=False, extra="ignore"
)