Files
zeropost-engine/src/services/emailService.js
T
Ник (Claude) c40ef90ad1 feat: SMTP, maintenance mode, blog topic bank UI
8. SMTP: emailService.js (nodemailer), templates (welcome/payment/low_credits)
   /api/admin/email/test — тест отправки
   app_settings category=smtp (HOST/PORT/USER/PASS/FROM/ENABLED)
9. Maintenance mode: middleware в index.js, MAINTENANCE_MODE в engine settings
   При true → 503 для всех запросов кроме /uploads и /api/settings
10. Blog topic bank:
   DB: blog_topics(category,topic,is_used,source,priority)
   40 тем мигрированы из хардкода (source=hardcoded)
   autogen.js: getNextTopic берёт из DB, fallback на TOPIC_BANK
   admin API: GET/POST /blog-topics, DELETE /:id, POST /generate (AI +10)
2026-06-13 11:45:23 +03:00

112 lines
4.2 KiB
JavaScript

/**
* emailService.js — отправка email уведомлений через SMTP.
* Использует nodemailer. Настройки из app_settings (category=smtp).
*/
const nodemailer = require('nodemailer');
const settings = require('./settings');
let _transporter = null;
let _configHash = null;
async function getTransporter() {
const [host, port, user, pass, from, enabled] = await Promise.all([
settings.get('SMTP_HOST', ''),
settings.get('SMTP_PORT', '587'),
settings.get('SMTP_USER', ''),
settings.get('SMTP_PASS', ''),
settings.get('SMTP_FROM', 'ZeroPost <noreply@zeropost.ru>'),
settings.get('SMTP_ENABLED', 'false'),
]);
if (enabled !== 'true') return null;
if (!host || !user) return null;
const hash = `${host}:${port}:${user}:${pass}`;
if (_transporter && hash === _configHash) return _transporter;
_transporter = nodemailer.createTransport({
host, port: parseInt(port),
secure: parseInt(port) === 465,
auth: { user, pass },
tls: { rejectUnauthorized: false },
});
_configHash = hash;
return _transporter;
}
/**
* Отправить email.
* @param {string} to — адрес получателя
* @param {string} subject — тема
* @param {string} html — HTML тело
* @param {string} [text] — plain text fallback
*/
async function send({ to, subject, html, text }) {
const transporter = await getTransporter();
if (!transporter) {
console.log(`[Email] SMTP disabled or not configured, skip: ${subject}${to}`);
return { skipped: true };
}
const from = await settings.get('SMTP_FROM', 'ZeroPost <noreply@zeropost.ru>');
try {
const info = await transporter.sendMail({ from, to, subject, html, text });
console.log(`[Email] sent: ${subject}${to} (${info.messageId})`);
return { ok: true, messageId: info.messageId };
} catch (err) {
console.error(`[Email] send error: ${err.message}`);
return { error: err.message };
}
}
/**
* Шаблоны уведомлений
*/
const templates = {
welcome({ email, credits }) {
return {
subject: 'Добро пожаловать в ZeroPost!',
html: `
<div style="font-family:sans-serif;max-width:480px;margin:0 auto">
<h2>Привет! 👋</h2>
<p>Рады видеть тебя в ZeroPost.</p>
<p>На твой счёт зачислено <b>${credits} кредитов</b> для начала работы.</p>
<p><a href="https://app.zeropost.ru" style="color:#6366f1">Открыть приложение →</a></p>
<hr style="margin:24px 0;border:none;border-top:1px solid #eee">
<p style="color:#999;font-size:12px">ZeroPost · Автоматизация контента</p>
</div>
`,
};
},
payment_success({ amount, plan, email }) {
return {
subject: `✅ Оплата ${amount}₽ прошла успешно`,
html: `
<div style="font-family:sans-serif;max-width:480px;margin:0 auto">
<h2>Оплата подтверждена</h2>
<p>Тариф <b>${plan}</b> активирован.</p>
<p>Сумма: <b>${amount}₽</b></p>
<p><a href="https://app.zeropost.ru/billing" style="color:#6366f1">История платежей →</a></p>
<hr style="margin:24px 0;border:none;border-top:1px solid #eee">
<p style="color:#999;font-size:12px">ZeroPost · Автоматизация контента</p>
</div>
`,
};
},
low_credits({ credits, email }) {
return {
subject: '⚠️ Кредиты заканчиваются',
html: `
<div style="font-family:sans-serif;max-width:480px;margin:0 auto">
<h2>Осталось ${credits} кредитов</h2>
<p>Пополни баланс чтобы продолжить генерацию контента.</p>
<p><a href="https://app.zeropost.ru/plans" style="color:#6366f1">Выбрать тариф →</a></p>
<hr style="margin:24px 0;border:none;border-top:1px solid #eee">
<p style="color:#999;font-size:12px">ZeroPost · Автоматизация контента</p>
</div>
`,
};
},
};
module.exports = { send, templates };