import { db } from "@dokploy/server/db";import { notifications } from "@dokploy/server/db/schema";import BuildFailedEmail from "@dokploy/server/emails/emails/build-failed";import { render } from "@react-email/components";import { format } from "date-fns";import { and, eq } from "drizzle-orm";import { sendCustomNotification, sendDiscordNotification, sendEmailNotification, sendGotifyNotification, sendLarkNotification, sendMattermostNotification, sendNtfyNotification, sendPushoverNotification, sendResendNotification, sendSlackNotification, sendTeamsNotification, sendTelegramNotification,} from "./utils"; interface Props { projectName: string; applicationName: string; applicationType: string; errorMessage: string; buildLink: string; organizationId: string;} export const sendBuildErrorNotifications = async ({ projectName, applicationName, applicationType, errorMessage, buildLink, organizationId,}: Props) => { const date = new Date(); const unixDate = ~~(Number(date) / 1000); const notificationList = await db.query.notifications.findMany({ where: and( eq(notifications.appBuildError, true), eq(notifications.organizationId, organizationId), ), with: { email: true, discord: true, telegram: true, slack: true, resend: true, gotify: true, ntfy: true, mattermost: true, custom: true, lark: true, pushover: true, teams: true, }, }); for (const notification of notificationList) { const { email, resend, discord, telegram, slack, gotify, ntfy, mattermost, custom, lark, pushover, teams, } = notification; try { if (email || resend) { const template = await render( BuildFailedEmail({ projectName, applicationName, applicationType, errorMessage: errorMessage, buildLink, date: date.toLocaleString(), }), ).catch(); if (email) { await sendEmailNotification( email, "Build failed for dokploy", template, ); } if (resend) { await sendResendNotification( resend, "Build failed for dokploy", template, ); } } if (discord) { const decorate = (decoration: string, text: string) => `${discord.decoration ? decoration : ""} ${text}`.trim(); const limitCharacter = 800; const truncatedErrorMessage = errorMessage.substring(0, limitCharacter); await sendDiscordNotification(discord, { title: decorate(">", "`⚠️` Build Failed"), color: 0xed4245, fields: [ { name: decorate("`🛠️`", "Project"), value: projectName, inline: true, }, { name: decorate("`⚙️`", "Application"), value: applicationName, inline: true, }, { name: decorate("`❔`", "Type"), value: applicationType, inline: true, }, { name: decorate("`📅`", "Date"), value: `<t:${unixDate}:D>`, inline: true, }, { name: decorate("`⌚`", "Time"), value: `<t:${unixDate}:t>`, inline: true, }, { name: decorate("`❓`", "Type"), value: "Failed", inline: true, }, { name: decorate("`⚠️`", "Error Message"), value: `\`\`\`${truncatedErrorMessage}\`\`\``, }, { name: decorate("`🧷`", "Build Link"), value: `[Click here to access build link](${buildLink})`, }, ], timestamp: date.toISOString(), footer: { text: "Dokploy Build Notification", }, }); } if (gotify) { const decorate = (decoration: string, text: string) => `${gotify.decoration ? decoration : ""} ${text}\n`; await sendGotifyNotification( gotify, decorate("⚠️", "Build Failed"), `${decorate("🛠️", `Project: ${projectName}`)}` + `${decorate("⚙️", `Application: ${applicationName}`)}` + `${decorate("❔", `Type: ${applicationType}`)}` + `${decorate("🕒", `Date: ${date.toLocaleString()}`)}` + `${decorate("⚠️", `Error:\n${errorMessage}`)}` + `${decorate("🔗", `Build details:\n${buildLink}`)}`, ); } if (ntfy) { await sendNtfyNotification( ntfy, "Build Failed", "warning", `view, Build details, ${buildLink}, clear=true;`, `🛠️Project: ${projectName}\n` + `⚙️Application: ${applicationName}\n` + `❔Type: ${applicationType}\n` + `🕒Date: ${date.toLocaleString()}\n` + `⚠️Error:\n${errorMessage}`, ); } if (telegram) { const inlineButton = [ [ { text: "Deployment Logs", url: buildLink, }, ], ]; await sendTelegramNotification( telegram, `<b>⚠️ Build Failed</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`, inlineButton, ); } if (slack) { const { channel } = slack; await sendSlackNotification(slack, { channel: channel, attachments: [ { color: "#FF0000", pretext: ":warning: *Build Failed*", fields: [ { title: "Project", value: projectName, short: true, }, { title: "Application", value: applicationName, short: true, }, { title: "Type", value: applicationType, short: true, }, { title: "Time", value: date.toLocaleString(), short: true, }, { title: "Error", value: `\`\`\`${errorMessage}\`\`\``, short: false, }, { title: "Details", value: `<${buildLink}|View Build Details>`, short: false, }, ], mrkdwn_in: ["fields"], }, ], }); } if (mattermost) { await sendMattermostNotification(mattermost, { text: `:warning: **Build Failed** **Project:** ${projectName}**Application:** ${applicationName}**Type:** ${applicationType}**Time:** ${date.toLocaleString()} **Error:**\`\`\`${errorMessage}\`\`\` [View Build Details](${buildLink})`, channel: mattermost.channel, username: mattermost.username || "Dokploy Bot", }); } if (custom) { await sendCustomNotification(custom, { title: "Build Error", message: "Build failed with errors", projectName, applicationName, applicationType, errorMessage, buildLink, timestamp: date.toISOString(), date: date.toLocaleString(), status: "error", type: "build", }); } if (lark) { const limitCharacter = 800; const truncatedErrorMessage = errorMessage.substring(0, limitCharacter); await sendLarkNotification(lark, { msg_type: "interactive", card: { schema: "2.0", config: { update_multi: true, style: { text_size: { normal_v2: { default: "normal", pc: "normal", mobile: "heading", }, }, }, }, header: { title: { tag: "plain_text", content: "⚠️ Build Failed", }, subtitle: { tag: "plain_text", content: "", }, template: "red", padding: "12px 12px 12px 12px", }, body: { direction: "vertical", padding: "12px 12px 12px 12px", elements: [ { tag: "column_set", columns: [ { tag: "column", width: "weighted", elements: [ { tag: "markdown", content: `**Project:**\n${projectName}`, text_align: "left", text_size: "normal_v2", }, { tag: "markdown", content: `**Type:**\n${applicationType}`, text_align: "left", text_size: "normal_v2", }, { tag: "markdown", content: `**Error Message:**\n\`\`\`\n${truncatedErrorMessage}\n\`\`\``, text_align: "left", text_size: "normal_v2", }, ], vertical_align: "top", weight: 1, }, { tag: "column", width: "weighted", elements: [ { tag: "markdown", content: `**Application:**\n${applicationName}`, text_align: "left", text_size: "normal_v2", }, { tag: "markdown", content: `**Date:**\n${format(date, "PP pp")}`, text_align: "left", text_size: "normal_v2", }, ], vertical_align: "top", weight: 1, }, ], }, { tag: "button", text: { tag: "plain_text", content: "View Build Details", }, type: "danger", width: "default", size: "medium", behaviors: [ { type: "open_url", default_url: buildLink, pc_url: "", ios_url: "", android_url: "", }, ], margin: "0px 0px 0px 0px", }, ], }, }, }); } if (pushover) { await sendPushoverNotification( pushover, "Build Failed", `Project: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${date.toLocaleString()}\nError: ${errorMessage}`, ); } if (teams) { const limitCharacter = 800; const truncatedErrorMessage = errorMessage.substring(0, limitCharacter); await sendTeamsNotification(teams, { title: "⚠️ Build Failed", facts: [ { name: "Project", value: projectName }, { name: "Application", value: applicationName }, { name: "Type", value: applicationType }, { name: "Date", value: format(date, "PP pp") }, { name: "Error Message", value: truncatedErrorMessage }, ], potentialAction: { type: "Action.OpenUrl", title: "View Build Details", url: buildLink, }, }); } } catch (error) { console.log(error); } }};
import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema";
import BuildFailedEmail from "@dokploy/server/emails/emails/build-failed";
import { render } from "@react-email/components";
import { format } from "date-fns";
import { and, eq } from "drizzle-orm";
import {
sendCustomNotification,
sendDiscordNotification,
sendEmailNotification,
sendGotifyNotification,
sendLarkNotification,
sendMattermostNotification,
sendNtfyNotification,
sendPushoverNotification,
sendResendNotification,
sendSlackNotification,
sendTeamsNotification,
sendTelegramNotification,
} from "./utils";
interface Props {
projectName: string;
applicationName: string;
applicationType: string;
errorMessage: string;
buildLink: string;
organizationId: string;
}
export const sendBuildErrorNotifications = async ({
projectName,
applicationName,
applicationType,
errorMessage,
buildLink,
organizationId,
}: Props) => {
const date = new Date();
const unixDate = ~~(Number(date) / 1000);
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.appBuildError, true),
eq(notifications.organizationId, organizationId),
),
with: {
email: true,
discord: true,
telegram: true,
slack: true,
resend: true,
gotify: true,
ntfy: true,
mattermost: true,
custom: true,
lark: true,
pushover: true,
teams: true,
},
});
for (const notification of notificationList) {
const {
email,
resend,
discord,
telegram,
slack,
gotify,
ntfy,
mattermost,
custom,
lark,
pushover,
teams,
} = notification;
try {
if (email || resend) {
const template = await render(
BuildFailedEmail({
projectName,
applicationName,
applicationType,
errorMessage: errorMessage,
buildLink,
date: date.toLocaleString(),
}),
).catch();
if (email) {
await sendEmailNotification(
email,
"Build failed for dokploy",
template,
);
}
if (resend) {
await sendResendNotification(
resend,
"Build failed for dokploy",
template,
);
}
}
if (discord) {
const decorate = (decoration: string, text: string) =>
`${discord.decoration ? decoration : ""} ${text}`.trim();
const limitCharacter = 800;
const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
await sendDiscordNotification(discord, {
title: decorate(">", "`⚠️` Build Failed"),
color: 0xed4245,
fields: [
{
name: decorate("`🛠️`", "Project"),
value: projectName,
inline: true,
},
{
name: decorate("`⚙️`", "Application"),
value: applicationName,
inline: true,
},
{
name: decorate("`❔`", "Type"),
value: applicationType,
inline: true,
},
{
name: decorate("`📅`", "Date"),
value: `<t:${unixDate}:D>`,
inline: true,
},
{
name: decorate("`⌚`", "Time"),
value: `<t:${unixDate}:t>`,
inline: true,
},
{
name: decorate("`❓`", "Type"),
value: "Failed",
inline: true,
},
{
name: decorate("`⚠️`", "Error Message"),
value: `\`\`\`${truncatedErrorMessage}\`\`\``,
},
{
name: decorate("`🧷`", "Build Link"),
value: `[Click here to access build link](${buildLink})`,
},
],
timestamp: date.toISOString(),
footer: {
text: "Dokploy Build Notification",
},
});
}
if (gotify) {
const decorate = (decoration: string, text: string) =>
`${gotify.decoration ? decoration : ""} ${text}\n`;
await sendGotifyNotification(
gotify,
decorate("⚠️", "Build Failed"),
`${decorate("🛠️", `Project: ${projectName}`)}` +
`${decorate("⚙️", `Application: ${applicationName}`)}` +
`${decorate("❔", `Type: ${applicationType}`)}` +
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}` +
`${decorate("⚠️", `Error:\n${errorMessage}`)}` +
`${decorate("🔗", `Build details:\n${buildLink}`)}`,
);
}
if (ntfy) {
await sendNtfyNotification(
ntfy,
"Build Failed",
"warning",
`view, Build details, ${buildLink}, clear=true;`,
`🛠️Project: ${projectName}\n` +
`⚙️Application: ${applicationName}\n` +
`❔Type: ${applicationType}\n` +
`🕒Date: ${date.toLocaleString()}\n` +
`⚠️Error:\n${errorMessage}`,
);
}
if (telegram) {
const inlineButton = [
[
{
text: "Deployment Logs",
url: buildLink,
},
],
];
await sendTelegramNotification(
telegram,
`<b>⚠️ Build Failed</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`,
inlineButton,
);
}
if (slack) {
const { channel } = slack;
await sendSlackNotification(slack, {
channel: channel,
attachments: [
{
color: "#FF0000",
pretext: ":warning: *Build Failed*",
fields: [
{
title: "Project",
value: projectName,
short: true,
},
{
title: "Application",
value: applicationName,
short: true,
},
{
title: "Type",
value: applicationType,
short: true,
},
{
title: "Time",
value: date.toLocaleString(),
short: true,
},
{
title: "Error",
value: `\`\`\`${errorMessage}\`\`\``,
short: false,
},
{
title: "Details",
value: `<${buildLink}|View Build Details>`,
short: false,
},
],
mrkdwn_in: ["fields"],
},
],
});
}
if (mattermost) {
await sendMattermostNotification(mattermost, {
text: `:warning: **Build Failed**
**Project:** ${projectName}
**Application:** ${applicationName}
**Type:** ${applicationType}
**Time:** ${date.toLocaleString()}
**Error:**
\`\`\`
${errorMessage}
\`\`\`
[View Build Details](${buildLink})`,
channel: mattermost.channel,
username: mattermost.username || "Dokploy Bot",
});
}
if (custom) {
await sendCustomNotification(custom, {
title: "Build Error",
message: "Build failed with errors",
projectName,
applicationName,
applicationType,
errorMessage,
buildLink,
timestamp: date.toISOString(),
date: date.toLocaleString(),
status: "error",
type: "build",
});
}
if (lark) {
const limitCharacter = 800;
const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
await sendLarkNotification(lark, {
msg_type: "interactive",
card: {
schema: "2.0",
config: {
update_multi: true,
style: {
text_size: {
normal_v2: {
default: "normal",
pc: "normal",
mobile: "heading",
},
},
},
},
header: {
title: {
tag: "plain_text",
content: "⚠️ Build Failed",
},
subtitle: {
tag: "plain_text",
content: "",
},
template: "red",
padding: "12px 12px 12px 12px",
},
body: {
direction: "vertical",
padding: "12px 12px 12px 12px",
elements: [
{
tag: "column_set",
columns: [
{
tag: "column",
width: "weighted",
elements: [
{
tag: "markdown",
content: `**Project:**\n${projectName}`,
text_align: "left",
text_size: "normal_v2",
},
{
tag: "markdown",
content: `**Type:**\n${applicationType}`,
text_align: "left",
text_size: "normal_v2",
},
{
tag: "markdown",
content: `**Error Message:**\n\`\`\`\n${truncatedErrorMessage}\n\`\`\``,
text_align: "left",
text_size: "normal_v2",
},
],
vertical_align: "top",
weight: 1,
},
{
tag: "column",
width: "weighted",
elements: [
{
tag: "markdown",
content: `**Application:**\n${applicationName}`,
text_align: "left",
text_size: "normal_v2",
},
{
tag: "markdown",
content: `**Date:**\n${format(date, "PP pp")}`,
text_align: "left",
text_size: "normal_v2",
},
],
vertical_align: "top",
weight: 1,
},
],
},
{
tag: "button",
text: {
tag: "plain_text",
content: "View Build Details",
},
type: "danger",
width: "default",
size: "medium",
behaviors: [
{
type: "open_url",
default_url: buildLink,
pc_url: "",
ios_url: "",
android_url: "",
},
],
margin: "0px 0px 0px 0px",
},
],
},
},
});
}
if (pushover) {
await sendPushoverNotification(
pushover,
"Build Failed",
`Project: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${date.toLocaleString()}\nError: ${errorMessage}`,
);
}
if (teams) {
const limitCharacter = 800;
const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
await sendTeamsNotification(teams, {
title: "⚠️ Build Failed",
facts: [
{ name: "Project", value: projectName },
{ name: "Application", value: applicationName },
{ name: "Type", value: applicationType },
{ name: "Date", value: format(date, "PP pp") },
{ name: "Error Message", value: truncatedErrorMessage },
],
potentialAction: {
type: "Action.OpenUrl",
title: "View Build Details",
url: buildLink,
},
});
}
} catch (error) {
console.log(error);
}
}
};