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 info = await transporter.sendMail({ from: from ?? env.SMTP_FROM ?? `Port Nimara CRM `, to: Array.isArray(to) ? to.join(', ') : to, subject, html, ...(text ? { text } : {}), }); logger.debug({ messageId: info.messageId, to, subject }, 'Email sent'); return info; }