- Go 97.9%
- Shell 2%
- PowerShell 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .github | ||
| build | ||
| debian | ||
| docs | ||
| internal | ||
| pkg | ||
| scripts | ||
| shoutrrr | ||
| testing/e2e | ||
| .all-contributorsrc | ||
| .codecov.yml | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| CHANGELOG.md | ||
| cliff.toml | ||
| CONTRIBUTING.md | ||
| go.mod | ||
| go.sum | ||
| LICENSE.md | ||
| README.md | ||
| SECURITY.md | ||
| shoutrrr.go | ||
| shoutrrr_test.go | ||
| Taskfile.yml | ||
Shoutrrr
A notification library for gophers and their furry friends.
Heavily inspired by caronc/apprise.
Table of Contents
- Full Documentation
- Installation
- Usage
- Supported Services
- Contributors ✨
- Related Project(s)
Full Documentation
Visit the project's GitHub Page for full documentation.
Installation
From Source
go install github.com/nicholas-fedor/shoutrrr/shoutrrr@latest
Binaries
Install the latest release binary to $HOME/go/bin (ensure it's in your PATH).
-
Windows (amd64):
New-Item -ItemType Directory -Path $HOME\go\bin -Force | Out-Null; iwr (iwr https://api.github.com/repos/nicholas-fedor/shoutrrr/releases/latest | ConvertFrom-Json).assets.where({$_.name -like "*windows_amd64*.zip"}).browser_download_url -OutFile shoutrrr.zip; Add-Type -AssemblyName System.IO.Compression.FileSystem; ($z=[System.IO.Compression.ZipFile]::OpenRead("$PWD\shoutrrr.zip")).Entries | ? {$_.Name -eq 'shoutrrr.exe'} | % {[System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, "$HOME\go\bin\$($_.Name)", $true)}; $z.Dispose(); rm shoutrrr.zip; if (Test-Path "$HOME\go\bin\shoutrrr.exe") { Write-Host "Successfully installed shoutrrr.exe to $HOME\go\bin" } else { Write-Host "Failed to install shoutrrr.exe" } -
Linux (amd64):
mkdir -p $HOME/go/bin && curl -L $(curl -s https://api.github.com/repos/nicholas-fedor/shoutrrr/releases/latest | grep -o 'https://[^"]*linux_amd64[^"]*\.tar\.gz"' | grep -o 'https://[^"]*' | head -n1) | tar -xz -C $HOME/go/bin shoutrrr -
macOS (amd64):
mkdir -p $HOME/go/bin && curl -L $(curl -s https://api.github.com/repos/nicholas-fedor/shoutrrr/releases/latest | grep -o 'https://[^"]*macOS_amd64[^"]*\.tar\.gz"' | grep -o 'https://[^"]*' | head -n1) | tar -xz -C $HOME/go/bin shoutrrr
Note
Visit the releases page for other architectures (e.g., arm, arm64, i386, riscv64).
Container Images
-
docker pull nickfedor/shoutrrr:latest -
GHCR:
docker pull ghcr.io/nicholas-fedor/shoutrrr:latest
Note
Tags:
latest(stable),X.Y.Z(specific version, e.g.,0.21.2),X.YandX(latest release of a minor or major),nightly(development), platform-specific (e.g.,amd64-latest,amd64-nightly).
Go Package
go get github.com/nicholas-fedor/shoutrrr@latest
Minimum Supported Version Policy
Projects importing Shoutrrr are expected to follow the latest Go minor and/or patch semantic version; ergo, Shoutrrr follows the latest minor Go version, i.e. 1.27.
GitHub Action
- name: Shoutrrr
uses: nicholas-fedor/shoutrrr-action@v1
with:
url: ${{ secrets.SHOUTRRR_URL }}
title: Deployed ${{ github.sha }}
message: See changes at ${{ github.event.compare }}.
Usage
CLI
shoutrrr send --url "slack://hook:T00000000-B00000000-XXXXXXXXXXXXXXXXXXXXXXXX@webhook" --message "Hello, Slack!"
Go Package Usage
import "github.com/nicholas-fedor/shoutrrr"
err := shoutrrr.Send("slack://hook:T00000000-B00000000-XXXXXXXXXXXXXXXXXXXXXXXX@webhook", "Hello, Slack!")
if err != nil {
// Handle the error
}
Docker
docker run --rm nickfedor/shoutrrr:latest send --url "slack://hook:T00000000-B00000000-XXXXXXXXXXXXXXXXXXXXXXXX@webhook" --message "Hello, Slack!"
GitHub Action Usage
See installation example above.
Use as a Package
Option 1 - Using the direct send command
url := "slack://hook:T00000000-B00000000-XXXXXXXXXXXXXXXXXXXXXXXX@webhook"
err := shoutrrr.Send(url, "Hello world (or slack channel) !")
To pass parameters such as a title, or to cancel the send, use shoutrrr.SendContext:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
params := types.Params{}
params.SetTitle("Alert")
err := shoutrrr.SendContext(ctx, url, "Hello world (or slack channel) !", ¶ms)
Option 2 - Using a sender
Single URL
url := "slack://hook:T00000000-B00000000-XXXXXXXXXXXXXXXXXXXXXXXX@webhook"
sender, err := shoutrrr.CreateSenderWithOptions(types.SenderOptions{}, url)
if err != nil {
log.Fatal(err)
}
params := types.Params{}
for i, err := range sender.Send("Hello world (or slack channel) !", ¶ms) { // one entry per URL, nil on success
if err != nil {
log.Printf("failed to send to URL %d: %v", i, err)
}
}
Multiple URLs
urls := []string{
"slack://hook:T00000000-B00000000-XXXXXXXXXXXXXXXXXXXXXXXX@webhook",
"discord://token@channel",
}
sender, err := shoutrrr.CreateSenderWithOptions(types.SenderOptions{}, urls...)
if err != nil {
log.Fatal(err)
}
params := types.Params{}
for i, err := range sender.Send("Hello world (or slack channel) !", ¶ms) { // one entry per URL, nil on success
if err != nil {
log.Printf("failed to send to URL %d: %v", i, err)
}
}
Custom HTTP Client and DialContext (SSRF / Egress Control)
shoutrrr.Send and shoutrrr.SendContext cannot take these options. Use NewSenderWithOptions (or CreateSenderWithOptions).
import (
"context"
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"github.com/nicholas-fedor/shoutrrr"
"github.com/nicholas-fedor/shoutrrr/pkg/types"
)
dial := func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
ips, err := net.DefaultResolver.LookupIPAddr(ctx, host)
if err != nil {
return nil, err
}
d := &net.Dialer{}
for _, ip := range ips {
if ip.IP.IsLoopback() || ip.IP.IsPrivate() || ip.IP.IsLinkLocalUnicast() || ip.IP.IsLinkLocalMulticast() {
continue
}
conn, err := d.DialContext(ctx, network, net.JoinHostPort(ip.IP.String(), port))
if err == nil {
return conn, nil
}
}
return nil, &net.OpError{Op: "dial", Net: network, Err: fmt.Errorf("destination blocked by egress policy")}
}
customClient := &http.Client{
Transport: &http.Transport{
DialContext: dial,
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
},
}
sender, err := shoutrrr.NewSenderWithOptions(
nil,
types.SenderOptions{HTTPClient: customClient, DialContext: dial},
"discord://token@channel",
)
if err != nil {
log.Fatal(err)
}
sender.Send("Hello with custom egress!", nil)
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.
params := types.Params{}
params.SetLevel(types.Warning)
params.SetTitle("Disk usage high")
errs := sender.Send("Root partition at 92%", ¶ms)
Services that implement types.RichSender (such as Discord) receive the full []types.MessageItem slice when using SendItems, preserving level, fields, and file attachments.
Services that do not implement RichSender fall back to plain text automatically.
Per-Target Errors
Sender.Send, *ServiceRouter.SendItems, and their Context variants return one result per configured URL, including duplicate URLs, in the order the URLs were given, and the result is nil when that send succeeded. *ServiceRouter.SendAsync reports one result per configured URL in the order the sends finish. 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), never the service URL, and its Index field holds the URL's position. It supports errors.Unwrap and errors.Is. Creating a sender and locating a service return ordinary wrapped errors:
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)
}
}
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.
Format Conversion
import "github.com/nicholas-fedor/shoutrrr/pkg/format"
body, err := format.ConvertFormat("Hello **world**", "markdown", "text")
Supported conversions: text ↔ markdown ↔ html.
Use Through the CLI
shoutrrr send [FLAGS] [URL] [MESSAGE]
Use as a GitHub Action
You can also use Shoutrrr in a GitHub Actions workflow.
name: Deploy
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- [Your other workflow steps]
- name: Shoutrrr
uses: nicholas-fedor/shoutrrr-action@v0.0.11
with:
url: ${{ secrets.SHOUTRRR_URL }}
title: Deployed ${{ github.sha }}
message: See changes at ${{ github.event.compare }}.
Supported Services
| Service | Description |
|---|---|
| Bark | iOS push notifications |
| Discord | Discord webhooks |
| Generic | Custom HTTP webhooks |
| Google Chat | Google Chat webhooks |
| Gotify | Gotify push notifications |
| Home Assistant | Home Assistant REST notifications |
| IFTTT | IFTTT webhooks |
| Join | Join push notifications |
| Lark | Lark (Feishu) webhooks |
| Logger | Local logging (for testing) |
| Matrix | Matrix rooms |
| Mattermost | Mattermost webhooks |
| MQTT | MQTT message broker |
| Notifiarr | Notifiarr message forwarding |
| Ntfy | Ntfy push notifications |
| Opsgenie | Opsgenie alerts |
| PagerDuty | PagerDuty incident notifications |
| Pushbullet | Pushbullet push notifications |
| Pushover | Pushover push notifications |
| Rocket.Chat | Rocket.Chat webhooks |
| Signal | Signal messages via signal-cli-rest-api |
| Signalgrid | Signalgrid push notifications |
| Slack | Slack webhooks or Bot API |
| SMTP | Email notifications |
| Teams | Microsoft Teams webhooks |
| Telegram | Telegram bots |
| Twilio | Twilio SMS notifications |
| WeCom | WeCom (WeChat Work) webhook bots |
| XMPP | XMPP chat and MUC notifications |
| Zulip | Zulip chat |
Service Discovery
Use services.SupportedSchemas() and services.SupportsSchema(schema) to enumerate or check 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") {
// ...
}
Contributors ✨
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!
Related Project(s)
- Watchtower - Automate Docker container image updates.
- Shoutrrr GitHub Action - Notifications using Shoutrrr in GitHub Actions.
- Bezel - A lightweight server monitoring platform that includes Docker statistics, historical data, and alert functions.
- WatchYourLAN - Lightweight network IP scanner with web GUI.
- DNSControl- Infrastructure as code for DNS.
- docker-volume-backup - Backup Docker volumes locally or to any S3, WebDAV, Azure Blob Storage, Dropbox, Google Drive or SSH compatible storage.
- BirdNET-Go - An AI solution for continuous avian monitoring and identification.