/* ===================================================================== functions/_shared/email.js — E-Mail-Versand über Resend (kostenlos bis 3.000 E-Mails/Monat, 100/Tag — für einen kleinen Handmade-Shop mehr als ausreichend, siehe Recherche im Vault- Tagesprotokoll). Bewusst NICHT über Cloudflare Email Routing/Workers Send-Email-Binding gelöst, weil das einen bereits verifizierten, eigenen Domain-Versand voraussetzt — die Seite läuft aktuell noch auf *.pages.dev ohne eigene Domain. Resend funktioniert dagegen sofort mit der kostenlosen "onboarding@resend.dev"-Absenderadresse, ganz ohne eigene Domain-Verifizierung. WICHTIG: E-Mail-Versand darf NIE eine Bestellung zum Scheitern bringen — die Bestellung ist in der Datenbank (D1) längst sicher gespeichert, bevor diese Funktion überhaupt aufgerufen wird. Deshalb wirft sendResendEmail() nie einen Fehler nach außen, sondern gibt nur { ok: true/false, error? } zurück — der Aufrufer entscheidet selbst, ob/wie er einen Fehlschlag protokolliert (siehe functions/api/orders.js). ===================================================================== */ const RESEND_API_URL = "https://api.resend.com/emails"; const STANDARD_ABSENDER = "Van's DIY & Bastelbedarf "; /** * Verschickt eine E-Mail über die Resend-API. Gibt IMMER ein Ergebnisobjekt zurück (nie eine * geworfene Exception), damit der Aufrufer den Versand einfach "fire and forget" behandeln kann. * `env.RESEND_API_KEY` fehlt einfach so lange, bis VanVan/qciga einen kostenlosen Resend-Account * eingerichtet haben — bis dahin liefert diese Funktion sauber `{ ok: false, error: "..." }` * zurück, ohne irgendetwas kaputtzumachen. */ export async function sendeEmail(env, { to, subject, html, text, from }) { if (!env.RESEND_API_KEY) { return { ok: false, error: "RESEND_API_KEY nicht gesetzt — E-Mail-Versand noch nicht eingerichtet." }; } try { const res = await fetch(RESEND_API_URL, { method: "POST", headers: { Authorization: `Bearer ${env.RESEND_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: from || env.RESEND_FROM || STANDARD_ABSENDER, to: Array.isArray(to) ? to : [to], subject, html, text, }), }); if (!res.ok) { const fehlertext = await res.text().catch(() => ""); return { ok: false, error: `Resend antwortete mit Status ${res.status}: ${fehlertext}` }; } return { ok: true }; } catch (err) { return { ok: false, error: err instanceof Error ? err.message : String(err) }; } } /** Escaped Text sicher fürs Einbetten in eine HTML-E-Mail (verhindert kaputtes HTML bei z.B. * Sonderzeichen im Kundennamen). */ export function escapeHtmlFuerEmail(str) { return String(str ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); }