diff --git a/apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx b/apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx
index 3eac18522..958eb94c7 100644
--- a/apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx
+++ b/apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx
@@ -18,6 +18,7 @@ import {
PushoverIcon,
ResendIcon,
SlackIcon,
+ TeamsIcon,
TelegramIcon,
} from "@/components/icons/notification-icons";
import { Button } from "@/components/ui/button";
@@ -164,6 +165,12 @@ export const notificationSchema = z.discriminatedUnion("type", [
webhookUrl: z.string().min(1, { message: "Webhook URL is required" }),
})
.merge(notificationBaseSchema),
+ z
+ .object({
+ type: z.literal("teams"),
+ webhookUrl: z.string().min(1, { message: "Webhook URL is required" }),
+ })
+ .merge(notificationBaseSchema),
]);
export const notificationsMap = {
@@ -183,6 +190,10 @@ export const notificationsMap = {
icon: ,
label: "Lark",
},
+ teams: {
+ icon: ,
+ label: "Microsoft Teams",
+ },
email: {
icon: ,
label: "Email",
@@ -244,6 +255,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
api.notification.testNtfyConnection.useMutation();
const { mutateAsync: testLarkConnection, isLoading: isLoadingLark } =
api.notification.testLarkConnection.useMutation();
+ const { mutateAsync: testTeamsConnection, isLoading: isLoadingTeams } =
+ api.notification.testTeamsConnection.useMutation();
const { mutateAsync: testCustomConnection, isLoading: isLoadingCustom } =
api.notification.testCustomConnection.useMutation();
@@ -278,6 +291,9 @@ export const HandleNotifications = ({ notificationId }: Props) => {
const larkMutation = notificationId
? api.notification.updateLark.useMutation()
: api.notification.createLark.useMutation();
+ const teamsMutation = notificationId
+ ? api.notification.updateTeams.useMutation()
+ : api.notification.createTeams.useMutation();
const pushoverMutation = notificationId
? api.notification.updatePushover.useMutation()
: api.notification.createPushover.useMutation();
@@ -435,6 +451,19 @@ export const HandleNotifications = ({ notificationId }: Props) => {
volumeBackup: notification.volumeBackup,
serverThreshold: notification.serverThreshold,
});
+ } else if (notification.notificationType === "teams") {
+ form.reset({
+ appBuildError: notification.appBuildError,
+ appDeploy: notification.appDeploy,
+ dokployRestart: notification.dokployRestart,
+ databaseBackup: notification.databaseBackup,
+ volumeBackup: notification.volumeBackup,
+ type: notification.notificationType,
+ webhookUrl: notification.teams?.webhookUrl,
+ name: notification.name,
+ dockerCleanup: notification.dockerCleanup,
+ serverThreshold: notification.serverThreshold,
+ });
} else if (notification.notificationType === "custom") {
form.reset({
appBuildError: notification.appBuildError,
@@ -488,6 +517,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
gotify: gotifyMutation,
ntfy: ntfyMutation,
lark: larkMutation,
+ teams: teamsMutation,
custom: customMutation,
pushover: pushoverMutation,
};
@@ -630,6 +660,20 @@ export const HandleNotifications = ({ notificationId }: Props) => {
larkId: notification?.larkId || "",
serverThreshold: serverThreshold,
});
+ } else if (data.type === "teams") {
+ promise = teamsMutation.mutateAsync({
+ appBuildError: appBuildError,
+ appDeploy: appDeploy,
+ dokployRestart: dokployRestart,
+ databaseBackup: databaseBackup,
+ volumeBackup: volumeBackup,
+ webhookUrl: data.webhookUrl,
+ name: data.name,
+ dockerCleanup: dockerCleanup,
+ notificationId: notificationId || "",
+ teamsId: notification?.teamsId || "",
+ serverThreshold: serverThreshold,
+ });
} else if (data.type === "custom") {
// Convert headers array to object
const headersRecord =
@@ -1465,6 +1509,32 @@ export const HandleNotifications = ({ notificationId }: Props) => {
/>
>
)}
+
+ {type === "teams" && (
+ <>
+ (
+
+ Webhook URL
+
+
+
+
+ Incoming Webhook URL from a Teams channel. Add an
+ Incoming Webhook in your channel settings to get the
+ URL.
+
+
+
+ )}
+ />
+ >
+ )}
{type === "pushover" && (
<>
{
isLoadingGotify ||
isLoadingNtfy ||
isLoadingLark ||
+ isLoadingTeams ||
isLoadingCustom ||
isLoadingPushover
}
@@ -1841,6 +1912,10 @@ export const HandleNotifications = ({ notificationId }: Props) => {
await testLarkConnection({
webhookUrl: data.webhookUrl,
});
+ } else if (data.type === "teams") {
+ await testTeamsConnection({
+ webhookUrl: data.webhookUrl,
+ });
} else if (data.type === "custom") {
const headersRecord =
data.headers && data.headers.length > 0
diff --git a/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx b/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx
index a3c1377ae..60124cc6a 100644
--- a/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx
+++ b/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx
@@ -7,6 +7,7 @@ import {
NtfyIcon,
ResendIcon,
SlackIcon,
+ TeamsIcon,
TelegramIcon,
} from "@/components/icons/notification-icons";
import { DialogAction } from "@/components/shared/dialog-action";
@@ -37,7 +38,7 @@ export const ShowNotifications = () => {
Add your providers to receive notifications, like Discord, Slack,
- Telegram, Email, Resend, Lark.
+ Telegram, Teams, Email, Resend, Lark.
@@ -112,6 +113,11 @@ export const ShowNotifications = () => {
)}
+ {notification.notificationType === "teams" && (
+
+
+
+ )}
{notification.name}
diff --git a/apps/dokploy/components/icons/notification-icons.tsx b/apps/dokploy/components/icons/notification-icons.tsx
index 05f66146a..f902af415 100644
--- a/apps/dokploy/components/icons/notification-icons.tsx
+++ b/apps/dokploy/components/icons/notification-icons.tsx
@@ -88,6 +88,35 @@ export const DiscordIcon = ({ className }: Props) => {
);
};
+export const TeamsIcon = ({ className }: Props) => {
+ return (
+
+ );
+};
+
export const LarkIcon = ({ className }: Props) => {
return (