Notify: Broadcasting Alerts Across 20+ Platforms With One Go Function Call
Hook
What if your alert about a production outage reached your team on Slack, Discord, and Telegram—but only required writing one Send() call? That's exactly what 3,700+ developers are using notify for.
Context
Anyone who's built alerting into an application knows the pain: each messaging service has its own SDK, authentication flow, and API quirks. Want to notify your team on both Slack and Discord? That's two sets of credentials, two client initializations, two error handling patterns, and two maintenance burdens when APIs change. Multiply this across email, SMS, Microsoft Teams, and whatever chat platform your organization adopts next quarter, and you're maintaining a notification spaghetti mess.
Niko Köser's notify library attacks this problem with a deceptively simple premise: what if every notification service implemented the same interface? Instead of learning the Slack Go SDK, the Discord API, and the Telegram Bot framework, you configure each service once, register them with a central notifier, and call a single Send() method that broadcasts your message everywhere simultaneously. It's the adapter pattern applied to the chaos of modern messaging platforms, and it supports everything from mainstream tools like Slack and Telegram to regional platforms like WeChat, DingTalk, and Lark.
Technical Insight
Notify's architecture revolves around a service registry pattern wrapped in a clean, middleware-inspired API. At its core is the Notifier type, which maintains a slice of services that each implement a simple interface requiring only a Send(context.Context, string, string) error method. When you call Send() on the notifier, it iterates through registered services and calls their individual Send() methods, collecting errors into a multierror type.
Here's what the basic usage looks like:
package main
import (
"context"
"github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/telegram"
"github.com/nikoksr/notify/service/slack"
)
func main() {
// Initialize the notifier
notifier := notify.New()
// Set up Telegram service
telegramService, _ := telegram.New("YOUR_TELEGRAM_BOT_TOKEN")
telegramService.AddReceivers(-1234567890) // Chat ID
// Set up Slack service
slackService := slack.New("YOUR_SLACK_WEBHOOK_URL")
// Register both services
notifier.UseServices(telegramService, slackService)
// Send to all services with one call
err := notifier.Send(
context.Background(),
"Deployment Alert",
"Production deployment completed successfully",
)
if err != nil {
// Handle aggregated errors from all services
log.Fatal(err)
}
}
The elegant part is how each service package isolates its dependencies. When you import notify/service/telegram, you're pulling in the Telegram Bot API client. Import notify/service/discord instead, and you get DiscordGo. This prevents dependency bloat—if you only need Slack and email notifications, you're not downloading SDKs for 18 other platforms. Each service package acts as a thin adapter, translating the common Send(context, subject, message) signature into whatever the underlying SDK expects.
The library also supports both global and local instances, following Go community conventions. For quick scripts, you can use the package-level functions:
import "github.com/nikoksr/notify"
func init() {
notify.UseServices(myTelegramService, mySlackService)
}
func alertOnError(err error) {
notify.Send(context.Background(), "Error Alert", err.Error())
}
For more control, particularly in applications where you need different notification configurations (maybe development alerts go to Discord while production goes to PagerDuty), you instantiate separate Notifier objects with notify.New().
Under the hood, service implementations vary in complexity based on what the underlying platform supports. Simple services like Slack webhooks are straightforward—you're just making an HTTP POST with a JSON payload. More complex services like Telegram require maintaining bot tokens, handling chat IDs, and potentially dealing with media attachments. Here's a peek at how the Discord service might be configured with multiple channels:
discordService := discord.New()
discordService.AuthenticateWithBotToken("YOUR_BOT_TOKEN")
// Send to multiple channels
discordService.AddReceivers("channel-id-1", "channel-id-2", "channel-id-3")
notifier.UseServices(discordService)
notifier.Send(ctx, "Alert", "This goes to three Discord channels")
The AddReceivers() pattern is consistent across services—whether you're adding email addresses, phone numbers, channel IDs, or webhook URLs, the API stays familiar. This consistency is notify's superpower: once you understand the pattern for one service, you understand it for all twenty.
Gotcha
The library's simplicity comes with trade-offs that matter in production scenarios. Most critically, there's no built-in retry logic or circuit breaking. If Slack's API is experiencing an outage and returns a 500 error, notify will report that error back to you, but it won't attempt to retry or queue the message for later delivery. The author is explicit about this in the README: "This library does not attempt to handle errors from external services in sophisticated ways." For fire-and-forget notifications in development environments or personal projects, this is fine. For critical alerting where you need guaranteed delivery, it's a dealbreaker.
Error handling when broadcasting to multiple services is also simplified—perhaps oversimplified. When you send to five services and two fail, you get back a multierror containing both failures, but you have no built-in way to know which services succeeded and which failed without parsing error messages. There's no callback mechanism, no per-service error handlers, and no partial success reporting. Additionally, all services are called sequentially in the order they were registered, so a slow service blocks subsequent ones. If your Microsoft Teams webhook takes 3 seconds to timeout, your Telegram notification waits 3 seconds to even attempt sending. There's no concurrent dispatch, no timeout configuration per service, and no priority ordering.
Verdict
Use if: You're building CI/CD pipelines, monitoring dashboards, personal automation scripts, or internal tools where notification delivery is important but not mission-critical. Notify excels when you need to broadcast the same message across multiple platforms without writing platform-specific code, and when a dropped notification is annoying but not catastrophic. It's perfect for open-source projects that want to support multiple notification backends without maintaining separate integrations, or for teams that frequently switch between messaging platforms and need flexibility. Skip if: You're building production alerting for critical systems where every notification must be delivered and tracked. If you need retry logic, rate limiting, delivery confirmations, or sophisticated error recovery, you'll need to build that yourself or use a commercial alerting service like PagerDuty. Also skip if you only need one notification service—just use that platform's official SDK directly for better control and documentation. Finally, avoid notify if you need features beyond basic text messages; support for attachments, rich formatting, and interactive elements varies wildly across service implementations.