All files / web/src/lib/notifications email.ts

53.7% Statements 29/54
100% Branches 0/0
0% Functions 0/3
53.7% Lines 29/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 551x 1x 1x 1x 1x 1x 1x 1x 1x                       1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                         1x 1x 1x 1x 1x      
import nodemailer from 'nodemailer'
import type { Transporter } from 'nodemailer'
 
let _transport: Transporter | null = null
 
/**
 * Lazily initialised Nodemailer transport — reuses the EMAIL_SERVER
 * connection string already configured for NextAuth magic links.
 */
function ensureTransport(): Transporter {
  if (_transport) return _transport

  const server = process.env.EMAIL_SERVER
  if (!server) {
    throw new Error('[email] EMAIL_SERVER is not set')
  }

  _transport = nodemailer.createTransport(server)
  return _transport
}
 
export interface SendEmailParams {
  to: string
  subject: string
  html: string
  from?: string
  replyTo?: string
}
 
/**
 * Send an email using the shared Nodemailer transport.
 *
 * Uses EMAIL_FROM as the default sender. Callers can override
 * `from` and `replyTo` for channel-specific customisation.
 */
export async function sendEmail(params: SendEmailParams): Promise<void> {
  const transport = ensureTransport()
  const defaultFrom = process.env.EMAIL_FROM ?? 'Abaci One <hallock@gmail.com>'

  await transport.sendMail({
    from: params.from ?? defaultFrom,
    to: params.to,
    subject: params.subject,
    html: params.html,
    replyTo: params.replyTo,
  })
}
 
/**
 * Reset transport (for testing).
 */
export function _resetEmailTransport(): void {
  _transport = null
}