Using the Shoutrrr Package¶
Overview¶
The Shoutrrr Go package (github.com/nicholas-fedor/shoutrrr) enables sending notifications to various services (e.g., discord, slack, telegram, smtp, etc.) using service URLs. It provides two primary methods: a direct Send function for simple use cases and a Sender struct for advanced scenarios with multiple URLs, message queuing, and parameter customization.
Minimum Supported Go Version¶
Projects importing Shoutrrr are expected to follow the latest Go minor and/or patch semantic versions; ergo, Shoutrrr follows the latest minor Go version, i.e. 1.27. (Go refers to minor semantic version releases as major releases.)
Go release information can be reviewed here: https://go.dev/doc/devel/release
Usage¶
import "github.com/nicholas-fedor/shoutrrr"
Direct Send¶
Sends a notification to a single service URL.
- Functions:
shoutrrr.Send(url string, message string) errorshoutrrr.SendContext(ctx context.Context, url string, message string, params *types.Params) error- Behavior: Initializes a service from the provided URL, sends the message within the service's send budget, closes
the service, and returns any error. A send failure wraps a
*types.TargetError, which names the service but never includes the URL, so it is safe to log. Useerrors.Asto get it. An invalid or unsupported URL returns an ordinary wrapped error instead. - Parameters:
Sendtakes no parameters. UseSendContextto pass parameters such as a title, and to cancel the send or bound it with a deadline.paramsmay benil.
Example
url := "slack://hook:T00000000-B00000000-XXXXXXXXXXXXXXXXXXXXXXXX@webhook"
err := shoutrrr.Send(url, "Hello, Slack!")
if err != nil {
fmt.Println("Error:", err)
}
Example
import (
"context"
"fmt"
"time"
"github.com/nicholas-fedor/shoutrrr"
"github.com/nicholas-fedor/shoutrrr/pkg/types"
)
func notify(url string) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
params := types.Params{}
params.SetTitle("Alert")
if err := shoutrrr.SendContext(ctx, url, "System alert!", ¶ms); err != nil {
return fmt.Errorf("sending alert: %w", err)
}
return nil
}
Sender¶
Creates a Sender (*ServiceRouter) to manage multiple service URLs, support message queuing, and allow parameter customization.
- Function:
shoutrrr.CreateSenderWithOptions(opts types.SenderOptions, urls ...string) (*ServiceRouter, error).shoutrrr.NewSenderWithOptionsalso takes a logger.CreateSenderandNewSenderare deprecated. - Options:
types.SenderOptions{}uses the defaults.HTTPClientis injected into HTTP services.DialContextis injected into TCP services that implementtypes.DialContextSetter.shoutrrr.Sendandshoutrrr.SendContextcannot take these options. - Methods:
Send(message string, params *types.Params) []error: Sends a message to all configured services.SendContext(ctx context.Context, message string, params *types.Params) []error: LikeSend, and returns whenctxis canceled or its deadline passes.SendItems(items []types.MessageItem, params types.Params) []error: Sends structured message items to services that support rich formatting.SendItemsContext(ctx context.Context, items []types.MessageItem, params types.Params) []error: LikeSendItems, bounded byctx.SendAsync(message string, params *types.Params) chan error: Sends a message asynchronously and returns a channel of errors.Enqueue(message string, v ...any): Queues a formatted message for later sending.Flush(params *types.Params): Sends all queued messages and resets the queue.Close() error: Closes services that hold resources between sends, such as MQTT connections.- Behavior: Initializes a service for each URL and sends to all of them concurrently.
Send,SendContext,SendItems, andSendItemsContextreturn one entry per configured URL, in the same order, and the entry isnilwhen that send succeeded.
Example
urls := []string{
"slack://hook:T00000000-B00000000-XXXXXXXXXXXXXXXXXXXXXXXX@webhook",
"telegram://110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw@telegram?channels=@mychannel",
}
sender, err := shoutrrr.CreateSenderWithOptions(types.SenderOptions{}, urls...)
if err != nil {
log.Fatal(err)
}
defer sender.Close()
params := types.Params{}
params.SetTitle("Test Notification")
for i, err := range sender.Send("Hello, world!", ¶ms) {
if err != nil {
fmt.Printf("Error sending to URL %d: %v\n", i, err)
}
}
Message Queuing¶
Allows queuing messages for deferred sending, useful for aggregating notifications during a process.
- Queues messages with
Enqueueand sends them withFlush. Queued messages use theparamsprovided duringFlush.
Example
url := "discord://abc123@123456789"
sender, err := shoutrrr.CreateSenderWithOptions(types.SenderOptions{}, url)
if err != nil {
log.Fatal(err)
}
defer sender.Flush(nil)
sender.Enqueue("Started doing work")
if err := doWork(); err != nil {
sender.Enqueue("Error: %v", err)
return
}
sender.Enqueue("Work completed successfully!")
SendItems and RichSender¶
Sends structured message items to services that support rich formatting.
Services implementing types.RichSender receive the full []types.MessageItem slice, preserving level, fields, and file attachments.
Services that do not implement RichSender fall back to plain text automatically.
- Method:
SendItems(items []types.MessageItem, params types.Params) []error - Behavior: Dispatches to
ContextAttachmentSenderif available, otherwiseRichSender, otherwise falls back toSendwith plain text.
Example
items := []types.MessageItem{
{Text: "Deployment complete", Level: types.Info},
{Text: "Rollback available", Level: types.Warning},
}
sender, err := shoutrrr.CreateSenderWithOptions(types.SenderOptions{}, "discord://token@webhookid")
if err != nil {
log.Fatal(err)
}
errs := sender.SendItems(items, types.Params{})
Context Propagation¶
shoutrrr.SendContext, ServiceRouter.SendContext, and ServiceRouter.SendItemsContext take a caller context.
Services that implement types.ContextSender or types.ContextAttachmentSender receive a context derived from it with
the service's send budget, so cancellation and deadlines stop their requests. For other services, the call returns when
the context ends, and the service's error reports the context's error. Send and SendItems use a background context.
Per-Target Errors¶
*ServiceRouter.Send, SendContext, SendItems, and SendItemsContext return one entry per configured URL, in the
order the URLs were given, and the entry is nil when that send succeeded. SendAsync's channel receives one result per
service, nil for a successful send, in the order the sends finish, and then closes. Route and shoutrrr.SendContext return a single error. A failed send is a *types.TargetError, or wraps
one, so use errors.As to get it. Its URL field holds the service ID (such as discord) and its Index field holds
the URL's position. It never contains the service URL, so it is safe to log, and it supports errors.Unwrap and
errors.Is. Creating a sender and locating a service return ordinary wrapped errors, not a *types.TargetError.
Example
errs := sender.Send("deploy complete", nil)
for i, err := range errs {
if err == nil {
continue
}
var targetErr *types.TargetError
if errors.As(err, &targetErr) {
log.Printf("failed to send to URL %d (%s): %v", i, targetErr.URL, targetErr.Err)
}
}
Message Levels¶
Services that support severity or priority can receive a semantic level through the level param.
Shoutrrr defines five levels: Unknown, Debug, Info, Warning, and Error.
The default is Info.
Example
params := types.Params{}
params.SetLevel(types.Warning)
params.SetTitle("Disk usage high")
sender, err := shoutrrr.CreateSenderWithOptions(types.SenderOptions{}, "discord://token@webhookid")
if err != nil {
log.Fatal(err)
}
errs := sender.Send("Root partition at 92%", ¶ms)
Format Conversion¶
Converts message bodies between supported formats.
import "github.com/nicholas-fedor/shoutrrr/pkg/format"
body, err := format.ConvertFormat("Hello **world**", "markdown", "text")
Supported conversions: text ↔ markdown ↔ html.
Service Discovery¶
Enumerates or checks available notification services without constructing a router.
import "github.com/nicholas-fedor/shoutrrr/pkg/services"
for _, schema := range services.SupportedSchemas() {
fmt.Println(schema)
}
if services.SupportsSchema("discord") {
// ...
}
Examples¶
Send with Parameters and Error Handling¶
Example
url := "discord://abc123@123456789"
params := types.Params{}
params.SetTitle("Alert")
if err := shoutrrr.SendContext(context.Background(), url, "System alert!", ¶ms); err != nil {
fmt.Println("Error:", err)
}
(No output on success)
Error: sending message: discord: <the service's error>
Send to Multiple Services with Queuing¶
Example
urls := []string{
"slack://hook:T00000000-B00000000-XXXXXXXXXXXXXXXXXXXXXXXX@webhook",
"discord://abc123@123456789",
}
sender, err := shoutrrr.CreateSenderWithOptions(types.SenderOptions{}, urls...)
if err != nil {
log.Fatal(err)
}
start := time.Now()
params := types.Params{}
params.SetTitle("Task Summary")
defer sender.Flush(¶ms)
sender.Enqueue("Task started")
time.Sleep(time.Second)
sender.Enqueue("Task finished in %v", time.Now().Sub(start))
(No output on success)
2026/10/10 12:00:00 creating sender: error initializing router services: URL 0: slack: invalid slack token format
Notes¶
- Error Handling:
shoutrrr.Sendandshoutrrr.SendContextreturn a single error.Sender.Send,SendContext,SendItems, andSendItemsContextreturn one entry per URL,nilon success, so check each entry rather than the length of the slice. A failed send is or wraps a*types.TargetErrornaming the service, never its URL, so useerrors.Asto get it. Creating a sender and locating a service return ordinary wrapped errors. - Parameters:
paramsis a*types.Paramsvalue forSend,SendContext,SendAsync, andFlush, includingshoutrrr.SendContext.SendItemsacceptstypes.Paramsby value. Use setter methods such asSetTitle,SetMessage, andSetLevelto configure service-specific options. Useshoutrrr docsto view supported parameters for each service. - Timeouts: The default is 10 seconds per service. A longer service timeout extends that service. A positive
SenderOptions.Timeoutis the exact fixed timeout for every service. - Duplicate URLs: A
Sendersends to every URL it is given, including duplicates. The CLI'ssendcommand removes duplicate URLs before sending.