Merge pull request #4969 from Dokploy/fix/server-threshold-email

fix(notifications): send server threshold alerts through email, resend, gotify and ntfy

(cherry picked from commit 218687f46d)

[skip ci]
This commit is contained in:
Narciso E. Núñez Arias 2026-08-10 23:29:21 -04:00 committed by Dokploy Bot
parent bd92dfb8c1
commit 19958ee9d9
2 changed files with 162 additions and 0 deletions

View File

@ -0,0 +1,91 @@
import {
Body,
Container,
Head,
Heading,
Html,
Img,
Preview,
Section,
Tailwind,
Text,
} from "@react-email/components";
import { emailTailwindConfig } from "../tailwind-config";
export type TemplateProps = {
serverName: string;
type: "CPU" | "Memory";
value: string;
threshold: string;
message: string;
date: string;
};
export const ServerThresholdEmail = ({
serverName = "my-server",
type = "CPU",
value = "95.00",
threshold = "90.00",
message = "Resource usage exceeded the configured threshold",
date = "2023-05-01T00:00:00.000Z",
}: TemplateProps) => {
const previewText = `Server ${type} alert for ${serverName} ⚠️`;
return (
<Html>
<Preview>{previewText}</Preview>
<Tailwind config={emailTailwindConfig}>
<Head />
<Body className="bg-white my-auto mx-auto font-sans px-2">
<Container className="border border-solid border-[#eaeaea] rounded-lg my-[40px] mx-auto p-[20px] max-w-[465px]">
<Section className="mt-[32px]">
<Img
src={
"https://raw.githubusercontent.com/Dokploy/dokploy/refs/heads/canary/apps/dokploy/logo.png"
}
width="100"
height="50"
alt="Dokploy"
className="my-0 mx-auto"
/>
</Section>
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
Server {type} alert for <strong>{serverName}</strong>
</Heading>
<Text className="text-black text-[14px] leading-[24px]">
Hello,
</Text>
<Text className="text-black text-[14px] leading-[24px]">
The {type} usage on <strong>{serverName}</strong> exceeded the
configured threshold.
</Text>
<Section className="flex text-black text-[14px] leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
<Text className="!leading-3 font-bold">Details: </Text>
<Text className="!leading-3">
Server Name: <strong>{serverName}</strong>
</Text>
<Text className="!leading-3">
Type: <strong>{type}</strong>
</Text>
<Text className="!leading-3">
Current Value: <strong>{value}%</strong>
</Text>
<Text className="!leading-3">
Threshold: <strong>{threshold}%</strong>
</Text>
<Text className="!leading-3">
Date: <strong>{date}</strong>
</Text>
</Section>
<Section className="flex text-black text-[14px] mt-4 leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
<Text className="!leading-3 font-bold">Message: </Text>
<Text className="text-[12px] leading-[24px]">{message}</Text>
</Section>
</Container>
</Body>
</Tailwind>
</Html>
);
};
export default ServerThresholdEmail;

View File

@ -1,12 +1,18 @@
import { render } from "@react-email/components";
import { and, eq } from "drizzle-orm";
import { db } from "../../db";
import { notifications } from "../../db/schema";
import ServerThresholdEmail from "../../emails/emails/server-threshold";
import {
sendCustomNotification,
sendDiscordNotification,
sendEmailNotification,
sendGotifyNotification,
sendLarkNotification,
sendMattermostNotification,
sendNtfyNotification,
sendPushoverNotification,
sendResendNotification,
sendSlackNotification,
sendTeamsNotification,
sendTelegramNotification,
@ -39,6 +45,9 @@ export const sendServerThresholdNotifications = async (
discord: true,
telegram: true,
slack: true,
resend: true,
gotify: true,
ntfy: true,
mattermost: true,
custom: true,
lark: true,
@ -52,9 +61,13 @@ export const sendServerThresholdNotifications = async (
for (const notification of notificationList) {
const {
email,
discord,
telegram,
slack,
resend,
gotify,
ntfy,
mattermost,
custom,
lark,
@ -63,6 +76,64 @@ export const sendServerThresholdNotifications = async (
} = notification;
try {
if (email || resend) {
const template = await render(
ServerThresholdEmail({
serverName: payload.ServerName,
type: payload.Type,
value: payload.Value.toFixed(2),
threshold: payload.Threshold.toFixed(2),
message: payload.Message,
date: date.toLocaleString(),
}),
);
if (email) {
await sendEmailNotification(
email,
`Server ${payload.Type} alert for ${payload.ServerName}`,
template,
);
}
if (resend) {
await sendResendNotification(
resend,
`Server ${payload.Type} alert for ${payload.ServerName}`,
template,
);
}
}
if (gotify) {
const decorate = (decoration: string, text: string) =>
`${gotify.decoration ? decoration : ""} ${text}\n`;
await sendGotifyNotification(
gotify,
decorate("⚠️", `Server ${payload.Type} Alert`),
`${decorate("🏷️", `Server: ${payload.ServerName}`)}` +
`${decorate("📊", `Current Value: ${payload.Value.toFixed(2)}%`)}` +
`${decorate("⚠️", `Threshold: ${payload.Threshold.toFixed(2)}%`)}` +
`${decorate("📜", `Message: ${payload.Message}`)}` +
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}`,
);
}
if (ntfy) {
await sendNtfyNotification(
ntfy,
`Server ${payload.Type} Alert`,
"warning",
"",
`🏷Server: ${payload.ServerName}\n` +
`📊Current Value: ${payload.Value.toFixed(2)}%\n` +
`Threshold: ${payload.Threshold.toFixed(2)}%\n` +
`📜Message: ${payload.Message}\n` +
`🕒Date: ${date.toLocaleString()}`,
);
}
if (discord) {
const decorate = (decoration: string, text: string) =>
`${discord.decoration ? decoration : ""} ${text}`.trim();