From fbb0e940c644e51f69553cebe4895e8673bb80d6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 17 Feb 2026 01:47:57 +0000 Subject: [PATCH] emails --- .../service/infrastructure/emailService.ts | 1 + .../notificationProviders/email.ts | 83 +++++++++++++++++++ .../infrastructure/notificationsService.ts | 9 ++ 3 files changed, 93 insertions(+) diff --git a/server/src/service/infrastructure/emailService.ts b/server/src/service/infrastructure/emailService.ts index 1f46a080a..48653340a 100755 --- a/server/src/service/infrastructure/emailService.ts +++ b/server/src/service/infrastructure/emailService.ts @@ -65,6 +65,7 @@ class EmailService { passwordResetTemplate: this.loadTemplate("passwordReset"), hardwareIncidentTemplate: this.loadTemplate("hardwareIncident"), testEmailTemplate: this.loadTemplate("testEmailTemplate"), + unifiedNotificationTemplate: this.loadTemplate("unifiedNotification"), }; }; diff --git a/server/src/service/infrastructure/notificationProviders/email.ts b/server/src/service/infrastructure/notificationProviders/email.ts index fd15034d1..3c8ba5527 100644 --- a/server/src/service/infrastructure/notificationProviders/email.ts +++ b/server/src/service/infrastructure/notificationProviders/email.ts @@ -3,6 +3,7 @@ import type { Monitor, Notification, MonitorStatusResponse } from "@/types/index import { INotificationProvider } from "@/service/index.js"; import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; import { buildHardwareAlerts, buildHardwareEmail, buildEmail, buildTestEmail } from "@/service/infrastructure/notificationProviders/utils.js"; +import type { NotificationMessage } from "@/types/notificationMessage.js"; export class EmailProvider implements INotificationProvider { private emailService: any; @@ -90,4 +91,86 @@ export class EmailProvider implements INotificationProvider { } return true; } + + async sendMessage(notification: Notification, message: NotificationMessage): Promise { + if (!notification.address) { + return false; + } + + const subject = this.buildSubject(message); + const html = await this.buildEmailFromMessage(message); + + const messageId = await this.emailService.sendEmail(notification.address, subject, html); + if (!messageId) { + this.logger.warn({ + message: "Email notification failed via sendMessage", + service: SERVICE_NAME, + method: "sendMessage", + }); + return false; + } + return true; + } + + private buildSubject(message: NotificationMessage): string { + switch (message.type) { + case "monitor_down": + return `Monitor ${message.monitor.name} is down`; + case "monitor_up": + return `Monitor ${message.monitor.name} is back up`; + case "threshold_breach": + return `Monitor ${message.monitor.name} threshold breached`; + case "threshold_resolved": + return `Monitor ${message.monitor.name} thresholds resolved`; + default: + return `Alert: ${message.monitor.name}`; + } + } + + private async buildEmailFromMessage(message: NotificationMessage): Promise { + const context = { + title: message.content.title, + summary: message.content.summary, + monitorName: message.monitor.name, + monitorUrl: message.monitor.url, + monitorType: message.monitor.type, + monitorStatus: message.monitor.status, + headerColor: this.getColorForSeverity(message.severity), + thresholds: message.content.thresholds, + details: message.content.details, + incidentUrl: message.content.incident?.url, + }; + + this.logger.info({ + message: "[DEBUG] Building email from message", + service: SERVICE_NAME, + method: "buildEmailFromMessage", + details: { context }, + }); + + return await this.emailService.buildEmail("unifiedNotificationTemplate", context); + const html = await this.emailService.buildEmail("unifiedNotificationTemplate", context); + + this.logger.info({ + message: "[DEBUG] Email HTML generated", + service: SERVICE_NAME, + method: "buildEmailFromMessage", + details: { + htmlLength: html?.length || 0, + htmlPreview: html?.substring(0, 200), + }, + }); + + return html; + } + + private getColorForSeverity(severity: string): string { + const colorMap: Record = { + critical: "red", + warning: "#f59e0b", + info: "#3b82f6", + success: "green", + }; + return colorMap[severity] ?? "#3b82f6"; + } } diff --git a/server/src/service/infrastructure/notificationsService.ts b/server/src/service/infrastructure/notificationsService.ts index a26a39d33..046560d61 100644 --- a/server/src/service/infrastructure/notificationsService.ts +++ b/server/src/service/infrastructure/notificationsService.ts @@ -117,6 +117,15 @@ export class NotificationsService implements INotificationsService { return await this.discordProvider.sendMessage(notification, notificationMessage); } + if (notification.type === "email" && this.emailProvider.sendMessage && notificationMessage) { + this.logger.info({ + message: "[NEW] Using sendMessage for email", + service: SERVICE_NAME, + method: "send", + }); + return await this.emailProvider.sendMessage(notification, notificationMessage); + } + // Fallback to existing sendAlert for all providers switch (notification.type) { case "email":