notifier

May 24, 2026 · View on GitHub

Build Tests Lint Go Reference Coverage Status

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

ProviderPackage
DingTalkprovider/dingtalk
Barkprovider/bark
Larkprovider/lark
Feishuprovider/feishu
Server 酱provider/serverchan
WeCom (企业微信)provider/wecom
Telegram Botprovider/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,
    },
})
ProviderKey constantTypeEffect
barkKeySoundstringOverride notification sound
barkKeyIconstringCustom icon URL
barkKeyGroupstringGroup key
barkKeyURLstringTap-to-open URL
barkKeyBadgeintBadge count
dingtalkKeyAtAllbool@-everyone
dingtalkKeyAtMobiles[]string@ by phone number
dingtalkKeyAtUserIDs[]string@ by user ID
wecomKeyMentionedList[]string@ by username (text mode)
wecomKeyMentionedMobile[]string@ by phone (text mode)
telegramKeyDisableNotificationboolSilent message
telegramKeyDisableWebPagePreviewboolNo 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.