diff --git a/apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx b/apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx
index c4e549573..28d762c1c 100644
--- a/apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx
+++ b/apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx
@@ -12,6 +12,7 @@ import { toast } from "sonner";
import { z } from "zod";
import {
DiscordIcon,
+ MattermostIcon,
GotifyIcon,
LarkIcon,
NtfyIcon,
@@ -134,6 +135,14 @@ export const notificationSchema = z.discriminatedUnion("type", [
priority: z.number().min(1).max(5).default(3),
})
.merge(notificationBaseSchema),
+ z
+ .object({
+ type: z.literal("mattermost"),
+ webhookUrl: z.string().min(1, { message: "Webhook URL is required" }),
+ channel: z.string().optional(),
+ username: z.string().optional(),
+ })
+ .merge(notificationBaseSchema),
z
.object({
type: z.literal("pushover"),
@@ -210,6 +219,10 @@ export const notificationsMap = {
icon: ,
label: "ntfy",
},
+ mattermost: {
+ icon: ,
+ label: "Mattermost",
+ },
pushover: {
icon: ,
label: "Pushover",
@@ -253,14 +266,16 @@ export const HandleNotifications = ({ notificationId }: Props) => {
api.notification.testGotifyConnection.useMutation();
const { mutateAsync: testNtfyConnection, isPending: isLoadingNtfy } =
api.notification.testNtfyConnection.useMutation();
+ const {
+ mutateAsync: testMattermostConnection,
+ isPending: isLoadingMattermost,
+ } = api.notification.testMattermostConnection.useMutation();
const { mutateAsync: testLarkConnection, isPending: isLoadingLark } =
api.notification.testLarkConnection.useMutation();
const { mutateAsync: testTeamsConnection, isPending: isLoadingTeams } =
api.notification.testTeamsConnection.useMutation();
-
const { mutateAsync: testCustomConnection, isPending: isLoadingCustom } =
api.notification.testCustomConnection.useMutation();
-
const { mutateAsync: testPushoverConnection, isPending: isLoadingPushover } =
api.notification.testPushoverConnection.useMutation();
@@ -288,6 +303,9 @@ export const HandleNotifications = ({ notificationId }: Props) => {
const ntfyMutation = notificationId
? api.notification.updateNtfy.useMutation()
: api.notification.createNtfy.useMutation();
+ const mattermostMutation = notificationId
+ ? api.notification.updateMattermost.useMutation()
+ : api.notification.createMattermost.useMutation();
const larkMutation = notificationId
? api.notification.updateLark.useMutation()
: api.notification.createLark.useMutation();
@@ -438,6 +456,21 @@ export const HandleNotifications = ({ notificationId }: Props) => {
dockerCleanup: notification.dockerCleanup,
serverThreshold: notification.serverThreshold,
});
+ } else if (notification.notificationType === "mattermost") {
+ form.reset({
+ appBuildError: notification.appBuildError,
+ appDeploy: notification.appDeploy,
+ dokployRestart: notification.dokployRestart,
+ databaseBackup: notification.databaseBackup,
+ volumeBackup: notification.volumeBackup,
+ type: notification.notificationType,
+ webhookUrl: notification.mattermost?.webhookUrl,
+ channel: notification.mattermost?.channel || "",
+ username: notification.mattermost?.username || "",
+ name: notification.name,
+ dockerCleanup: notification.dockerCleanup,
+ serverThreshold: notification.serverThreshold,
+ });
} else if (notification.notificationType === "lark") {
form.reset({
appBuildError: notification.appBuildError,
@@ -516,6 +549,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
resend: resendMutation,
gotify: gotifyMutation,
ntfy: ntfyMutation,
+ mattermost: mattermostMutation,
lark: larkMutation,
teams: teamsMutation,
custom: customMutation,
@@ -646,6 +680,22 @@ export const HandleNotifications = ({ notificationId }: Props) => {
notificationId: notificationId || "",
ntfyId: notification?.ntfyId || "",
});
+ } else if (data.type === "mattermost") {
+ promise = mattermostMutation.mutateAsync({
+ appBuildError: appBuildError,
+ appDeploy: appDeploy,
+ dokployRestart: dokployRestart,
+ databaseBackup: databaseBackup,
+ volumeBackup: volumeBackup,
+ webhookUrl: data.webhookUrl,
+ channel: data.channel || undefined,
+ username: data.username || undefined,
+ name: data.name,
+ dockerCleanup: dockerCleanup,
+ notificationId: notificationId || "",
+ mattermostId: notification?.mattermostId || "",
+ serverThreshold: serverThreshold,
+ });
} else if (data.type === "lark") {
promise = larkMutation.mutateAsync({
appBuildError: appBuildError,
@@ -1406,6 +1456,62 @@ export const HandleNotifications = ({ notificationId }: Props) => {
/>
>
)}
+
+ {type === "mattermost" && (
+ <>
+ (
+
+ Webhook URL
+
+
+
+
+
+ )}
+ />
+
+ (
+
+ Channel
+
+
+
+
+ Optional. Channel to post to (without #).
+
+
+
+ )}
+ />
+
+ (
+
+ Username
+
+
+
+
+ Optional. Display name for the webhook.
+
+
+
+ )}
+ />
+ >
+ )}
+
{type === "custom" && (
{
)}
+
{type === "lark" && (
<>
{
isLoadingResend ||
isLoadingGotify ||
isLoadingNtfy ||
+ isLoadingMattermost ||
isLoadingLark ||
isLoadingTeams ||
isLoadingCustom ||
@@ -1911,6 +2019,12 @@ export const HandleNotifications = ({ notificationId }: Props) => {
accessToken: data.accessToken || "",
priority: data.priority ?? 0,
});
+ } else if (data.type === "mattermost") {
+ await testMattermostConnection({
+ webhookUrl: data.webhookUrl,
+ channel: data.channel || undefined,
+ username: data.username || undefined,
+ });
} else if (data.type === "lark") {
await testLarkConnection({
webhookUrl: data.webhookUrl,
diff --git a/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx b/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx
index 3d62658ae..ccb28ee4c 100644
--- a/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx
+++ b/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx
@@ -4,6 +4,7 @@ import {
DiscordIcon,
GotifyIcon,
LarkIcon,
+ MattermostIcon,
NtfyIcon,
ResendIcon,
SlackIcon,
@@ -121,6 +122,12 @@ export const ShowNotifications = () => {
)}
+ {notification.notificationType ===
+ "mattermost" && (
+
+
+
+ )}
{notification.name}
diff --git a/apps/dokploy/components/icons/notification-icons.tsx b/apps/dokploy/components/icons/notification-icons.tsx
index f902af415..5b7f7ee41 100644
--- a/apps/dokploy/components/icons/notification-icons.tsx
+++ b/apps/dokploy/components/icons/notification-icons.tsx
@@ -88,6 +88,21 @@ export const DiscordIcon = ({ className }: Props) => {
);
};
+
+export const MattermostIcon = ({ className }: Props) => {
+ return (
+
+ );
+};
+
export const TeamsIcon = ({ className }: Props) => {
return (