Abo-Angebote: Mail-Status in der Liste und E-Mail erneut senden

Bisher sah man nirgends, ob die Angebots-Mail rausging, Fehler landeten
nicht im Log, und senden ließ sie sich nur beim Anlegen.
- Angebotsliste: Kunden-Adresse unter dem Namen, im Status "E-Mail
  gesendet <Zeitpunkt>" bzw. "E-Mail nicht verschickt" / "Keine
  E-Mail-Adresse hinterlegt"
- Knopf "E-Mail senden" / "Erneut senden" mit Rückfrage (Adresse, letzter
  Versand), solange das Angebot offen und gültig ist; bei laufendem Abo,
  solange die erste rabattierte Abbuchung noch bevorsteht (die Mail
  nennt sie). POST /api/portal-admin/offers/:id/notify setzt notifiedAt.
- Server-Log: "[offers] Angebots-Mail <Ref> vom Mailserver angenommen:
  <Message-ID> 250 … queued as …" bzw. "… fehlgeschlagen: <Grund>"
- Lehnt der Mailserver nur den Kunden ab (die BCC-Kopie ans Team geht
  trotzdem raus), gilt die Mail als nicht verschickt
- sendCustomerMail gibt die nodemailer-Info zurück

Tests: npm test (40), Server-Durchlauf gegen Appwrite-Mock, SMTP-Sink
und Stripe-Double (28 Prüfungen), Oberfläche (18 Prüfungen).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-09-27 11:12:39 +00:00
1 parent 81119c19bc
commit 32e441c8bc
5 files changed
+161 -18

No files matched your search

+2 -1
View File
@@ -80,11 +80,12 @@ export async function sendAdminNotice(subject, lines) {
/**
* Mail an Kunden. `attachments` fuer die Vertragsbestaetigung auf dauerhaftem
* Datentraeger (§ 312f BGB): Bedingungen als HTML-Anhang.
* Ergebnis: nodemailer-Info (messageId, Antwort des Mailservers, abgelehnte Empfaenger).
*/
export async function sendCustomerMail(to, subject, lines, attachments = []) {
const mailer = getTransporter()
if (!mailer) throw new Error('SMTP nicht konfiguriert')
await mailer.sendMail({
return mailer.sendMail({
from: `"Webklar" <${config.smtp.from}>`,
to,
bcc: config.chatNotify.emails.join(', ') || undefined,