Files
coder/cli/notifications.go
T
Susana Ferreira eec6c8c120 feat: support custom notifications (#19751)
## Description

Adds support for sending an ad‑hoc custom notification to the
authenticated user via API and CLI. This is useful for surfacing the
result of scripts or long‑running tasks. Notifications are delivered
through the configured method and the dashboard Inbox, respecting
existing preferences and delivery settings.

## Changes

* New notification template: “Custom Notification” with a label for a
custom title and a custom message.
* New API endpoint: `POST /api/v2/notifications/custom` to send a custom
notification to the requesting user.
* New API endpoint: `GET /notifications/templates/custom` to get custom
notification template.
* New CLI subcommand: `coder notifications custom <title> <message>` to
send a custom notification to the requesting user.
* Documentation updates: Add a “Custom notifications” section under
Administration > Monitoring > Notifications, including instructions on
sending custom notifications and examples of when to use them.

Closes: https://github.com/coder/coder/issues/19611
2025-09-11 15:08:57 +02:00

144 lines
4.0 KiB
Go

package cli
import (
"fmt"
"golang.org/x/xerrors"
"github.com/coder/serpent"
"github.com/coder/coder/v2/codersdk"
)
func (r *RootCmd) notifications() *serpent.Command {
cmd := &serpent.Command{
Use: "notifications",
Short: "Manage Coder notifications",
Long: "Administrators can use these commands to change notification settings.\n" + FormatExamples(
Example{
Description: "Pause Coder notifications. Administrators can temporarily stop notifiers from dispatching messages in case of the target outage (for example: unavailable SMTP server or Webhook not responding)",
Command: "coder notifications pause",
},
Example{
Description: "Resume Coder notifications",
Command: "coder notifications resume",
},
Example{
Description: "Send a test notification. Administrators can use this to verify the notification target settings",
Command: "coder notifications test",
},
Example{
Description: "Send a custom notification to the requesting user. Sending notifications targeting other users or groups is currently not supported",
Command: "coder notifications custom \"Custom Title\" \"Custom Message\"",
},
),
Aliases: []string{"notification"},
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Children: []*serpent.Command{
r.pauseNotifications(),
r.resumeNotifications(),
r.testNotifications(),
r.customNotifications(),
},
}
return cmd
}
func (r *RootCmd) pauseNotifications() *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "pause",
Short: "Pause notifications",
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
err := client.PutNotificationsSettings(inv.Context(), codersdk.NotificationsSettings{
NotifierPaused: true,
})
if err != nil {
return xerrors.Errorf("unable to pause notifications: %w", err)
}
_, _ = fmt.Fprintln(inv.Stderr, "Notifications are now paused.")
return nil
},
}
return cmd
}
func (r *RootCmd) resumeNotifications() *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "resume",
Short: "Resume notifications",
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
err := client.PutNotificationsSettings(inv.Context(), codersdk.NotificationsSettings{
NotifierPaused: false,
})
if err != nil {
return xerrors.Errorf("unable to resume notifications: %w", err)
}
_, _ = fmt.Fprintln(inv.Stderr, "Notifications are now resumed.")
return nil
},
}
return cmd
}
func (r *RootCmd) testNotifications() *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "test",
Short: "Send a test notification",
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
if err := client.PostTestNotification(inv.Context()); err != nil {
return xerrors.Errorf("unable to post test notification: %w", err)
}
_, _ = fmt.Fprintln(inv.Stderr, "A test notification has been sent. If you don't receive the notification, check Coder's logs for any errors.")
return nil
},
}
return cmd
}
func (r *RootCmd) customNotifications() *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "custom <title> <message>",
Short: "Send a custom notification",
Middleware: serpent.Chain(
serpent.RequireNArgs(2),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
err := client.PostCustomNotification(inv.Context(), codersdk.CustomNotificationRequest{
Content: &codersdk.CustomNotificationContent{
Title: inv.Args[0],
Message: inv.Args[1],
},
})
if err != nil {
return xerrors.Errorf("unable to post custom notification: %w", err)
}
_, _ = fmt.Fprintln(inv.Stderr, "A custom notification has been sent.")
return nil
},
}
return cmd
}