Files
dogfather-universe/server-internal/lib/supporter-mail.js
T
DogFatherGitandClaude Opus 5 5d0ea11570 Arbeit der parallelen Session in git gesichert (war nur auf dem Server)
Diese Änderungen liefen bereits live auf dem Server, lagen dort aber
ausschließlich als nicht eingecheckte Arbeitskopie — bei jedem Deploy
(stash/pull/pop) und bei jedem Serverproblem wären sie verloren gewesen.
Deshalb hier unverändert in git übernommen, bevor darauf aufgebaut wird.

Enthalten (nicht von mir gebaut, nur gesichert):
- Supporter: eigener fester Zugangscode statt Einmalcode-Login
  (Migration 0009, lib/crypto.js scrypt-Hash, routes/supporter.js,
  abonnieren.html, supporter.html, i18n-abonnieren/-supporter)
- Stimmen: Profilbilder (Migration 0010, routes/testimonials.js)
- Event-Bild-Upload: voller Pfad statt relativem (routes/events.js)
- Neue/überarbeitete Hintergrundbilder für viele Seiten
- Teilen-Funktion (streamplan.js, i18n-index/-streamplan, main.css)

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-21 17:35:14 +02:00

96 lines
4.1 KiB
JavaScript

/* =====================================================================
lib/supporter-mail.js — Transaktions-E-Mails für den Supporter-Bereich
(Resend-Adapter). 1:1 portiert, env.X → process.env.X.
===================================================================== */
export class EmailNotConfiguredError extends Error {
constructor() {
super("E-Mail-Versand ist noch nicht eingerichtet (RESEND_API_KEY fehlt).");
this.name = "EmailNotConfiguredError";
this.code = "EMAIL_NOT_CONFIGURED";
}
}
async function send({ to, subject, html }) {
if (!process.env.RESEND_API_KEY || !process.env.RESEND_FROM) throw new EmailNotConfiguredError();
const res = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.RESEND_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ from: process.env.RESEND_FROM, to: [to], subject, html }),
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`E-Mail-Versand fehlgeschlagen (${res.status}): ${text.slice(0, 300)}`);
}
return true;
}
const WRAP = (title, bodyHtml) => `
<div style="font-family:Arial,sans-serif;background:#0b0d10;color:#f3f5f7;padding:32px;">
<div style="max-width:480px;margin:0 auto;background:#171b21;border-radius:14px;padding:28px;border:1px solid #262c35;">
<h1 style="color:#8fd9ea;font-size:20px;margin:0 0 16px;">${title}</h1>
${bodyHtml}
<p style="color:#9aa4b2;font-size:12px;margin-top:24px;">DogiCrew 🩵</p>
</div>
</div>`;
export async function sendVerifyEmailCode({ to, name, code }) {
return send({
to,
subject: "Bestätige deine E-Mail-Adresse — DogiCrew 🩵",
html: WRAP(
`Willkommen, ${name}! 🩵`,
`<p style="line-height:1.6;">Bestätige deine E-Mail-Adresse mit diesem Code:</p>
<p style="font-size:32px;font-weight:800;letter-spacing:4px;color:#fff;">${code}</p>
<p style="color:#9aa4b2;font-size:13px;">Der Code ist 15 Minuten gültig.</p>`
),
});
}
export async function sendLoginCode({ to, name, code }) {
return send({
to,
subject: "Dein Login-Code — Dogfather Supporter-Bereich",
html: WRAP(
`Hallo ${name} 👋`,
`<p style="line-height:1.6;">Dein Login-Code für den Supporter-Bereich:</p>
<p style="font-size:32px;font-weight:800;letter-spacing:4px;color:#fff;">${code}</p>
<p style="color:#9aa4b2;font-size:13px;">Der Code ist 15 Minuten gültig. Hast du das nicht angefordert, ignoriere diese E-Mail.</p>`
),
});
}
/* Willkommens-E-Mail nach der Registrierung (21.08.2026). Enthält bewusst
KEINEN Code — man hat sich ja gerade selbst einen gesetzt und ist schon
eingeloggt. Sie dient als Beleg, dass die Adresse stimmt, und als
Erinnerung, wo man wieder reinkommt. */
export async function sendWelcomeMail({ to, name }) {
return send({
to,
subject: "Willkommen in der DogiCrew 🩵",
html: WRAP(
`Willkommen, ${name}! 🩵`,
`<p style="line-height:1.6;">Dein Konto ist fertig. Du kommst ab jetzt jederzeit mit dieser
E-Mail-Adresse und deinem selbst gewählten Zugangscode rein.</p>
<p style="line-height:1.6;">Solltest du deinen Code mal vergessen, gibt es auf der Anmeldeseite
„Code vergessen?“ — darüber bekommst du hier per E-Mail einen Einmalcode und kannst dir
einen neuen Zugangscode setzen.</p>
<p style="color:#9aa4b2;font-size:13px;">Hast du dich nicht angemeldet? Dann ignoriere diese
E-Mail einfach — ohne deinen Zugangscode kommt niemand in das Konto.</p>`
),
});
}
export async function sendMilestoneUnlocked({ to, name, premiumLabel }) {
return send({
to,
subject: `Neuer Meilenstein freigeschaltet — ${premiumLabel} 🎉`,
html: WRAP(
`Glückwunsch, ${name}! 🩵`,
`<p style="line-height:1.6;">Du hast einen neuen Meilenstein erreicht und kannst jetzt deine Prämie einlösen:</p>
<p style="font-size:20px;font-weight:800;color:#fff;">${premiumLabel}</p>
<p style="color:#9aa4b2;font-size:13px;">Logge dich in deinen Supporter-Bereich ein, um die Prämie einzulösen.</p>`
),
});
}