import nodemailer from "nodemailer"; import type Mail from "nodemailer/lib/mailer/index.js"; // Returns null when SMTP is not configured — callers skip sending silently. function createTransport(): nodemailer.Transporter | null { const host = process.env.SMTP_HOST; if (!host) return null; return nodemailer.createTransport({ host, port: Number(process.env.SMTP_PORT ?? 587), secure: process.env.SMTP_SECURE === "true", auth: process.env.SMTP_USER ? { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS } : undefined, }); } let _transport: nodemailer.Transporter | null | undefined; function getTransport(): nodemailer.Transporter | null { if (_transport === undefined) _transport = createTransport(); return _transport; } const FROM = process.env.SMTP_FROM ?? "Groom Book "; export async function sendEmail(opts: Mail.Options): Promise { const transport = getTransport(); if (!transport) return false; // SMTP not configured — skip silently await transport.sendMail({ from: FROM, ...opts }); return true; } // ─── Email templates ────────────────────────────────────────────────────────── interface AppointmentEmailData { clientName: string; petName: string; serviceName: string; groomerName: string | null; startTime: Date; } function formatDateTime(d: Date): string { return d.toLocaleString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric", hour: "2-digit", minute: "2-digit", }); } export function buildConfirmationEmail( to: string, data: AppointmentEmailData ): Mail.Options { const time = formatDateTime(data.startTime); const groomer = data.groomerName ? ` with ${data.groomerName}` : ""; return { to, subject: `Appointment Confirmed — ${data.petName} on ${data.startTime.toLocaleDateString()}`, text: [ `Hi ${data.clientName},`, ``, `Your appointment has been confirmed!`, ``, ` Pet: ${data.petName}`, ` Service: ${data.serviceName}`, ` When: ${time}${groomer}`, ``, `We look forward to seeing you. If you need to reschedule, please contact us.`, ``, `— Groom Book`, ].join("\n"), html: `

Hi ${data.clientName},

Your appointment has been confirmed!

Pet${data.petName}
Service${data.serviceName}
When${time}${groomer}

We look forward to seeing you. If you need to reschedule, please contact us.

— Groom Book

`, }; } export function buildReminderEmail( to: string, data: AppointmentEmailData, hoursAhead: number, confirmationToken?: string | null ): Mail.Options { const time = formatDateTime(data.startTime); const groomer = data.groomerName ? ` with ${data.groomerName}` : ""; const when = hoursAhead >= 24 ? `tomorrow` : `in ${hoursAhead} hours`; const apiUrl = process.env.API_URL ?? "http://localhost:3000"; const confirmUrl = confirmationToken ? `${apiUrl}/api/book/confirm/${confirmationToken}` : null; const cancelUrl = confirmationToken ? `${apiUrl}/api/book/cancel/${confirmationToken}` : null; const actionText = confirmationToken ? [ ``, `Confirm your appointment: ${confirmUrl}`, `Cancel your appointment: ${cancelUrl}`, ].join("\n") : ""; const actionHtml = confirmationToken ? `
Confirm Appointment Cancel Appointment
` : ""; return { to, subject: `Reminder: ${data.petName}'s appointment is ${when}`, text: [ `Hi ${data.clientName},`, ``, `Just a reminder that ${data.petName}'s grooming appointment is ${when}.`, ``, ` Pet: ${data.petName}`, ` Service: ${data.serviceName}`, ` When: ${time}${groomer}`, actionText, `See you soon!`, ``, `— Groom Book`, ].join("\n"), html: `

Hi ${data.clientName},

Just a reminder that ${data.petName}'s grooming appointment is ${when}.

Pet${data.petName}
Service${data.serviceName}
When${time}${groomer}
${actionHtml}

See you soon!

— Groom Book

`, }; } interface WaitlistNotificationData { clientName: string; petName: string; serviceName: string; preferredDate: string; preferredTime: string; } export function buildWaitlistNotificationEmail( to: string, data: WaitlistNotificationData ): Mail.Options { const apiUrl = process.env.API_URL ?? "http://localhost:3000"; const bookUrl = `${apiUrl}/book`; return { to, subject: `Appointment Cancelled — A slot has opened up for ${data.petName}`, text: [ `Hi ${data.clientName},`, ``, `Great news! An appointment slot has become available.`, ``, `We had a cancellation for:`, ` Pet: ${data.petName}`, ` Service: ${data.serviceName}`, ` Date: ${data.preferredDate}`, ` Time: ${data.preferredTime}`, ``, `If you're still interested, book now before this slot is taken!`, ``, `Book your appointment: ${bookUrl}`, ``, `— Groom Book`, ].join("\n"), html: `

Hi ${data.clientName},

Great news! An appointment slot has become available.

We had a cancellation for:

Pet${data.petName}
Service${data.serviceName}
Date${data.preferredDate}
Time${data.preferredTime}
Book This Slot

If you're no longer interested, you can ignore this email or remove yourself from the waitlist in your portal.

— Groom Book

`, }; }