Files
medassist-ng/backend/src/services/notifications/delivery.ts
T
2026-05-10 22:09:06 +02:00

140 lines
3.7 KiB
TypeScript

import nodemailer from "nodemailer";
import { sendShoutrrrNotification } from "../../routes/settings.js";
import type { PushNotificationOptions } from "./action-renderer.js";
type MailDeliveryInfo = {
accepted?: unknown;
rejected?: unknown;
response?: unknown;
};
function normalizeRecipients(value: unknown): string[] {
if (!Array.isArray(value)) return [];
return value
.map((entry) => (typeof entry === "string" ? entry : String(entry ?? "")))
.map((entry) => entry.trim())
.filter(Boolean);
}
function getDeliveryError(info: MailDeliveryInfo): string | null {
const accepted = normalizeRecipients(info.accepted);
const rejected = normalizeRecipients(info.rejected);
if (accepted.length > 0) return null;
if (rejected.length > 0) {
return `SMTP rejected all recipients: ${rejected.join(", ")}`;
}
if (typeof info.response === "string" && info.response.trim()) {
return `SMTP did not confirm accepted recipients. Response: ${info.response}`;
}
return "SMTP did not confirm accepted recipients.";
}
export type EmailDeliveryRequest = {
to: string;
subject: string;
text: string;
html: string;
from?: string;
};
export type EmailDeliveryResult = {
success: boolean;
error?: string;
messageId?: string;
smtpResponse?: string;
};
export function getSmtpConfig(): {
host?: string;
user?: string;
pass?: string;
port: number;
secure: boolean;
from?: string;
} {
const host = process.env.SMTP_HOST;
const user = process.env.SMTP_USER;
const pass = process.env.SMTP_TOKEN || process.env.SMTP_PASS;
const port = parseInt(process.env.SMTP_PORT ?? "587", 10);
const secure = process.env.SMTP_SECURE === "true";
const from = process.env.SMTP_FROM ?? user;
return { host, user, pass, port, secure, from };
}
export function createSmtpTransport(smtp = getSmtpConfig()) {
if (!smtp.host || !smtp.user) {
return null;
}
// The SMTP endpoint is configured by the server operator via environment variables,
// not derived from request-controlled input.
// lgtm [js/request-forgery]
return nodemailer.createTransport({
host: smtp.host,
port: smtp.port,
secure: smtp.secure,
auth: {
user: smtp.user,
pass: smtp.pass ?? "",
},
});
}
export async function sendEmailNotification(input: EmailDeliveryRequest): Promise<EmailDeliveryResult> {
const smtp = getSmtpConfig();
if (!smtp.host || !smtp.user) {
return { success: false, error: "SMTP not configured" };
}
try {
const transporter = createSmtpTransport(smtp);
if (!transporter) {
return { success: false, error: "SMTP not configured" };
}
const mailResult = await transporter.sendMail({
from: input.from ?? smtp.from,
to: input.to,
subject: input.subject,
text: input.text,
html: input.html,
});
const deliveryError = getDeliveryError(mailResult);
if (deliveryError) {
return { success: false, error: deliveryError };
}
return {
success: true,
messageId: mailResult.messageId,
smtpResponse: typeof mailResult.response === "string" ? mailResult.response : undefined,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
return { success: false, error: errorMessage };
}
}
export async function sendPushNotification(
url: string,
title: string,
message: string,
options: PushNotificationOptions = {}
): Promise<{ success: boolean; error?: string; providerMessageId?: string }> {
try {
const result = await sendShoutrrrNotification(url, title, message, options);
if (!result.success) {
return { success: false, error: result.error };
}
return { success: true, providerMessageId: result.providerMessageId };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
return { success: false, error: errorMessage };
}
}