Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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,
|
2026-04-14 12:54:25 -04:00
|
|
|
text?: string,
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
): 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,
|
2026-04-14 12:54:25 -04:00
|
|
|
...(text ? { text } : {}),
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
});
|
|
|
|
|
|
2026-04-14 12:54:25 -04:00
|
|
|
logger.debug({ messageId: info.messageId, to, subject }, 'Email sent');
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
|
|
|
|
|
return info;
|
|
|
|
|
}
|