notifier
May 24, 2026 · View on GitHub
A small, zero-dependency Go library that fans a single message out to multiple notification channels concurrently. Useful for alerting, build pipelines, personal automations, and anywhere you want one call to reach DingTalk, Bark, Telegram, etc. without writing five HTTP clients.
Providers
| Provider | Package |
|---|---|
| DingTalk | provider/dingtalk |
| Bark | provider/bark |
| Lark | provider/lark |
| Feishu | provider/feishu |
| Server 酱 | provider/serverchan |
| WeCom (企业微信) | provider/wecom |
| Telegram Bot | provider/telegram |
Install
go get github.com/moond4rk/notifier
Requires Go 1.26 or later.
Quickstart
package main
import (
"context"
"log"
"os"
"time"
"github.com/moond4rk/notifier"
)
func main() {
n := notifier.New(
notifier.WithDingTalk(os.Getenv("DINGTALK_TOKEN"), os.Getenv("DINGTALK_SECRET")),
notifier.WithBark(os.Getenv("BARK_KEY"), ""),
notifier.WithTelegram(os.Getenv("TELEGRAM_TOKEN"), os.Getenv("TELEGRAM_CHAT_ID")),
)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := n.Send(ctx, notifier.Message{
Subject: "build failed",
Content: "main.go:42 syntax error",
}); err != nil {
log.Print(err)
}
}
A failure in one provider does not silence the rest — Send returns a *notifier.MultiError that wraps each per-provider failure as a *notifier.SendError. errors.As and errors.Is work as you'd expect:
var multi *notifier.MultiError
if errors.As(err, &multi) {
for _, e := range multi.Errors {
log.Printf("%s: %v", e.Provider, e.Err)
}
}
Channel-specific options (Extras)
Message.Extras is a map[string]any of provider-specific keys. Unknown keys are silently ignored, so it is safe to set them when broadcasting to a mixed list of providers.
import (
"github.com/moond4rk/notifier"
"github.com/moond4rk/notifier/provider/bark"
"github.com/moond4rk/notifier/provider/dingtalk"
"github.com/moond4rk/notifier/provider/telegram"
)
n.Send(ctx, notifier.Message{
Subject: "alarm",
Content: "fire in srv-01",
Format: notifier.FormatMarkdown,
Extras: map[string]any{
bark.KeySound: "siren.caf",
bark.KeyGroup: "alerts",
dingtalk.KeyAtAll: true,
telegram.KeyDisableNotification: false,
},
})
| Provider | Key constant | Type | Effect |
|---|---|---|---|
bark | KeySound | string | Override notification sound |
bark | KeyIcon | string | Custom icon URL |
bark | KeyGroup | string | Group key |
bark | KeyURL | string | Tap-to-open URL |
bark | KeyBadge | int | Badge count |
dingtalk | KeyAtAll | bool | @-everyone |
dingtalk | KeyAtMobiles | []string | @ by phone number |
dingtalk | KeyAtUserIDs | []string | @ by user ID |
wecom | KeyMentionedList | []string | @ by username (text mode) |
wecom | KeyMentionedMobile | []string | @ by phone (text mode) |
telegram | KeyDisableNotification | bool | Silent message |
telegram | KeyDisableWebPagePreview | bool | No link preview |
Custom providers
Implement the notifier.Provider interface (alias of provider.Provider) and register with WithProvider:
type slackProvider struct{ webhook string }
func (slackProvider) Name() string { return "slack" }
func (s slackProvider) Send(ctx context.Context, msg notifier.Message) error {
// POST to s.webhook with ctx; return error on failure.
return nil
}
n := notifier.New(notifier.WithProvider(slackProvider{webhook: "..."}))
HTTP client and timeouts
Each built-in provider uses an *http.Client with a 30-second timeout by default. To share a client (for connection pooling, proxies, custom transports, ...), pass it through WithHTTPClient before the convenience helpers:
client := &http.Client{Timeout: 5 * time.Second}
n := notifier.New(
notifier.WithHTTPClient(client), // must come first
notifier.WithDingTalk(token, secret),
notifier.WithBark(key, ""),
)
Or set it per provider via the Config struct when constructing manually:
notifier.WithProvider(dingtalk.New(dingtalk.Config{
Token: token, Secret: secret, HTTPClient: client,
}))
License
MIT — see LICENSE.