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.
| Provider | Package |
|---|---|
| DingTalk | provider/dingtalk |
| Bark | provider/bark |
| Lark | provider/lark |
| Feishu | provider/feishu |
| Server 酱 | provider/serverchan |
| WeCom (企业微信) | provider/wecom |
| Telegram Bot | provider/telegram |
go get github.com/moond4rk/notifierRequires Go 1.26 or later.
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)
}
}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 |
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: "..."}))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,
}))MIT — see LICENSE.