import { Resend } from 'resend' import type { RestaurantTable } from '#/db/schema' const dateTimeFormatter = new Intl.DateTimeFormat('de-DE', { dateStyle: 'full', timeStyle: 'short', }) const COLORS = { bg: '#17120f', bgRaised: '#1e1712', line: '#3a2f26', text: '#f3ead9', textMuted: '#c9baa3', gold: '#c9a15a', goldBright: '#e0bb78', } function escapeHtml(value: string) { return value .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", ''') } let resendClient: Resend | undefined function getResendClient() { const apiKey = process.env.RESEND_API_KEY if (!apiKey) { return null } resendClient ??= new Resend(apiKey) return resendClient } type ReservationEmailInfo = { customerName: string customerEmail: string message?: string | null partySize: number reservationTime: Date cancelToken: string table: Pick } const CONTACT_EMAIL = 'info@steakhaus-meppen.de' function getSiteUrl() { return process.env.BETTER_AUTH_URL ?? 'http://localhost:3000' } function buildCancelUrl(cancelToken: string) { return `${getSiteUrl()}/stornieren/${cancelToken}` } function renderReservationEmailHtml( heading: string, intro: string, reservation: ReservationEmailInfo, showActions: boolean, ) { const formattedTime = dateTimeFormatter.format(reservation.reservationTime) const name = escapeHtml(reservation.customerName) const tableName = escapeHtml(reservation.table.name) const cancelUrl = buildCancelUrl(reservation.cancelToken) const row = (label: string, value: string) => ` ${label} ${value} ` return ` ${heading}
Steakhaus bei Mirko
${ reservation.message ? ` ` : '' } ${ showActions ? ` ` : '' }
${heading}
Hallo ${name},
${intro}
${row('Tisch', tableName)} ${row('Datum & Uhrzeit', formattedTime)} ${row('Personen', String(reservation.partySize))}
Nachricht
${escapeHtml(reservation.message)}
Reservierung stornieren
Möchten Sie etwas ändern? Schreiben Sie uns einfach eine E-Mail an ${CONTACT_EMAIL}.
Wir freuen uns auf Sie!
Meppener Straße 8 · 49716 Meppen · 05931 - 12750
` } function renderReservationEmailText( heading: string, intro: string, reservation: ReservationEmailInfo, showActions: boolean, ) { const formattedTime = dateTimeFormatter.format(reservation.reservationTime) return [ 'Steakhaus bei Mirko', '', heading, '', `Hallo ${reservation.customerName},`, '', intro, '', `Tisch: ${reservation.table.name}`, `Datum & Uhrzeit: ${formattedTime}`, `Personen: ${reservation.partySize}`, ...(reservation.message ? [`Nachricht: ${reservation.message}`] : []), '', ...(showActions ? [ `Reservierung stornieren: ${buildCancelUrl(reservation.cancelToken)}`, `Möchten Sie etwas ändern? Schreiben Sie uns eine E-Mail an ${CONTACT_EMAIL}.`, '', ] : []), 'Wir freuen uns auf Sie!', 'Steakhaus bei Mirko', 'Meppener Straße 8 · 49716 Meppen · 05931 - 12750', ].join('\n') } async function sendReservationEmail( subject: string, intro: string, reservation: ReservationEmailInfo, showActions: boolean, ) { const client = getResendClient() if (!client) { console.warn('RESEND_API_KEY is not set — skipping reservation email') return } const fromAddress = process.env.RESERVATIONS_FROM_EMAIL ?? 'Steakhaus bei Mirko ' try { await client.emails.send({ from: fromAddress, to: reservation.customerEmail, subject, html: renderReservationEmailHtml(subject, intro, reservation, showActions), text: renderReservationEmailText(subject, intro, reservation, showActions), }) } catch (err) { console.error('Failed to send reservation email', err) } } export function sendReservationConfirmationEmail(reservation: ReservationEmailInfo) { return sendReservationEmail( 'Ihre Reservierung ist bestätigt', 'vielen Dank für Ihre Reservierung. Hier sind die Details:', reservation, true, ) } export function sendReservationUpdateEmail(reservation: ReservationEmailInfo) { return sendReservationEmail( 'Ihre Reservierung wurde geändert', 'Ihre Reservierung wurde aktualisiert. Hier sind die neuen Details:', reservation, true, ) } export function sendReservationCancellationEmail(reservation: ReservationEmailInfo) { return sendReservationEmail( 'Ihre Reservierung wurde storniert', 'Ihre folgende Reservierung wurde storniert:', reservation, false, ) }