import nodemailer from "nodemailer"; function getTransport() { const host = process.env["SMTP_HOST"]; if (!host) return null; return nodemailer.createTransport({ host, port: parseInt(process.env["SMTP_PORT"] ?? "587"), secure: process.env["SMTP_SECURE"] === "true", auth: { user: process.env["SMTP_USER"], pass: process.env["SMTP_PASS"], }, }); } const FROM = process.env["SMTP_FROM"] ?? "Epicure "; export async function sendEmail({ to, subject, html, }: { to: string; subject: string; html: string; }) { const transport = getTransport(); if (!transport) { console.log(`[email:dev] No SMTP_HOST — logging email instead.\n Subject: ${subject}\n To: ${to}\n Body: ${html.replace(/<[^>]+>/g, "").slice(0, 200)}`); return; } try { const info = await transport.sendMail({ from: FROM, to, subject, html }); console.log(`[email:sent] "${subject}" → ${to} | id:${info.messageId} | response:${info.response}`); } catch (err) { console.error(`[email:error] "${subject}" → ${to} |`, err); throw err; } } // ── Templates ───────────────────────────────────────────────────────────────── function layout(title: string, body: string) { return ` ${title}
🍴 Epicure
${body}

You received this email from Epicure. If you didn't request it, ignore this email.

`; } function btn(href: string, label: string) { return `${label}`; } export function verifyEmailHtml(url: string) { return layout( "Verify your email — Epicure", `

Verify your email

Click the button below to verify your email address and activate your Epicure account.

${btn(url, "Verify email")}

Link expires in 24 hours. Or paste this URL into your browser:
${url}

` ); } export function resetPasswordHtml(url: string) { return layout( "Reset your password — Epicure", `

Reset your password

We received a request to reset the password for your Epicure account.

${btn(url, "Reset password")}

Link expires in 1 hour. If you didn't request a reset, you can safely ignore this email.

` ); } export function notificationEmailHtml(title: string, body: string, url: string) { return layout( `${title} — Epicure`, `

${title}

${body}

${btn(url, "View on Epicure")}` ); } export function weeklyDigestHtml(opts: { newFollowers: number; newComments: number; newRatings: number; trending: { id: string; title: string }[]; baseUrl: string; }) { const { newFollowers, newComments, newRatings, trending, baseUrl } = opts; const stats = [ newFollowers > 0 ? `${newFollowers} new follower${newFollowers === 1 ? "" : "s"}` : null, newComments > 0 ? `${newComments} new comment${newComments === 1 ? "" : "s"} on your recipes` : null, newRatings > 0 ? `${newRatings} new rating${newRatings === 1 ? "" : "s"} on your recipes` : null, ].filter((s): s is string => s !== null); const statsHtml = stats.length > 0 ? `` : `

Quiet week on your recipes — but here's what's trending.

`; const trendingHtml = trending.length > 0 ? `

Trending this week

    ${trending.map((r) => `
  1. ${r.title}
  2. `).join("")}
` : ""; return layout( "Your weekly digest — Epicure", `

Your week on Epicure

${statsHtml} ${trendingHtml} ${btn(baseUrl, "Open Epicure")}` ); } export function supportTicketReceivedHtml(opts: { type: string; title: string; ticketUrl: string; giteaIssueUrl: string | null; }) { const { type, title, ticketUrl, giteaIssueUrl } = opts; const typeLabel = type === "bug" ? "bug report" : type === "suggestion" ? "suggestion" : "question"; return layout( "We got your message — Epicure", `

Thanks for reaching out

We received your ${typeLabel}:

${title}

We'll follow up if we need more details.${giteaIssueUrl ? " It's also been logged for tracking." : ""}

${btn(ticketUrl, "View your ticket")}` ); } export function welcomeHtml(name: string) { return layout( "Welcome to Epicure", `

Welcome, ${name}!

Your account is ready. Start by creating your first recipe, importing from a URL, or letting AI generate one for you.

${btn(process.env["BETTER_AUTH_URL"] ?? "http://localhost:3000", "Go to Epicure")}

Questions? Just reply to this email.

` ); }