import type { custom, discord, email, gotify, lark, mattermost, ntfy, pushover, resend, slack, teams, telegram, } from "@dokploy/server/db/schema"; import nodemailer from "nodemailer"; import { Resend } from "resend"; export const sendEmailNotification = async ( connection: typeof email.$inferInsert, subject: string, htmlContent: string, attachments?: { filename: string; content: Buffer }[], ) => { try { const { smtpServer, smtpPort, username, password, fromAddress, toAddresses, } = connection; const transporter = nodemailer.createTransport({ host: smtpServer, port: smtpPort, auth: { user: username, pass: password }, }); await transporter.sendMail({ from: fromAddress, to: toAddresses.join(", "), subject, html: htmlContent, textEncoding: "base64", attachments, }); } catch (err) { console.log(err); throw new Error( `Failed to send email notification ${err instanceof Error ? err.message : "Unknown error"}`, ); } }; export const sendResendNotification = async ( connection: typeof resend.$inferInsert, subject: string, htmlContent: string, ) => { try { const client = new Resend(connection.apiKey); const result = await client.emails.send({ from: connection.fromAddress, to: connection.toAddresses, subject, html: htmlContent, }); if (result.error) { throw new Error(result.error.message); } } catch (err) { console.log(err); throw new Error( `Failed to send Resend notification ${err instanceof Error ? err.message : "Unknown error"}`, ); } }; export const sendDiscordNotification = async ( connection: typeof discord.$inferInsert, embed: any, ) => { try { const response = await fetch(connection.webhookUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ embeds: [embed] }), }); if (!response.ok) { throw new Error( `Failed to send discord notification ${response.statusText}`, ); } } catch (err) { console.log("error", err); throw new Error( `Failed to send discord notification ${err instanceof Error ? err.message : "Unknown error"}`, ); } }; export const sendTelegramNotification = async ( connection: typeof telegram.$inferInsert, messageText: string, inlineButton?: { text: string; url: string; }[][], ) => { try { const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`; await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ chat_id: connection.chatId, message_thread_id: connection.messageThreadId, text: messageText, parse_mode: "HTML", disable_web_page_preview: true, reply_markup: { inline_keyboard: inlineButton, }, }), }); } catch (err) { console.log(err); } }; export const sendSlackNotification = async ( connection: typeof slack.$inferInsert, message: any, ) => { try { const response = await fetch(connection.webhookUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(message), }); if (!response.ok) { throw new Error( `Failed to send slack notification ${response.statusText}`, ); } } catch (err) { console.log("error", err); throw new Error( `Failed to send slack notification ${err instanceof Error ? err.message : "Unknown error"}`, ); } }; export const sendGotifyNotification = async ( connection: typeof gotify.$inferInsert, title: string, message: string, ) => { const response = await fetch(`${connection.serverUrl}/message`, { method: "POST", headers: { "Content-Type": "application/json", "X-Gotify-Key": connection.appToken, }, body: JSON.stringify({ title: title, message: message, priority: connection.priority, extras: { "client::display": { contentType: "text/plain", }, }, }), }); if (!response.ok) { throw new Error( `Failed to send Gotify notification: ${response.statusText}`, ); } }; export const sendNtfyNotification = async ( connection: typeof ntfy.$inferInsert, title: string, tags: string, actions: string, message: string, ) => { const response = await fetch(`${connection.serverUrl}/${connection.topic}`, { method: "POST", headers: { ...(connection.accessToken && { Authorization: `Bearer ${connection.accessToken}`, }), "X-Priority": connection.priority?.toString() || "3", "X-Title": title, "X-Tags": tags, "X-Actions": actions, }, body: message, }); if (!response.ok) { throw new Error(`Failed to send ntfy notification: ${response.statusText}`); } }; export const sendMattermostNotification = async ( connection: typeof mattermost.$inferInsert, message: any, ) => { const payload = { ...message, // Only include username if it's provided and not empty ...(message.username?.trim() && { username: message.username }), // Only include channel if it's provided and not empty ...(message.channel?.trim() && { channel: `#${message.channel.replace("#", "")}`, }), }; const response = await fetch(connection.webhookUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!response.ok) { throw new Error( `Failed to send Mattermost notification: ${response.statusText}`, ); } }; export const sendCustomNotification = async ( connection: typeof custom.$inferInsert, payload: Record, ) => { try { // Merge default headers with custom headers (now already an object from jsonb) const headers: Record = { "Content-Type": "application/json", ...(connection.headers || {}), }; // Default body with payload const body = JSON.stringify(payload); const response = await fetch(connection.endpoint, { method: "POST", headers, body, }); if (!response.ok) { throw new Error( `Failed to send custom notification: ${response.statusText}`, ); } return response; } catch (error) { console.error("Error sending custom notification:", error); throw error; } }; export const sendLarkNotification = async ( connection: typeof lark.$inferInsert, message: any, ) => { try { await fetch(connection.webhookUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(message), }); } catch (err) { console.log(err); } }; export interface TeamsAdaptiveCardMessage { title: string; themeColor?: string; facts?: { name: string; value: string }[]; potentialAction?: { type: "Action.OpenUrl"; title: string; url: string }; } export const sendTeamsNotification = async ( connection: typeof teams.$inferInsert, message: TeamsAdaptiveCardMessage, ) => { try { const bodyElements: Record[] = [ { type: "TextBlock", text: message.title, size: "Medium", weight: "Bolder", wrap: true, }, ]; if (message.facts && message.facts.length > 0) { bodyElements.push({ type: "FactSet", facts: message.facts.map((f) => ({ title: f.name, value: f.value, })), }); } const cardContent: Record = { type: "AdaptiveCard", $schema: "http://adaptivecards.io/schemas/adaptive-card.json", version: "1.4", body: bodyElements, }; if (message.potentialAction) { cardContent.actions = [ { type: "Action.OpenUrl", title: message.potentialAction.title, url: message.potentialAction.url, }, ]; } const payload = { type: "message", attachments: [ { contentType: "application/vnd.microsoft.card.adaptive", content: cardContent, }, ], }; const response = await fetch(connection.webhookUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!response.ok) { throw new Error( `Failed to send Teams notification: ${response.statusText}`, ); } } catch (err) { console.log(err); throw new Error( `Failed to send Teams notification ${err instanceof Error ? err.message : "Unknown error"}`, ); } }; export const sendPushoverNotification = async ( connection: typeof pushover.$inferInsert, title: string, message: string, ) => { const formData = new URLSearchParams(); formData.append("token", connection.apiToken); formData.append("user", connection.userKey); formData.append("title", title); formData.append("message", message); formData.append("priority", connection.priority?.toString() || "0"); // For emergency priority (2), retry and expire are required if (connection.priority === 2) { formData.append("retry", connection.retry?.toString() || "30"); formData.append("expire", connection.expire?.toString() || "3600"); } const response = await fetch("https://api.pushover.net/1/messages.json", { method: "POST", body: formData, }); if (!response.ok) { throw new Error( `Failed to send Pushover notification: ${response.statusText}`, ); } };