75 lines
2.9 KiB
JavaScript
75 lines
2.9 KiB
JavaScript
// services/mailer.js
|
|
// Thin nodemailer wrapper for stage-change email notifications (Production
|
|
// module). SMTP credentials come from .env (SMTP_HOST/PORT/SECURE/USER/
|
|
// PASSWORD/FROM) — there is no SMTP config in the Admin UI. Until SMTP_HOST
|
|
// is set, sendMail() is a safe no-op (logs and resolves) so the rest of the
|
|
// notification pipeline (and the request that triggered it) never breaks
|
|
// because email isn't configured yet.
|
|
'use strict';
|
|
const nodemailer = require('nodemailer');
|
|
|
|
let _transporter = null;
|
|
|
|
function isConfigured() {
|
|
return !!process.env.SMTP_HOST;
|
|
}
|
|
|
|
function getTransporter() {
|
|
if (_transporter) return _transporter;
|
|
_transporter = nodemailer.createTransport({
|
|
host: process.env.SMTP_HOST,
|
|
port: parseInt(process.env.SMTP_PORT) || 587,
|
|
secure: String(process.env.SMTP_SECURE).toLowerCase() === 'true',
|
|
auth: process.env.SMTP_USER ? { user: process.env.SMTP_USER, pass: process.env.SMTP_PASSWORD } : undefined,
|
|
});
|
|
return _transporter;
|
|
}
|
|
|
|
const mailLog = () => require('./mailLogStore');
|
|
|
|
// Never throws — logs and resolves so a mail failure can't break the caller's
|
|
// request. Returns true if actually sent, false if skipped/failed. Every
|
|
// outcome (sent/failed/skipped) is also recorded to the Mail Log (see
|
|
// services/mailLogStore.js, viewer at /mail-logs) so delivery can be
|
|
// confirmed without digging through console output.
|
|
async function sendMail({ to, subject, html, text }) {
|
|
const recipients = Array.isArray(to) ? to.filter(Boolean) : [to].filter(Boolean);
|
|
if (!recipients.length) return false;
|
|
if (!isConfigured()) {
|
|
console.log(`[MAILER] SMTP not configured — skipping email "${subject}" to ${recipients.join(', ')}`);
|
|
await mailLog().record({ to: recipients, subject, status: 'SKIPPED', error: 'SMTP not configured' });
|
|
return false;
|
|
}
|
|
try {
|
|
const info = await getTransporter().sendMail({
|
|
from: process.env.SMTP_FROM || process.env.SMTP_USER,
|
|
to: recipients.join(', '),
|
|
subject,
|
|
html,
|
|
text: text || undefined,
|
|
});
|
|
console.log(`[MAILER] sent "${subject}" to ${recipients.join(', ')} (messageId: ${info.messageId})`);
|
|
await mailLog().record({ to: recipients, subject, status: 'SENT', messageId: info.messageId });
|
|
return true;
|
|
} catch (e) {
|
|
console.error(`[MAILER] send FAILED for "${subject}" to ${recipients.join(', ')}:`, e.message);
|
|
await mailLog().record({ to: recipients, subject, status: 'FAILED', error: e.message });
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Checks the SMTP connection/credentials without sending anything — surfaces
|
|
// the real error (bad host, auth rejected, etc.) rather than a generic
|
|
// send failure.
|
|
async function verifyConnection() {
|
|
if (!isConfigured()) return { ok: false, message: 'SMTP_HOST is not set in .env' };
|
|
try {
|
|
await getTransporter().verify();
|
|
return { ok: true };
|
|
} catch (e) {
|
|
return { ok: false, message: e.message };
|
|
}
|
|
}
|
|
|
|
module.exports = { sendMail, isConfigured, verifyConnection };
|