import nodemailer, { type Transporter } from 'nodemailer'; import { env } from '@/lib/env'; import { logger } from '@/lib/logger'; import { getPortEmailConfig, type PortEmailConfig } from '@/lib/services/port-config'; /** * Creates and returns a new Nodemailer SMTP transporter using env defaults. * For port-scoped configuration use {@link createPortTransporter} instead. * * 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 } } : {}), }); } function createTransporterFromConfig(cfg: PortEmailConfig): Transporter { return nodemailer.createTransport({ host: cfg.smtpHost, port: cfg.smtpPort, secure: cfg.smtpPort === 465, ...(cfg.smtpUser && cfg.smtpPass ? { auth: { user: cfg.smtpUser, pass: cfg.smtpPass } } : {}), }); } export interface SendEmailOptions { to: string | string[]; subject: string; html: string; from?: string; /** When provided, port-level email settings override env defaults. */ portId?: string; text?: 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, portId?: string, ): Promise { const cfg = portId ? await getPortEmailConfig(portId) : null; const transporter = cfg ? createTransporterFromConfig(cfg) : 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 fromHeader = from ?? (cfg ? `${cfg.fromName} <${cfg.fromAddress}>` : undefined) ?? env.SMTP_FROM ?? `Port Nimara CRM `; const info = await transporter.sendMail({ from: fromHeader, to: effectiveTo, subject: effectiveSubject, html, ...(cfg?.replyTo ? { replyTo: cfg.replyTo } : {}), ...(text ? { text } : {}), }); logger.debug( { messageId: info.messageId, to: effectiveTo, originalTo: requestedTo, subject, portId }, env.EMAIL_REDIRECT_TO ? 'Email sent (redirected)' : 'Email sent', ); return info; }