// Outbound email (meeting invites to external participants — #4). Config-gated: does nothing unless // SMTP_* env vars are set (SMTP_ENABLED). Credentials come from config (env), never the client. // // Kept intentionally small: one lazily-created nodemailer transport + a couple of helpers. Sending is // fire-and-forget from the caller's perspective (we log failures but never throw into request handlers, // so a mail outage can't break scheduling). const cfg = require('./config'); let _transport = null; let _nodemailer = null; function transport() { if (!cfg.SMTP_ENABLED) return null; if (_transport) return _transport; try { _nodemailer = _nodemailer || require('nodemailer'); _transport = _nodemailer.createTransport({ host: cfg.SMTP_HOST, port: cfg.SMTP_PORT, secure: cfg.SMTP_SECURE, // true for 465, false for 587/STARTTLS auth: { user: cfg.SMTP_USER, pass: cfg.SMTP_PASS }, }); } catch (e) { console.warn('[mailer] transport init failed:', e && e.message); _transport = null; } return _transport; } const isEnabled = () => cfg.SMTP_ENABLED; function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); } // Send one email. Returns a promise that resolves to true/false — never rejects (callers shouldn't // have to try/catch around scheduling). `to` may be a string or an array of addresses. function send({ to, subject, html, text }) { return new Promise((resolve) => { const t = transport(); if (!t) { console.warn('[mailer] SMTP not configured — skipping email:', subject); return resolve(false); } const list = Array.isArray(to) ? to.filter(Boolean) : [to].filter(Boolean); if (!list.length) return resolve(false); t.sendMail({ from: cfg.SMTP_FROM, to: list.join(', '), subject, text: text || '', html: html || undefined }, (err) => { if (err) { console.warn('[mailer] sendMail failed:', err && err.message); return resolve(false); } resolve(true); }); }); } // Branded meeting-invite email. `link` is the guest join URL; `when` a human-readable time string. function meetingInviteEmail({ title, when, link, host, description }) { const subject = 'Meeting invite: ' + title; const text = [ (host ? host + ' invited you to a meeting.' : 'You have a meeting invite.'), '', 'Title: ' + title, when ? ('When: ' + when) : '', description ? ('Details: ' + description) : '', '', 'Join: ' + link, '', 'No account needed — just open the link and enter your name.', ].filter(Boolean).join('\n'); const html = `
Biz Connect
Meeting invitation

${host ? esc(host) + ' invited you to a meeting.' : 'You have a meeting invite.'}

${esc(title)}
${when ? `
🗓 ${esc(when)}
` : ''} ${description ? `
${esc(description)}
` : ''} Join the meeting
No account needed — open the link and enter your name.
${esc(link)}
`; return { subject, text, html }; } module.exports = { send, meetingInviteEmail, isEnabled };