Zum Hauptinhalt springen

Dashboard-E-Mails (cockpitOS)

Was

Alle Dashboard-Benutzer-E-Mails laufen über Resend mit einheitlichem SaaS-Layout. Keine Passwörter im Klartext — nur Einmal-Links für Einrichtung und Reset. Center-Website-Formulare (Kontakt, Vermietung) sind separate Templates in email.ts und unverändert.

Warum

Professioneller SaaS-Standard: Setup- und Reset-Links statt Passwort per E-Mail (Risiko durch Postfach-Zugriff, Weiterleitung, Logs, Support-Screenshots).

Wer ist betroffen

GruppeInhalt
Redaktion / AdminsWillkommen, Einladung, Rollenänderung, Account-Status, Admin-Reset
Neue NutzerAccount einrichten über /auth/activate
EntwicklerTemplates, API-Hooks, Token-Logik
Betrieb / ITRESEND_API_KEY, NEXTAUTH_URL, Absender-Domain
Besuchernicht betroffen (Center-Mails separat)

Nutzer-Doku (Bedienung im Dashboard): Benutzer und Organisation.

Wo

Code

BereichPfad
Layout & Copyrightapps/dashboard/src/lib/email-layout.ts
Templates (15 Dashboard-Typen)apps/dashboard/src/lib/dashboard-email-templates.ts
Digest-Cron-Logikapps/dashboard/src/lib/email-digest.ts
Versand-Helferapps/dashboard/src/lib/email.ts
Token, URLs, Platzhalter-PWapps/dashboard/src/lib/user-account-tokens.ts
User-Onboarding-Flowsapps/dashboard/src/lib/user-account-email.ts (sendSelfServicePasswordResetEmail)
Auth-Footer (Copyright + Version)apps/dashboard/src/components/auth/cockpit-auth-footer.tsx, apps/dashboard/src/lib/cockpit-meta.ts, apps/dashboard/VERSION
UI Account einrichten / Resetapps/dashboard/src/components/auth/account-password-form.tsx
Testskriptscripts/test-dashboard-emails.tspnpm test:dashboard-emails

URLs (Dashboard-App)

PfadZweck
/auth/activate?token=…Neuer Account / Einladung — Passwort festlegen
/auth/reset-password?token=…Passwort vergessen oder Admin-Reset
/auth/unlock?token=…Account nach Login-Lockout entsperren
/auth/signin?message=account-activatedErfolg nach Einrichtung
/auth/signin?message=password-reset-successErfolg nach Reset

API (Auslöser)

EndpointE-Mail
POST /api/usersWillkommen oder Einladung (invite: true)
POST /api/users/{userId}/reset-passwordAdmin-Reset-Link
PUT /api/users/{userId} (newPassword)Admin-Reset-Link (kein direktes Setzen mehr)
PUT /api/users/{userId} (Rolle / isActive)Rolle / Deaktiviert / Reaktiviert
DELETE /api/users/{userId}Account gelöscht
POST /api/auth/forgot-passwordPasswort vergessen (Self-Service; gleiche Token-/Resend-Pipeline wie Admin-Reset)
POST /api/auth/reset-passwordBestätigung Passwort geändert (nach Token-Reset)
POST /api/users/me/change-passwordBestätigung Passwort geändert (Self-Service)
Social-WorkflowSOCIAL_REVIEW (abgelehnt / Publish-Fehler sofort; freigegeben nur sofort wenn Digest aus)
POST /api/cron/email-digestEMAIL_DIGEST (täglich, opt-in, nur bei Inhalt)
GET/PATCH /api/users/me/notification-preferencesDigest ein/aus + Versandstunde

Token-Speicher: User.resetToken, User.resetTokenExpiry, User.accountTokenPurpose (setup | reset | unlock). Lockout: failedLoginAttempts, lockedUntil. Migration: packages/database/migrations/20260606120000_add_user_account_security_SAFE.sql (additiv).

Templates (vollständig)

