import nodemailer from 'nodemailer'; import { buildLoginCodeEmailTemplate } from './notification-templates.js'; let cachedTransporter = null; function getSmtpConfig() { const host = process.env.SMTP_HOST; const portRaw = process.env.SMTP_PORT; const from = process.env.SMTP_FROM; const secureRaw = process.env.SMTP_SECURE ?? 'false'; const user = process.env.SMTP_USER; const pass = process.env.SMTP_PASS; if (!host) { throw new Error('SMTP_HOST is required for email login code delivery.'); } if (!portRaw) { throw new Error('SMTP_PORT is required for email login code delivery.'); } if (!from) { throw new Error('SMTP_FROM is required for email login code delivery.'); } const port = Number(portRaw); if (!Number.isInteger(port) || port <= 0) { throw new Error('SMTP_PORT must be a valid integer port number.'); } const secure = String(secureRaw).toLowerCase() === 'true'; const hasUser = Boolean(user); const hasPass = Boolean(pass); if (hasUser !== hasPass) { throw new Error('SMTP_USER and SMTP_PASS must be set together.'); } return { from, transport: { host, port, secure, ...(hasUser && hasPass ? { auth: { user, pass } } : {}), }, }; } function getTransporter() { if (cachedTransporter) { return cachedTransporter; } const config = getSmtpConfig(); cachedTransporter = nodemailer.createTransport(config.transport); return cachedTransporter; } function escapeHtml(value) { return String(value ?? '') .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"'); } function normalizeBody(body) { if (Array.isArray(body)) { return body .map((line) => String(line ?? '').trim()) .filter(Boolean); } const text = String(body ?? '').trim(); return text ? [text] : []; } function buildNotificationEmailText(body, buttonText, buttonUrl) { const lines = normalizeBody(body); if (buttonUrl) { lines.push(`${buttonText || 'Открыть'}: ${buttonUrl}`); } return lines.join('\n'); } function buildNotificationEmailHtml(body, buttonText, buttonUrl) { const paragraphs = normalizeBody(body) .map((line) => `
${escapeHtml(line)}
`) .join(''); if (!buttonUrl) { return paragraphs; } return `${paragraphs}`; } export async function sendLoginCodeEmail({ to, code, expiresAt }) { const { from } = getSmtpConfig(); const transporter = getTransporter(); const template = buildLoginCodeEmailTemplate({ code, expiresAt }); await transporter.sendMail({ from, to, subject: template.subject, text: template.text, html: template.html, }); } export async function sendNotificationEmail({ to, subject, body, buttonText = null, buttonUrl = null }) { const { from } = getSmtpConfig(); const transporter = getTransporter(); await transporter.sendMail({ from, to, subject, text: buildNotificationEmailText(body, buttonText, buttonUrl), html: buildNotificationEmailHtml(body, buttonText, buttonUrl), }); }