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:
parent
b112647ff1
commit
a68b4cf6ec
18 changed files with 385 additions and 42 deletions
|
|
@ -3,7 +3,7 @@ import type { PoolConnection } from 'mysql2/promise';
|
|||
import { run } from './db.js';
|
||||
import { randomToken, sha256 } from './crypto.js';
|
||||
import { config } from './config.js';
|
||||
import { sendMail } from './mail.js';
|
||||
import { sendTemplateMail } from './mail.js';
|
||||
|
||||
export const INVITE_DAYS = 7;
|
||||
|
||||
|
|
@ -23,5 +23,5 @@ export const inviteLink = (token: string): string => `${config.baseUrl}/einladun
|
|||
export const resetLink = (token: string): string => `${config.baseUrl}/passwort-reset?token=${encodeURIComponent(token)}`;
|
||||
|
||||
export async function mailInvite(email: string, name: string, token: string): Promise<string> {
|
||||
return sendMail(email, 'invite', 'Ihr Zugang zum Kundencenter', `Hallo ${name},\n\nfür Sie wurde ein Zugang eingerichtet. Bitte legen Sie hier Ihr Passwort fest (${INVITE_DAYS} Tage gültig):\n${inviteLink(token)}\n`);
|
||||
return sendTemplateMail(email, 'invite', { name, inviteLink: inviteLink(token), validDays: String(INVITE_DAYS) });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1 @@
|
|||
import nodemailer from 'nodemailer';
|
||||
import { config } from './config.js';
|
||||
import { run } from './db.js';
|
||||
|
||||
const transport = config.smtp
|
||||
? nodemailer.createTransport({ host: config.smtp.host, port: config.smtp.port, secure: config.smtp.port === 465, auth: config.smtp.user ? { user: config.smtp.user, pass: config.smtp.pass } : undefined })
|
||||
: null;
|
||||
|
||||
/** Versand mit Protokoll. Ohne SMTP wird nur "not_configured" protokolliert (Inhalt bewusst nicht gespeichert). */
|
||||
export async function sendMail(to: string, template: string, subject: string, text: string): Promise<'sent' | 'failed' | 'not_configured'> {
|
||||
if (!transport || !config.smtp) {
|
||||
await run('INSERT INTO mail_log (to_email, template, status) VALUES (?,?,?)', [to, template, 'not_configured']);
|
||||
return 'not_configured';
|
||||
}
|
||||
try {
|
||||
await transport.sendMail({ from: config.smtp.from, to, subject, text });
|
||||
await run('INSERT INTO mail_log (to_email, template, status) VALUES (?,?,?)', [to, template, 'sent']);
|
||||
return 'sent';
|
||||
} catch (e) {
|
||||
await run('INSERT INTO mail_log (to_email, template, status, error) VALUES (?,?,?,?)', [to, template, 'failed', String((e as Error).message).slice(0, 300)]);
|
||||
return 'failed';
|
||||
}
|
||||
}
|
||||
export * from '@kc/platform/mail';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue