E-Mail-Benachrichtigung für neue Anträge (BL/Partei-Filter) #124

Closed
opened 2026-04-11 21:15:18 +02:00 by tobias · 2 comments
Owner

Kontext

Aus #102 (dotty 2026-04-11): "E-Mail-Benachrichtigung bei neuen Antraegen eines BL / einer Partei — bei registrierten Nutzern haben wir ja eh eine Mailadresse. Bau das."

Registrierte Nutzer sollen eine tägliche oder event-basierte E-Mail bekommen, wenn neue Anträge zu ihrem Filter bewertet wurden.

Arbeitsschritte

  • Neue Tabelle email_subscriptions(user_id TEXT, bundesland TEXT NULL, partei TEXT NULL, frequency TEXT, last_sent TIMESTAMP) — beides NULL = alle
  • API: POST /api/subscriptions (Abo anlegen), GET /api/subscriptions, DELETE /api/subscriptions/{id}
  • UI: Abo-Management im Nutzer-Menü
  • Cron/Scheduler: täglicher Job, der neue Anträge seit last_sent pro Abo sammelt und Mail rendert
  • Mail-Template (HTML + Plaintext) mit Liste neuer Bewertungen, Link zur Webapp
  • SMTP-Config aus bestehender Keycloak-Mail-Konfig wiederverwenden (1blu, smtp.1blu.de:465)
  • Unsubscribe-Link pro Mail (Token-basiert)
  • Rate-Limit: max 1 Mail pro User pro Tag (Frequency=daily)

Akzeptanzkriterien

  • User kann Abo via UI anlegen/löschen
  • Täglicher Job schickt Zusammenfassung neuer Anträge
  • Unsubscribe funktioniert ohne Login
  • DSGVO-konform (Abo ist opt-in, Unsubscribe in jeder Mail)
## Kontext Aus #102 (dotty 2026-04-11): *"E-Mail-Benachrichtigung bei neuen Antraegen eines BL / einer Partei — bei registrierten Nutzern haben wir ja eh eine Mailadresse. Bau das."* Registrierte Nutzer sollen eine tägliche oder event-basierte E-Mail bekommen, wenn neue Anträge zu ihrem Filter bewertet wurden. ## Arbeitsschritte - [ ] Neue Tabelle `email_subscriptions(user_id TEXT, bundesland TEXT NULL, partei TEXT NULL, frequency TEXT, last_sent TIMESTAMP)` — beides NULL = alle - [ ] API: `POST /api/subscriptions` (Abo anlegen), `GET /api/subscriptions`, `DELETE /api/subscriptions/{id}` - [ ] UI: Abo-Management im Nutzer-Menü - [ ] Cron/Scheduler: täglicher Job, der neue Anträge seit `last_sent` pro Abo sammelt und Mail rendert - [ ] Mail-Template (HTML + Plaintext) mit Liste neuer Bewertungen, Link zur Webapp - [ ] SMTP-Config aus bestehender Keycloak-Mail-Konfig wiederverwenden (1blu, smtp.1blu.de:465) - [ ] Unsubscribe-Link pro Mail (Token-basiert) - [ ] Rate-Limit: max 1 Mail pro User pro Tag (Frequency=daily) ## Akzeptanzkriterien - User kann Abo via UI anlegen/löschen - Täglicher Job schickt Zusammenfassung neuer Anträge - Unsubscribe funktioniert ohne Login - DSGVO-konform (Abo ist opt-in, Unsubscribe in jeder Mail)
Author
Owner

Stand 2026-04-11 Abend — Fundament deployed, wartet auf SMTP-Credentials

Deployed:

  • email_subscriptions Tabelle (DB-Migration automatisch beim Start)
  • app/mail.py — Mail-Sending via stdlib smtplib.SMTP_SSL, Daily-Digest-Runner, HMAC-Unsubscribe-Token
  • API: POST /api/subscriptions (require_auth), GET /api/subscriptions, DELETE /api/subscriptions/{id}, GET /unsubscribe/{id}/{token} (public, HMAC-validiert)
  • UI: 📧 E-Mail-Abos im Hamburger-Menü (nur für eingeloggte Nutzer sichtbar), Modal mit Liste + Anlage-Formular
  • scripts/run-digest.sh für host-cron

⚠️ Wartet auf menschliche Aktion — SMTP-Credentials + Secret in .env

Bitte auf dem Server /opt/gwoe-antragspruefer/.env ergänzen:

# 1blu SMTP (gleiche Credentials wie Keycloak-Mail-Setup)
SMTP_HOST=smtp.1blu.de
SMTP_PORT=465
SMTP_USER=q294440_0-gwoe-toppyr
SMTP_PASSWORD=<dein_1blu_passwort>
SMTP_FROM_EMAIL=noreply@toppyr.de
SMTP_FROM_NAME=GWÖ-Antragsprüfer

# HMAC-Secret für Unsubscribe-Links (Claude hat einen generiert)
UNSUBSCRIBE_SECRET=HZ92pxXutDragrmbXv6aYX-J2Pv_sy1Er2M-OHLwTc0

BASE_URL=https://gwoe.toppyr.de

Danach:

cd /opt/gwoe-antragspruefer && docker compose up -d

