58 lines
1.4 KiB
TypeScript
58 lines
1.4 KiB
TypeScript
|
|
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,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
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,
|
||
|
|
): Promise<nodemailer.SentMessageInfo> {
|
||
|
|
const transporter = createTransporter();
|
||
|
|
|
||
|
|
const info = await transporter.sendMail({
|
||
|
|
from: from ?? `Port Nimara CRM <noreply@${env.SMTP_HOST}>`,
|
||
|
|
to: Array.isArray(to) ? to.join(', ') : to,
|
||
|
|
subject,
|
||
|
|
html,
|
||
|
|
});
|
||
|
|
|
||
|
|
logger.debug(
|
||
|
|
{ messageId: info.messageId, to, subject },
|
||
|
|
'Email sent',
|
||
|
|
);
|
||
|
|
|
||
|
|
return info;
|
||
|
|
}
|