/** * BetterDesk Console — Email Notification Service * * SMTP-based email delivery for alert notifications. * Configuration is stored in the database (admin-configurable). * * @author UNITRONIX * @version 1.0.0 */ 'use strict'; let nodemailer; try { nodemailer = require('nodemailer'); } catch (_) { // nodemailer is optional — alerts still work without email nodemailer = null; } const { getAdapter } = require('./dbAdapter'); const appConfig = require('../config/config'); let _transporter = null; let _cachedConfig = null; function countRecipients(to) { if (Array.isArray(to)) return to.filter(Boolean).length; return String(to || '') .split(',') .map((recipient) => recipient.trim()) .filter(Boolean) .length; } /** * Load SMTP configuration from DB. * Falls back to environment variables. */ async function loadSmtpConfig() { const adapter = getAdapter(); try { const cfg = await adapter.getSetting('smtp_config'); if (cfg) { const parsed = typeof cfg === 'string' ? JSON.parse(cfg) : cfg; if (parsed.host) return parsed; } } catch (_) { /* ignore */ } // Env fallback if (process.env.SMTP_HOST) { return { host: process.env.SMTP_HOST, port: parseInt(process.env.SMTP_PORT || '587', 10), secure: process.env.SMTP_SECURE === 'true', user: process.env.SMTP_USER || '', pass: process.env.SMTP_PASS || '', from: process.env.SMTP_FROM || 'betterdesk@localhost', alert_email: process.env.SMTP_ALERT_EMAIL || '', tlsVerify: appConfig.smtpTlsVerify, }; } return null; } function resolveTlsVerify(smtpConfig) { if (smtpConfig && smtpConfig.tlsVerify !== undefined) { return !!smtpConfig.tlsVerify; } return appConfig.smtpTlsVerify; } /** * Get or create the SMTP transporter. */ async function getTransporter() { if (!nodemailer) { console.warn('[Email] nodemailer not installed — email disabled'); return null; } const smtpConfig = await loadSmtpConfig(); if (!smtpConfig) return null; // Reuse cached transporter if config unchanged if (_transporter && _cachedConfig && JSON.stringify(_cachedConfig) === JSON.stringify(smtpConfig)) { return _transporter; } _transporter = nodemailer.createTransport({ host: smtpConfig.host, port: smtpConfig.port || 587, secure: smtpConfig.secure || false, auth: (smtpConfig.user && smtpConfig.pass) ? { user: smtpConfig.user, pass: smtpConfig.pass } : undefined, tls: { rejectUnauthorized: resolveTlsVerify(smtpConfig) }, }); _cachedConfig = smtpConfig; console.log(`[Email] SMTP transporter configured: ${smtpConfig.host}:${smtpConfig.port}`); return _transporter; } /** * Send an email notification. */ async function sendEmail({ to, subject, text, html }) { const transporter = await getTransporter(); if (!transporter) { console.warn('[Email] Cannot send — no SMTP config'); return false; } const config = _cachedConfig; try { await transporter.sendMail({ from: config.from || 'betterdesk@localhost', to, subject, text, html, }); console.log(`[Email] Sent message to ${countRecipients(to)} recipient(s)`); return true; } catch (err) { console.error(`[Email] Send failed: ${err.message}`); return false; } } /** * Send an alert notification email. */ async function sendAlertEmail(alert, rule) { const subject = `[BetterDesk Alert] ${rule.name} — ${alert.severity.toUpperCase()}`; const text = [ `Alert: ${rule.name}`, `Severity: ${alert.severity}`, `Device: ${alert.device_id || 'N/A'}`, `Message: ${alert.message}`, `Triggered at: ${alert.triggered_at}`, '', `Rule: ${rule.description || rule.condition_type} ${rule.condition_op} ${rule.condition_value}`, ].join('\n'); const html = `
| Severity | ${alert.severity} |
| Device | ${alert.device_id || 'N/A'} |
| Message | ${escapeHtml(alert.message)} |
| Time | ${alert.triggered_at} |
Rule: ${escapeHtml(rule.description || '')}
| Device ID | ${escapeHtml(helpRequest.device_id)} |
| Folder | ${escapeHtml(folderName)} |
| Message | ${escapeHtml(message)} |