feat: SMTP-Stack mit konfigurierbaren Mailvorlagen

Bisher gab es nur zwei fest im Code verdrahtete E-Mails (Einladung,
Passwort-Reset) und die SMTP-Verbindung kam ausschließlich aus
Umgebungsvariablen. Jetzt:

- SMTP-Verbindung unter Einstellungen > E-Mail konfigurierbar (Host,
  Port, Verschlüsselung, Zugangsdaten verschlüsselt gespeichert,
  Absender), mit Testmail-Versand. Die Mail-Logik wandert dafür nach
  @kc/platform/mail, damit API und Worker sie gemeinsam nutzen.
- Mailvorlagen-Verwaltung: jedes Ereignis hat eine feste "Definition"
  (Name, Auslöser, verfügbare Platzhalter/Daten), Betreff/Text sind
  editierbar, je Vorlage einzeln aktivierbar, mit Testversand anhand
  von Beispieldaten. Unbekannte Platzhalter werden beim Speichern
  abgelehnt.
- Neue Ereignisse verdrahtet: Ticket erstellt (Bestätigung an Kunde),
  Personal antwortet auf Ticket (Benachrichtigung an Kunde), Rechnung
  ausgestellt, sowie die zuvor zurückgestellte Funktion "Panel-
  Zugangsdaten per E-Mail senden" nach einem Passwort-Reset. Diese
  laufen über die Jobqueue (mail.template), analog zu discord.notify.
- Versandprotokoll (letzte 100 Versuche, ohne Inhalte) einsehbar unter
  Einstellungen > E-Mail.
- Migration 022, Rechte email.read (Admin+) / email.write (Superadmin).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kundencenter 2026-09-28 12:53:29 +02:00
parent b112647ff1
commit a68b4cf6ec
18 changed files with 385 additions and 42 deletions

View file

@ -1,6 +1,7 @@
import { pool } from '@kc/platform/db';
import { notifyAdmin } from './discord.js';
import { JobFailure } from '@kc/platform/jobs';
import { sendTemplateMail } from '@kc/platform/mail';
import { executeAction, executeChild, provisionOrder, syncInstance, type ChildPayload, type ExecutePayload } from '@kc/connectors';
interface JobMeta { id: string; correlationId: string; attempt: number; maxAttempts: number }
@ -19,6 +20,12 @@ const handlers: Record<string, Handler> = {
const r = await notifyAdmin(text);
if (r === 'skipped') return 'übersprungen: Discord nicht konfiguriert';
},
'mail.template': async (p: { to: string; key: string; vars: Record<string, string> }) => {
const status = await sendTemplateMail(p.to, p.key, p.vars);
if (status === 'failed') throw new Error('Mailversand fehlgeschlagen (siehe Protokoll unter Einstellungen > E-Mail)');
if (status === 'no_template') throw new Error(`Unbekannte Mailvorlage: ${p.key}`);
return status === 'not_configured' ? 'übersprungen: SMTP nicht konfiguriert' : status === 'disabled' ? 'übersprungen: Vorlage deaktiviert' : 'gesendet';
},
};
handlers['connector.sync'] = async (p, j) => {
const r = await syncInstance(p.instanceId, j.correlationId);