import nodemailer from "nodemailer"; import { logLine } from "@/lib/logger"; type EmailPayload = { to: string; subject: string; text: string; html: string; }; let cachedTransport: nodemailer.Transporter | null = null; function getTransporter() { if (cachedTransport) return cachedTransport; const host = process.env.SMTP_HOST; const port = Number(process.env.SMTP_PORT || 465); const user = process.env.SMTP_USER; const pass = process.env.SMTP_PASS; const secure = process.env.SMTP_SECURE !== undefined ? process.env.SMTP_SECURE === "true" : port === 465; if (!host || !user || !pass) { throw new Error("SMTP not configured"); } const smtpDebug = process.env.SMTP_DEBUG === "true"; logLine("smtp.config", { host, port, secure, user, from: process.env.SMTP_FROM, smtpDebug, }); cachedTransport = nodemailer.createTransport({ host, port, secure, auth: { user, pass }, logger: smtpDebug, debug: smtpDebug, }); return cachedTransport; } export async function sendEmail(payload: EmailPayload) { const from = process.env.SMTP_FROM || process.env.SMTP_USER; if (!from) { throw new Error("SMTP_FROM not configured"); } logLine("email.send.start", { to: payload.to, subject: payload.subject, from, }); const transporter = getTransporter(); try { const info = await transporter.sendMail({ from, to: payload.to, subject: payload.subject, text: payload.text, html: payload.html, headers: { "X-Mailer": "MIS Control Tower", }, replyTo: from, }); // Nodemailer response details: const pending = "pending" in info ? (info as { pending?: string[] }).pending : undefined; logLine("email.send.ok", { to: payload.to, from, messageId: info.messageId, response: info.response, accepted: info.accepted, rejected: info.rejected, pending, }); return info; } catch (err: unknown) { const error = err as { name?: string; message?: string; code?: string; command?: string; response?: unknown; responseCode?: number; stack?: string; }; logLine("email.send.err", { to: payload.to, from, name: error?.name, message: error?.message, code: error?.code, command: error?.command, response: error?.response, responseCode: error?.responseCode, stack: error?.stack, }); throw err; } } export function buildVerifyEmail(params: { appName: string; verifyUrl: string }) { const subject = `Verify your ${params.appName} account`; const text = `Welcome to ${params.appName}.\n\n` + `Verify your email to activate your account:\n${params.verifyUrl}\n\n` + `If you did not request this, ignore this email.`; const html = `

Welcome to ${params.appName}.

` + `

Verify your email to activate your account:

` + `

${params.verifyUrl}

` + `

If you did not request this, ignore this email.

`; return { subject, text, html }; } export function buildInviteEmail(params: { appName: string; orgName: string; inviteUrl: string; }) { const subject = `You're invited to ${params.orgName} on ${params.appName}`; const text = `You have been invited to join ${params.orgName} on ${params.appName}.\n\n` + `Accept the invite here:\n${params.inviteUrl}\n\n` + `If you did not expect this invite, you can ignore this email.`; const html = `

You have been invited to join ${params.orgName} on ${params.appName}.

` + `

Accept the invite here:

` + `

${params.inviteUrl}

` + `

If you did not expect this invite, you can ignore this email.

`; return { subject, text, html }; } export function buildDowntimeActionAssignedEmail(params: { appName: string; orgName: string; actionTitle: string; assigneeName: string; dueDate: string | null; actionUrl: string; priority: string; status: string; }) { const dueLabel = params.dueDate ? `Due ${params.dueDate}` : "No due date"; const subject = `Action assigned: ${params.actionTitle}`; const text = `Hi ${params.assigneeName},\n\n` + `You have been assigned an action in ${params.orgName} (${params.appName}).\n\n` + `Title: ${params.actionTitle}\n` + `Status: ${params.status}\n` + `Priority: ${params.priority}\n` + `${dueLabel}\n\n` + `Open in Control Tower:\n${params.actionUrl}\n\n` + `If you did not expect this assignment, please contact your admin.`; const html = `

Hi ${params.assigneeName},

` + `

You have been assigned an action in ${params.orgName} (${params.appName}).

` + `

Title: ${params.actionTitle}
` + `Status: ${params.status}
` + `Priority: ${params.priority}
` + `${dueLabel}

` + `

Open in Control Tower

` + `

If you did not expect this assignment, please contact your admin.

`; return { subject, text, html }; } export function buildDowntimeActionReminderEmail(params: { appName: string; orgName: string; actionTitle: string; assigneeName: string; dueDate: string | null; actionUrl: string; priority: string; status: string; }) { const dueLabel = params.dueDate ? `Due ${params.dueDate}` : "No due date"; const subject = `Reminder: ${params.actionTitle}`; const text = `Hi ${params.assigneeName},\n\n` + `Reminder for your action in ${params.orgName} (${params.appName}).\n\n` + `Title: ${params.actionTitle}\n` + `Status: ${params.status}\n` + `Priority: ${params.priority}\n` + `${dueLabel}\n\n` + `Open in Control Tower:\n${params.actionUrl}\n\n` + `If you have already completed this action, you can mark it done in the app.`; const html = `

Hi ${params.assigneeName},

` + `

Reminder for your action in ${params.orgName} (${params.appName}).

` + `

Title: ${params.actionTitle}
` + `Status: ${params.status}
` + `Priority: ${params.priority}
` + `${dueLabel}

` + `

Open in Control Tower

` + `

If you have already completed this action, you can mark it done in the app.

`; return { subject, text, html }; }