import nodemailer from "nodemailer" import { EVENT_SHORT, EVENT_FULL_NAME, EVENT_DATES, EVENT_LOCATION, EMAIL_BRANDING, LINKS, } from "./event.config" // Lazy-initialized SMTP transport (Mailcow) let transporter: nodemailer.Transporter | null = null function getTransporter() { if (!transporter && process.env.SMTP_PASS) { transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST || "mail.rmail.online", port: parseInt(process.env.SMTP_PORT || "587"), secure: false, auth: { user: process.env.SMTP_USER || "cofi@rspace.online", pass: process.env.SMTP_PASS, }, tls: { rejectUnauthorized: false }, }) } return transporter } const EMAIL_FROM = process.env.EMAIL_FROM || EMAIL_BRANDING.fromDefault const INTERNAL_NOTIFY_EMAIL = process.env.INTERNAL_NOTIFY_EMAIL || "jeff@jeffemmett.com" interface BookingNotificationData { guestName: string guestEmail: string accommodationType: string amountPaid: string bookingSuccess: boolean venue?: string room?: string bedType?: string error?: string } export async function sendBookingNotification( data: BookingNotificationData ): Promise { const transport = getTransporter() if (!transport) { console.log("[Email] SMTP not configured, skipping booking notification") return false } const statusColor = data.bookingSuccess ? "#16a34a" : "#dc2626" const statusLabel = data.bookingSuccess ? "ASSIGNED" : "FAILED" const flags: string[] = [] if (!data.bookingSuccess) { flags.push(`Booking assignment failed: ${data.error || "unknown reason"}`) } if (!data.guestEmail) { flags.push("No email address on file for this guest") } const html = `

Accommodation Update: ${data.guestName}

${statusLabel}

${data.bookingSuccess ? ` ` : ""}
Guest:${data.guestName}
Email:${data.guestEmail || "N/A"}
Paid:${data.amountPaid}
Requested:${data.accommodationType}
Assigned Venue:${data.venue}
Room:${data.room}
Bed Type:${data.bedType}
${ flags.length > 0 ? `
Flags:
    ${flags.map((f) => `
  • ${f}
  • `).join("")}
` : `

No issues detected.

` }

Automated notification from ${EVENT_SHORT} registration system

` try { const info = await transport.sendMail({ from: EMAIL_FROM, to: INTERNAL_NOTIFY_EMAIL, subject: `[${EVENT_SHORT} Booking] ${statusLabel}: ${data.guestName} — ${data.accommodationType}`, html, }) console.log(`[Email] Booking notification sent to ${INTERNAL_NOTIFY_EMAIL} (${info.messageId})`) return true } catch (error) { console.error("[Email] Failed to send booking notification:", error) return false } } interface PaymentConfirmationData { name: string email: string amountPaid: string paymentMethod: string contributions: string dietary: string accommodationVenue?: string accommodationRoom?: string } export async function sendPaymentConfirmation( data: PaymentConfirmationData ): Promise { const transport = getTransporter() if (!transport) { console.log("[Email] SMTP not configured, skipping confirmation email") return false } const html = `

${EMAIL_BRANDING.headerText}

${EVENT_FULL_NAME} ${EVENT_DATES}

Dear ${data.name},

Your payment of ${data.amountPaid} has been confirmed. You are now registered for ${EVENT_SHORT}, ${EVENT_DATES} in ${EVENT_LOCATION}.

Registration Details

${data.dietary ? `` : ""} ${data.accommodationVenue ? `` : ""}
Amount: ${data.amountPaid}
Payment: ${data.paymentMethod}
Dietary:${data.dietary}
Accommodation:${data.accommodationVenue}${data.accommodationRoom ? `, Room ${data.accommodationRoom}` : ""}

What's Next?

  • Join the ${EVENT_SHORT} community to connect with other participants
  • We'll follow up with further details on logistics and schedule

See you there,
The ${EVENT_SHORT} Team


You received this email because you registered at ${LINKS.website.replace("https://", "")}.
${LINKS.website.replace("https://", "")}

` try { const info = await transport.sendMail({ from: EMAIL_FROM, to: data.email, bcc: INTERNAL_NOTIFY_EMAIL, subject: `Registration Confirmed - ${EVENT_SHORT}`, html, }) console.log( `[Email] Payment confirmation sent to ${data.email} (${info.messageId})` ) return true } catch (error) { console.error("[Email] Failed to send payment confirmation:", error) return false } }