Automatische Mails im neuen Design (HTML mit Textfassung)
Alle 22 automatischen Mails gehen jetzt als HTML nach den Vorlagen aus
server/mail-templates raus, dazu eine aus dem HTML erzeugte Textfassung
(multipart/alternative). Werte werden HTML-sicher eingesetzt, optionale
Blöcke (WENN/ENDE) je nach Fall ein- oder ausgeblendet.
- Neu: services/mailTemplates.js (Vorlagen lesen, Platzhalter, Blöcke,
Textfassung); mailer.js nimmt { template, values, blocks }.
- K11 "Passwort geändert" war nicht im Design-Export: aus den Bausteinen
von K02 zusammengesetzt.
- T09 (Widerruf, intern): Fälle "kein Abo gefunden" und "Stripe-Kündigung
fehlgeschlagen" ergänzt; bisher stand dort immer "auf Kündigung gesetzt".
- Einheitlich: Datum TT.MM.JJJJ, Beträge deutsch (T04 "50,00 €"),
"WEBklar" statt "Webklar", Anrede mit Kundennamen, Team-Absender
"WEBklar Kundenportal", Betreff T05 "Kündigung (Stripe)".
- Logo für die Mails: public/webklar-logo.png (192 px, aus dem Design-SVG).
Tests: 52 neue (jede Vorlage: Kopf, Platzhalter, Blöcke, Rendern,
Escaping, Textfassung), npm test 97/97 auf Node 20.19.5; alle 40 Auslöser
einmal durch den Server geschickt (Appwrite-Mock, Stripe-Double,
SMTP-Falle mit STARTTLS).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e3daffffa3
commit
18c3cdf3e5
11 files changed
+657
-316
No files matched your search
+42
-21
@@ -1,5 +1,10 @@
|
||||
import nodemailer from 'nodemailer'
|
||||
import { config } from '../config.js'
|
||||
import { renderMail } from './mailTemplates.js'
|
||||
|
||||
// Absendernamen wie im Design: Kundenmails "WEBklar", Hinweise ans Team "WEBklar Kundenportal"
|
||||
const CUSTOMER_SENDER = 'WEBklar'
|
||||
const TEAM_SENDER = 'WEBklar Kundenportal'
|
||||
|
||||
let transporter = null
|
||||
|
||||
@@ -18,6 +23,13 @@ function getTransporter() {
|
||||
return transporter
|
||||
}
|
||||
|
||||
/** Vorlage rendern (server/mail-templates); Unstimmigkeiten landen im Log, die Mail geht trotzdem raus. */
|
||||
function render({ template, values = {}, blocks = {} }) {
|
||||
const mail = renderMail(template, values, blocks)
|
||||
for (const problem of mail.problems) console.warn(`[mailer] Vorlage ${template}: ${problem}`)
|
||||
return mail
|
||||
}
|
||||
|
||||
// Throttle: pro Kunde maximal eine Benachrichtigung je Zeitfenster,
|
||||
// damit ein aktiver Chat nicht pro Nachricht eine Mail ausloest.
|
||||
const lastNotifyByCustomer = new Map() // customerId -> timestamp
|
||||
@@ -37,18 +49,16 @@ export async function sendChatNotification(customer, text) {
|
||||
if (now - last < throttleMs) return { sent: false, reason: 'throttled' }
|
||||
lastNotifyByCustomer.set(customer.$id, now)
|
||||
|
||||
const name = customer.name || customer.email || 'Kunde'
|
||||
const mail = render({
|
||||
template: 'T01',
|
||||
values: { kunde_name: customer.name || customer.email || 'Kunde', nachricht: text },
|
||||
})
|
||||
await mailer.sendMail({
|
||||
from: `"WEBklar Kundenbereich" <${config.smtp.from}>`,
|
||||
from: `"${TEAM_SENDER}" <${config.smtp.from}>`,
|
||||
to: config.chatNotify.emails.join(', '),
|
||||
subject: `Neue Portalnachricht von ${name}`,
|
||||
text: [
|
||||
`${name} hat im Kundenportal geschrieben:`,
|
||||
'',
|
||||
text,
|
||||
'',
|
||||
'Antworten: https://project.webklar.com/admin.html (Tab Chat)',
|
||||
].join('\n'),
|
||||
subject: mail.subject,
|
||||
text: mail.text,
|
||||
html: mail.html,
|
||||
})
|
||||
return { sent: true }
|
||||
} catch (err) {
|
||||
@@ -61,16 +71,22 @@ export async function sendChatNotification(customer, text) {
|
||||
// Wartungs-Abo: Vertrags-/Kuendigungs-/Widerrufsbestaetigungen
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Mail an die Webklar-Admins (Abo-Ereignisse). Fehler werden nur geloggt. */
|
||||
export async function sendAdminNotice(subject, lines) {
|
||||
/**
|
||||
* Hinweis an das WEBklar-Team (Abo-Ereignisse) aus einer T-Vorlage; der Betreff steht in der Vorlage.
|
||||
* Fehler werden nur geloggt.
|
||||
* @param {{ template: string, values?: object, blocks?: object }} message
|
||||
*/
|
||||
export async function sendAdminNotice(message) {
|
||||
try {
|
||||
const mailer = getTransporter()
|
||||
if (!mailer || !config.chatNotify.emails.length) return { sent: false, reason: 'nicht konfiguriert' }
|
||||
const mail = render(message)
|
||||
await mailer.sendMail({
|
||||
from: `"WEBklar Kundenbereich" <${config.smtp.from}>`,
|
||||
from: `"${TEAM_SENDER}" <${config.smtp.from}>`,
|
||||
to: config.chatNotify.emails.join(', '),
|
||||
subject: `[Abo] ${subject}`,
|
||||
text: lines.join('\n'),
|
||||
subject: mail.subject,
|
||||
text: mail.text,
|
||||
html: mail.html,
|
||||
})
|
||||
return { sent: true }
|
||||
} catch (err) {
|
||||
@@ -80,20 +96,25 @@ export async function sendAdminNotice(subject, lines) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mail an Kunden. `attachments` fuer die Vertragsbestaetigung auf dauerhaftem
|
||||
* Datentraeger (§ 312f BGB): Bedingungen als HTML-Anhang.
|
||||
* Mail an Kunden aus einer K-Vorlage (HTML mit Textfassung), Kopie per BCC ans Team.
|
||||
* `attachments` fuer die Vertragsbestaetigung auf dauerhaftem Datentraeger (§ 312f BGB):
|
||||
* Vertragsunterlagen als PDF.
|
||||
* Ergebnis: nodemailer-Info (messageId, Antwort des Mailservers, abgelehnte Empfaenger).
|
||||
* @param {string} to
|
||||
* @param {{ template: string, values?: object, blocks?: object, attachments?: object[] }} message
|
||||
*/
|
||||
export async function sendCustomerMail(to, subject, lines, attachments = []) {
|
||||
export async function sendCustomerMail(to, { attachments = [], ...message }) {
|
||||
const mailer = getTransporter()
|
||||
if (!mailer) throw new Error('SMTP nicht konfiguriert')
|
||||
const mail = render(message)
|
||||
return mailer.sendMail({
|
||||
from: `"WEBklar" <${config.smtp.from}>`,
|
||||
from: `"${CUSTOMER_SENDER}" <${config.smtp.from}>`,
|
||||
replyTo: config.smtp.replyTo || undefined,
|
||||
to,
|
||||
bcc: config.chatNotify.emails.join(', ') || undefined,
|
||||
subject,
|
||||
text: lines.join('\n'),
|
||||
subject: mail.subject,
|
||||
text: mail.text,
|
||||
html: mail.html,
|
||||
attachments,
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user