import nodemailer from 'nodemailer'; import type { DatabaseSync } from 'node:sqlite'; import type { Alert } from '../alerts/AlertEngine.ts'; interface SmtpConfigRow { id: string; host: string; port: number; secure: number; user: string | null; pass_enc: string | null; from_name: string; from_email: string; enabled: number; updated_at: string; } export interface SmtpConfig { host: string; port: number; secure: boolean; user: string | null; passEnc: string | null; fromName: string; fromEmail: string; enabled: boolean; } export function readSmtpConfig(db: DatabaseSync): SmtpConfig | null { const row = db.prepare('SELECT * FROM smtp_config WHERE id = ?').get('singleton') as SmtpConfigRow | undefined; if (!row) return null; return { host: row.host, port: row.port, secure: row.secure === 1, user: row.user ?? null, passEnc: row.pass_enc ?? null, fromName: row.from_name, fromEmail: row.from_email, enabled: row.enabled === 1, }; } function buildAlertEmailHtml(alert: Alert): string { const severityColors: Record = { info: '#3b82f6', warning: '#f59e0b', critical: '#ef4444', }; const color = severityColors[alert.severity] ?? '#6b7280'; return `

Investor Flow

${alert.severity}

${alert.title}

${alert.description.replace(/\n/g, '
')}

${alert.symbol ? `` : ''}
Symbol:${alert.symbol}
Time:${new Date(alert.createdAt).toLocaleString()}
Type:${alert.type.replace(/_/g, ' ')}

This alert was sent by Investor Flow. You can manage your alert subscriptions in the app.

`; } export function getUserEmail(db: DatabaseSync, userId: string): string | null { const row = db.prepare('SELECT email FROM users WHERE id = ?').get(userId) as { email: string } | undefined; return row?.email ?? null; } export async function sendAlertEmail( db: DatabaseSync, alert: Alert, ): Promise { const config = readSmtpConfig(db); if (!config || !config.enabled) return false; const userEmail = getUserEmail(db, alert.userId); if (!userEmail) return false; // Check rate limit: 1 per alert_type per symbol per hour. const hourAgo = new Date(Date.now() - 3600000).toISOString(); const sent = db.prepare( `SELECT COUNT(*) as count FROM alert_events WHERE user_id = ? AND type = ? AND symbol = ? AND created_at > ?`, ).get(alert.userId, alert.type, alert.symbol ?? null, hourAgo) as { count: number }; if (sent.count > 1) return false; try { const transporter = nodemailer.createTransport({ host: config.host, port: config.port, secure: config.secure, auth: config.user && config.passEnc ? { user: config.user, pass: config.passEnc } : undefined, }); await transporter.sendMail({ from: `"${config.fromName}" <${config.fromEmail}>`, to: userEmail, subject: `[${alert.severity.toUpperCase()}] ${alert.title}`, html: buildAlertEmailHtml(alert), }); console.log(`[alert:email] sent ${alert.type} alert to ${userEmail}`); return true; } catch (err) { console.error(`[alert:email] failed to send to ${userEmail}:`, err); return false; } }