@@ -16,6 +16,7 @@ import {
1616 LarkIcon ,
1717 NtfyIcon ,
1818 PushoverIcon ,
19+ ResendIcon ,
1920 SlackIcon ,
2021 TelegramIcon ,
2122} from "@/components/icons/notification-icons" ;
@@ -97,6 +98,23 @@ export const notificationSchema = z.discriminatedUnion("type", [
9798 . min ( 1 , { message : "At least one email is required" } ) ,
9899 } )
99100 . merge ( notificationBaseSchema ) ,
101+ z
102+ . object ( {
103+ type : z . literal ( "resend" ) ,
104+ apiKey : z . string ( ) . min ( 1 , { message : "API Key is required" } ) ,
105+ fromAddress : z
106+ . string ( )
107+ . min ( 1 , { message : "From Address is required" } )
108+ . email ( { message : "Email is invalid" } ) ,
109+ toAddresses : z
110+ . array (
111+ z . string ( ) . min ( 1 , { message : "Email is required" } ) . email ( {
112+ message : "Email is invalid" ,
113+ } ) ,
114+ )
115+ . min ( 1 , { message : "At least one email is required" } ) ,
116+ } )
117+ . merge ( notificationBaseSchema ) ,
100118 z
101119 . object ( {
102120 type : z . literal ( "gotify" ) ,
@@ -169,6 +187,10 @@ export const notificationsMap = {
169187 icon : < Mail size = { 29 } className = "text-muted-foreground" /> ,
170188 label : "Email" ,
171189 } ,
190+ resend : {
191+ icon : < ResendIcon className = "text-muted-foreground" /> ,
192+ label : "Resend" ,
193+ } ,
172194 gotify : {
173195 icon : < GotifyIcon /> ,
174196 label : "Gotify" ,
@@ -214,6 +236,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
214236 api . notification . testDiscordConnection . useMutation ( ) ;
215237 const { mutateAsync : testEmailConnection , isLoading : isLoadingEmail } =
216238 api . notification . testEmailConnection . useMutation ( ) ;
239+ const { mutateAsync : testResendConnection , isLoading : isLoadingResend } =
240+ api . notification . testResendConnection . useMutation ( ) ;
217241 const { mutateAsync : testGotifyConnection , isLoading : isLoadingGotify } =
218242 api . notification . testGotifyConnection . useMutation ( ) ;
219243 const { mutateAsync : testNtfyConnection , isLoading : isLoadingNtfy } =
@@ -242,6 +266,9 @@ export const HandleNotifications = ({ notificationId }: Props) => {
242266 const emailMutation = notificationId
243267 ? api . notification . updateEmail . useMutation ( )
244268 : api . notification . createEmail . useMutation ( ) ;
269+ const resendMutation = notificationId
270+ ? api . notification . updateResend . useMutation ( )
271+ : api . notification . createResend . useMutation ( ) ;
245272 const gotifyMutation = notificationId
246273 ? api . notification . updateGotify . useMutation ( )
247274 : api . notification . createGotify . useMutation ( ) ;
@@ -281,7 +308,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
281308 } ) ;
282309
283310 useEffect ( ( ) => {
284- if ( type === "email" && fields . length === 0 ) {
311+ if ( ( type === "email" || type === "resend" ) && fields . length === 0 ) {
285312 append ( "" ) ;
286313 }
287314 } , [ type , append , fields . length ] ) ;
@@ -349,6 +376,21 @@ export const HandleNotifications = ({ notificationId }: Props) => {
349376 dockerCleanup : notification . dockerCleanup ,
350377 serverThreshold : notification . serverThreshold ,
351378 } ) ;
379+ } else if ( notification . notificationType === "resend" ) {
380+ form . reset ( {
381+ appBuildError : notification . appBuildError ,
382+ appDeploy : notification . appDeploy ,
383+ dokployRestart : notification . dokployRestart ,
384+ databaseBackup : notification . databaseBackup ,
385+ volumeBackup : notification . volumeBackup ,
386+ type : notification . notificationType ,
387+ apiKey : notification . resend ?. apiKey ,
388+ toAddresses : notification . resend ?. toAddresses ,
389+ fromAddress : notification . resend ?. fromAddress ,
390+ name : notification . name ,
391+ dockerCleanup : notification . dockerCleanup ,
392+ serverThreshold : notification . serverThreshold ,
393+ } ) ;
352394 } else if ( notification . notificationType === "gotify" ) {
353395 form . reset ( {
354396 appBuildError : notification . appBuildError ,
@@ -442,6 +484,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
442484 telegram : telegramMutation ,
443485 discord : discordMutation ,
444486 email : emailMutation ,
487+ resend : resendMutation ,
445488 gotify : gotifyMutation ,
446489 ntfy : ntfyMutation ,
447490 lark : larkMutation ,
@@ -525,6 +568,22 @@ export const HandleNotifications = ({ notificationId }: Props) => {
525568 emailId : notification ?. emailId || "" ,
526569 serverThreshold : serverThreshold ,
527570 } ) ;
571+ } else if ( data . type === "resend" ) {
572+ promise = resendMutation . mutateAsync ( {
573+ appBuildError : appBuildError ,
574+ appDeploy : appDeploy ,
575+ dokployRestart : dokployRestart ,
576+ databaseBackup : databaseBackup ,
577+ volumeBackup : volumeBackup ,
578+ apiKey : data . apiKey ,
579+ fromAddress : data . fromAddress ,
580+ toAddresses : data . toAddresses ,
581+ name : data . name ,
582+ dockerCleanup : dockerCleanup ,
583+ notificationId : notificationId || "" ,
584+ resendId : notification ?. resendId || "" ,
585+ serverThreshold : serverThreshold ,
586+ } ) ;
528587 } else if ( data . type === "gotify" ) {
529588 promise = gotifyMutation . mutateAsync ( {
530589 appBuildError : appBuildError ,
@@ -1042,6 +1101,96 @@ export const HandleNotifications = ({ notificationId }: Props) => {
10421101 </ >
10431102 ) }
10441103
1104+ { type === "resend" && (
1105+ < >
1106+ < FormField
1107+ control = { form . control }
1108+ name = "apiKey"
1109+ render = { ( { field } ) => (
1110+ < FormItem >
1111+ < FormLabel > API Key</ FormLabel >
1112+ < FormControl >
1113+ < Input
1114+ type = "password"
1115+ placeholder = "re_********"
1116+ { ...field }
1117+ />
1118+ </ FormControl >
1119+ < FormMessage />
1120+ </ FormItem >
1121+ ) }
1122+ />
1123+
1124+ < FormField
1125+ control = { form . control }
1126+ name = "fromAddress"
1127+ render = { ( { field } ) => (
1128+ < FormItem >
1129+ < FormLabel > From Address</ FormLabel >
1130+ < FormControl >
1131+ < Input placeholder = "from@example.com" { ...field } />
1132+ </ FormControl >
1133+ < FormMessage />
1134+ </ FormItem >
1135+ ) }
1136+ />
1137+
1138+ < div className = "flex flex-col gap-2 pt-2" >
1139+ < FormLabel > To Addresses</ FormLabel >
1140+
1141+ { fields . map ( ( field , index ) => (
1142+ < div
1143+ key = { field . id }
1144+ className = "flex flex-row gap-2 w-full"
1145+ >
1146+ < FormField
1147+ control = { form . control }
1148+ name = { `toAddresses.${ index } ` }
1149+ render = { ( { field } ) => (
1150+ < FormItem className = "w-full" >
1151+ < FormControl >
1152+ < Input
1153+ placeholder = "email@example.com"
1154+ className = "w-full"
1155+ { ...field }
1156+ />
1157+ </ FormControl >
1158+
1159+ < FormMessage />
1160+ </ FormItem >
1161+ ) }
1162+ />
1163+ < Button
1164+ variant = "outline"
1165+ type = "button"
1166+ onClick = { ( ) => {
1167+ remove ( index ) ;
1168+ } }
1169+ >
1170+ Remove
1171+ </ Button >
1172+ </ div >
1173+ ) ) }
1174+ { type === "resend" &&
1175+ "toAddresses" in form . formState . errors && (
1176+ < div className = "text-sm font-medium text-destructive" >
1177+ { form . formState ?. errors ?. toAddresses ?. root ?. message }
1178+ </ div >
1179+ ) }
1180+ </ div >
1181+
1182+ < Button
1183+ variant = "outline"
1184+ type = "button"
1185+ onClick = { ( ) => {
1186+ append ( "" ) ;
1187+ } }
1188+ >
1189+ Add
1190+ </ Button >
1191+ </ >
1192+ ) }
1193+
10451194 { type === "gotify" && (
10461195 < >
10471196 < FormField
@@ -1627,6 +1776,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
16271776 isLoadingTelegram ||
16281777 isLoadingDiscord ||
16291778 isLoadingEmail ||
1779+ isLoadingResend ||
16301780 isLoadingGotify ||
16311781 isLoadingNtfy ||
16321782 isLoadingLark ||
@@ -1667,6 +1817,12 @@ export const HandleNotifications = ({ notificationId }: Props) => {
16671817 fromAddress : data . fromAddress ,
16681818 toAddresses : data . toAddresses ,
16691819 } ) ;
1820+ } else if ( data . type === "resend" ) {
1821+ await testResendConnection ( {
1822+ apiKey : data . apiKey ,
1823+ fromAddress : data . fromAddress ,
1824+ toAddresses : data . toAddresses ,
1825+ } ) ;
16701826 } else if ( data . type === "gotify" ) {
16711827 await testGotifyConnection ( {
16721828 serverUrl : data . serverUrl ,
0 commit comments