⚠️ Wartet auf menschliche Aktion — Host-Cron installieren

crontab -e
# Zeile hinzufügen:
0 7 * * *  /opt/gwoe-antragspruefer/scripts/run-digest.sh >> /var/log/gwoe-digest.log 2>&1

Dann läuft jeden Morgen um 07:00 der Digest.

Manueller Test vor Cron-Install:

docker exec gwoe-antragspruefer python -m app.mail
# → sollte "keine due subscriptions" sagen bis jemand ein Abo angelegt hat

Offene Punkte in dieser Issue:

  • SMTP-Credentials in .env (human)
  • UNSUBSCRIBE_SECRET in .env (human)
  • Host-Cron installieren (human)
  • End-to-End-Test: Abo anlegen → 24h warten → Mail-Eingang prüfen
  • Optional: Webhook-basierter "sofort"-Digest bei neuen Anträgen (statt nur daily)
## Stand 2026-04-11 Abend — Fundament deployed, wartet auf SMTP-Credentials **✅ Deployed:** - `email_subscriptions` Tabelle (DB-Migration automatisch beim Start) - `app/mail.py` — Mail-Sending via stdlib `smtplib.SMTP_SSL`, Daily-Digest-Runner, HMAC-Unsubscribe-Token - API: `POST /api/subscriptions` (require_auth), `GET /api/subscriptions`, `DELETE /api/subscriptions/{id}`, `GET /unsubscribe/{id}/{token}` (public, HMAC-validiert) - UI: 📧 E-Mail-Abos im Hamburger-Menü (nur für eingeloggte Nutzer sichtbar), Modal mit Liste + Anlage-Formular - `scripts/run-digest.sh` für host-cron **⚠️ Wartet auf menschliche Aktion — SMTP-Credentials + Secret in .env** Bitte auf dem Server `/opt/gwoe-antragspruefer/.env` ergänzen: ```bash # 1blu SMTP (gleiche Credentials wie Keycloak-Mail-Setup) SMTP_HOST=smtp.1blu.de SMTP_PORT=465 SMTP_USER=q294440_0-gwoe-toppyr SMTP_PASSWORD=<dein_1blu_passwort> SMTP_FROM_EMAIL=noreply@toppyr.de SMTP_FROM_NAME=GWÖ-Antragsprüfer # HMAC-Secret für Unsubscribe-Links (Claude hat einen generiert) UNSUBSCRIBE_SECRET=HZ92pxXutDragrmbXv6aYX-J2Pv_sy1Er2M-OHLwTc0 BASE_URL=https://gwoe.toppyr.de ``` Danach: ```bash cd /opt/gwoe-antragspruefer && docker compose up -d ``` **⚠️ Wartet auf menschliche Aktion — Host-Cron installieren** ```bash crontab -e # Zeile hinzufügen: 0 7 * * * /opt/gwoe-antragspruefer/scripts/run-digest.sh >> /var/log/gwoe-digest.log 2>&1 ``` Dann läuft jeden Morgen um 07:00 der Digest. **Manueller Test vor Cron-Install:** ```bash docker exec gwoe-antragspruefer python -m app.mail # → sollte "keine due subscriptions" sagen bis jemand ein Abo angelegt hat ``` --- **Offene Punkte in dieser Issue:** - [ ] SMTP-Credentials in .env (human) - [ ] UNSUBSCRIBE_SECRET in .env (human) - [ ] Host-Cron installieren (human) - [ ] End-to-End-Test: Abo anlegen → 24h warten → Mail-Eingang prüfen - [ ] Optional: Webhook-basierter "sofort"-Digest bei neuen Anträgen (statt nur daily)
Author
Owner

Cron installiert, läuft täglich 07:00:

0 7 * * * /opt/gwoe-antragspruefer/scripts/run-digest.sh >> /var/log/gwoe-digest.log 2>&1
  • SMTP-TCP bei 1blu wieder offen (2026-04-20 ~00:30)
  • SMTP.login() OK gegen smtp.1blu.de:465 mit Keycloak-Credentials
  • Dry-Run des Scripts läuft sauber durch (keine due subscriptions, weil noch keine Abos existieren)
  • Logfile: /var/log/gwoe-digest.log

Akzeptanzkriterium: User kann jetzt Abo anlegen → bekommt am nächsten 7:00 die erste Digest-Mail.

Schließe, Restposten (UI-Flow für Abo-Anlage) ist bereits live laut letzter Session.

Cron installiert, läuft täglich 07:00: ``` 0 7 * * * /opt/gwoe-antragspruefer/scripts/run-digest.sh >> /var/log/gwoe-digest.log 2>&1 ``` - SMTP-TCP bei 1blu wieder offen (2026-04-20 ~00:30) - `SMTP.login()` OK gegen `smtp.1blu.de:465` mit Keycloak-Credentials - Dry-Run des Scripts läuft sauber durch (keine due subscriptions, weil noch keine Abos existieren) - Logfile: `/var/log/gwoe-digest.log` Akzeptanzkriterium: User kann jetzt Abo anlegen → bekommt am nächsten 7:00 die erste Digest-Mail. Schließe, Restposten (UI-Flow für Abo-Anlage) ist bereits live laut letzter Session.
Sign in to join this conversation.
No description provided.