123 lines
3.2 KiB
JavaScript
123 lines
3.2 KiB
JavaScript
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) => `<p>${escapeHtml(line)}</p>`)
|
|
.join('');
|
|
|
|
if (!buttonUrl) {
|
|
return paragraphs;
|
|
}
|
|
|
|
return `${paragraphs}<p><a href="${escapeHtml(buttonUrl)}" style="display:inline-block;padding:12px 18px;border-radius:999px;background:#123824;color:#ffffff;text-decoration:none;font-weight:700;">${escapeHtml(buttonText || 'Открыть')}</a></p>`;
|
|
}
|
|
|
|
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),
|
|
});
|
|
}
|