Alle ausstehenden Aenderungen (Bilder, Texte) fuer den Server-Umzug uebernommen
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/* =====================================================================
|
||||
lib/supporter-mail.js — Transaktions-E-Mails für den Supporter-Bereich
|
||||
(E-Mail-Bestätigung, Login-Codes, Zahlungs-/Prämien-Benachrichtigungen).
|
||||
|
||||
WICHTIG — offener Punkt, siehe auch README.md "Supporter-Abo Setup":
|
||||
Es existiert aktuell KEIN E-Mail-Versand-Anbieter (kein Resend/
|
||||
MailChannels o.ä. eingerichtet) und KEINE eigene Domain (die Seite läuft
|
||||
noch auf *.workers.dev) — beides wird für zuverlässigen E-Mail-Versand
|
||||
gebraucht (Absender-Domain muss per SPF/DKIM verifiziert sein, sonst
|
||||
landet alles im Spam oder wird abgelehnt).
|
||||
|
||||
Diese Datei ist deshalb bewusst als AUSTAUSCHBARER Adapter gebaut:
|
||||
Sobald RESEND_API_KEY (+ RESEND_FROM) als Secret gesetzt ist, wird über
|
||||
die Resend-API (https://resend.com, kostenloser Tier reicht für den
|
||||
Start) verschickt. Fehlt der Key, wird NICHTS verschickt und ein
|
||||
sprechender Fehler zurückgegeben — bewusst "fail closed" statt den Code
|
||||
irgendwo im Klartext zu loggen (Sicherheitsprinzip wie beim Rest des
|
||||
Zugangssystems, siehe audit.js).
|
||||
===================================================================== */
|
||||
|
||||
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(env, { to, subject, html }) {
|
||||
if (!env.RESEND_API_KEY || !env.RESEND_FROM) throw new EmailNotConfiguredError();
|
||||
const res = await fetch("https://api.resend.com/emails", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${env.RESEND_API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ from: 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(env, { to, name, code }) {
|
||||
return send(env, {
|
||||
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(env, { to, name, code }) {
|
||||
return send(env, {
|
||||
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>`
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
export async function sendMilestoneUnlocked(env, { to, name, premiumLabel }) {
|
||||
return send(env, {
|
||||
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>`
|
||||
),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user