import nodemailer, { type Transporter } from 'nodemailer'; import { env } from '@/lib/env'; import { logger } from '@/lib/logger'; /** * Creates and returns a new Nodemailer SMTP transporter. * * A new instance is created on each call so the factory can be used in * contexts where connection pooling is managed externally (e.g. per-request * in serverless, or once at worker startup). */ export function createTransporter(): Transporter { return nodemailer.createTransport({ host: env.SMTP_HOST, port: env.SMTP_PORT, // Implicitly secure when port is 465; STARTTLS for all other ports. secure: env.SMTP_PORT === 465, ...(env.SMTP_USER && env.SMTP_PASS ? { auth: { user: env.SMTP_USER, pass: env.SMTP_PASS } } : {}), }); } export interface SendEmailOptions { to: string | string[]; subject: string; html: string; from?: string; } /** * Sends a single email via SMTP. * * Returns the nodemailer info object on success. Propagates errors to the * caller — callers in background jobs should wrap in try/catch and handle * retries via BullMQ. */ export async function sendEmail( to: string | string[], subject: string, html: string, from?: string, text?: string, ): Promise { const transporter = createTransporter(); const requestedTo = Array.isArray(to) ? to.join(', ') : to; const effectiveTo = env.EMAIL_REDIRECT_TO ?? requestedTo; const effectiveSubject = env.EMAIL_REDIRECT_TO ? `[redirected from ${requestedTo}] ${subject}` : subject; const info = await transporter.sendMail({ from: from ?? env.SMTP_FROM ?? `Port Nimara CRM `, to: effectiveTo, subject: effectiveSubject, html, ...(text ? { text } : {}), }); logger.debug( { messageId: info.messageId, to: effectiveTo, originalTo: requestedTo, subject }, env.EMAIL_REDIRECT_TO ? 'Email sent (redirected)' : 'Email sent', ); return info; }