IDBetreff-KontextLink / Gültigkeit
PASSWORD_RESETPasswort vergessen/auth/reset-password24 h
WELCOMEUser angelegt/auth/activate (purpose=setup) — 7 Tage, auch bei Inaktiv
USER_INVITATIONEinladung (invite: true)/auth/activate (purpose=setup) — 7 Tage
PASSWORD_CHANGEDNach erfolgreichem Reset
ACCOUNT_LOCKEDNach 5 Fehlversuchen (Login)/auth/unlock (24 h)
ROLE_CHANGERolle geändert
PASSWORD_RESET_BY_ADMINAdmin-Reset/auth/reset-password24 h
ACCOUNT_DEACTIVATEDAccount deaktiviert
ACCOUNT_REACTIVATEDAccount reaktiviertLink zur Anmeldung
ACCOUNT_DELETEDAccount gelöscht
SOCIAL_REVIEWSocial-Freigabe-WorkflowDashboard-URLs zum Post
EMAIL_DIGESTTägliche Redaktions-ZusammenfassungEinstellungen + Dashboard

Redaktions-Digest

AspektVerhalten
Opt-inUser.emailDigestEnabled (Default false)
ZeitzoneEurope/Berlin, Stunde emailDigestHour (7, 8, 9, 10, 12, 15, 18)
FrequenzCron stündlich; Versand nur in gewählter Stunde, max. 1×/Tag (emailDigestLastSentAt)
InhaltGlocke (social_review_approved, social_new_comment), Zähler offene Social-Freigaben + Workflow-Entwürfe
Keine Duplikaterejected / publish_failed → immer Sofort-Mail; approved → Digest wenn aktiv, sonst Sofort
LeerKein Versand wenn nichts ansteht
CronPOST /api/cron/email-digest — Auth Bearer CRON_SECRET; Query ?dryRun=1, ?userId=
Migrationpackages/database/migrations/20260606140000_add_email_digest_prefs_SAFE.sql

Login: Dashboard = E-Mail + Passwort (NextAuth Credentials). Kein Magic-Link-Login im Dashboard. Magic Links existieren nur für AgencyOS / WordPress-Integration.

Copyright: Footer © {Jahr} SawatzkiMühlenbruch GmbH (Platzhalter im Template, zur Laufzeit EMAIL_BRAND.year); Produktname im Header: cockpitOS.

So testen

  1. RESEND_API_KEY in apps/dashboard/.env (oder .env.local)
  2. pnpm test:dashboard-emails — sendet 14 Templates an TEST_EMAIL_RECIPIENT (Default: sb@schickma.de), Betreff-Prefix [TEST]
  3. Im Dashboard: Test-User unter Einstellungen → Benutzer & Rollen anlegen → Willkommens-Mail prüfen
  4. Link Account einrichten öffnen → Passwort setzen → Anmeldung mit Hinweis
  5. Passwort zurücksetzen in der User-Liste → 24h-Link, kein Klartext
  6. Optional: Anlegen mit Als Einladung versenden → Einladungs-Template
  7. Statische Verdrahtung (ohne DB/Versand): node scripts/verify-dashboard-email-wiring.mjs
  8. Digest dry-run: pnpm test:email-digest — optional DIGEST_USER_ID=…; Versand: DIGEST_USER_ID=… pnpm test:email-digest -- --send
  9. Cron-Test remote: pnpm test:email-digest -- --cron (benötigt CRON_SECRET)

Betrieb

VariableZweck
RESEND_API_KEYResend API (Pflicht für Versand)
NEXTAUTH_URLBasis-URL für alle Links (z. B. https://dashboard.cockpit-os.de)
RESEND_FROM_EMAILAbsender-Adresse (Default: noreply@mail.cockpit-os.de)
RESEND_FROM_NAMEAnzeigename (Default: cockpitOS)
CRON_SECRETDigest-Cron + andere Scheduled Jobs (GitHub Actions → Dashboard)

Digest-Workflow: .github/workflows/email-digest.yml — Details: Cron-Setup.

Reply-To in Code: support@cockpit-os.de.

Domain und DNS für Resend müssen beim Betrieb freigeschaltet sein; ohne gültigen Key schlagen Versand und Testskript fehl.

Tonfall

  • Keine dekorativen Emojis in Betreff, Body oder Buttons — wirkt unprofessionell und „KI-generiert“.
  • Sachliche Anrede, klare Handlungsaufforderung (Link-Button), neutraler Hinweis bei Fristen.
  • UI-Feedback im Dashboard ebenfalls ohne Emoji (Toasts, Alerts) — siehe Projektregel .cursor/rules/no-decorative-emojis.mdc.

Abgrenzung

BereichTemplatesDatei
Dashboard-Benutzerdashboard-email-templates.tsZentrales Layout
Center Kontakt / VermietungInline in email.tsUnverändert, kein SaaS-Layout-Refactor

Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /developer-guide/dashboard-emails