Back to Articles

Notify: Why Go's Multi-Platform Notification Library Chooses Simplicity Over Guarantees

[ View on GitHub ]

Notify: Why Go’s Multi-Platform Notification Library Chooses Simplicity Over Guarantees

Hook

Most notification libraries promise reliability. Notify promises something more valuable: the ability to wire up Telegram, Discord, and email notifications in under 10 lines of code, then get back to work.

Context

Developer tooling has a notification problem. You build a CI/CD pipeline that needs to alert your team. You reach for a messaging SDK—hundreds of lines later, you’ve battled OAuth flows, webhook formatting, and error handling. Next sprint, management wants alerts in a different platform too. Another SDK, another authentication dance, another afternoon gone. Fast-forward six months: you’re maintaining service-specific code, each with its own retry logic, rate limiting quirks, and breaking changes.

Niko Köser built Notify to collapse this complexity. Instead of treating each messaging platform as a unique snowflake requiring bespoke integration code, Notify provides a unified interface across 20+ services—from email (Amazon SES) to chat platforms (Discord, Telegram, Google Chat) to specialized tools (Firebase Cloud Messaging, DingTalk, Lark). The pitch is dead simple: write your notification logic once, deliver everywhere. But the interesting story isn’t what Notify does—it’s what it deliberately doesn’t do, and why that makes it surprisingly useful for a specific class of problems.

Technical Insight

External

Services

UseServices

Send context, subject, message

Parallel dispatch

Parallel dispatch

Parallel dispatch

Parallel dispatch

Platform-specific API call

Platform-specific API call

Platform-specific API call

Platform-specific API call

Client Application

Notify Dispatcher

Telegram Service

Discord Service

Slack Service

Email Service

Telegram API

Discord API

Slack API

AWS SES

System architecture — auto-generated

Notify’s architecture centers on a deceptively simple abstraction: the Notifier interface. Every service—whether it’s sending an email via AWS SES or posting to a Discord channel—implements a single method signature that accepts a context, subject, and message. The Notify struct acts as a dispatcher, maintaining a collection of these service implementations and broadcasting messages to all registered services when Send() is called.

Here’s what basic integration looks like, pulled directly from the project README:

// Create a telegram service. Ignoring error for demo simplicity.
telegramService, _ := telegram.New("your_telegram_api_token")

// Passing a telegram chat id as receiver for our messages.
// Basically where should our message be sent?
telegramService.AddReceivers(-1234567890)

// Tell our notifier to use the telegram service. You can repeat the above process
// for as many services as you like and just tell the notifier to use them.
notify.UseServices(telegramService)

// Send a test message.
_ = notify.Send(
    context.Background(),
    "Subject/Title",
    "The actual message - Hello, you awesome gophers! :)",
)

The middleware-inspired pattern in UseServices() is the key architectural decision. Like HTTP middleware chains in web frameworks, you compose notification behavior by registering service implementations. Want redundant delivery? Register both Telegram and Discord. Need to alert both your team chat and multiple recipients? Add services and use the AddReceivers() method. The Send() method handles the fan-out automatically.

What makes this design elegant is how each service package isolates its dependencies. The Discord integration lives in service/discord, wrapping the bwmarrin/discordgo SDK. The mail service in service/mail uses jordan-wright/email. Import only what you need—if your binary never touches Discord, you never pull in Discord’s dependencies. This modular packaging keeps your compiled binary lean and your go.mod file manageable.

The service implementations are essentially adapters: translate the generic (subject, message) tuple into whatever format the underlying platform expects. For Telegram, that means constructing a message with appropriate formatting. For email services like AWS SES, it means building MIME messages with proper headers. For Discord, it’s creating embed objects. Each service handles its own authentication and API client initialization, but the core notification logic stays platform-agnostic.

Notify supports multiple receivers per service out of the box. Call AddReceivers() with multiple chat IDs, email addresses, or phone numbers, and the library handles delivery to all of them.

The library provides both global convenience functions (like the notify.Send() shown above) and explicit constructors for creating isolated instances. The README explicitly recommends against global functions in production—following the pattern established by structured logging libraries like Zap—but they’re useful for quick scripts and prototypes. For production code, you’d create a local Notify instance and inject your services.

Gotcha

The elephant in the room: Notify makes zero guarantees about message delivery. There’s no built-in retry logic, no exponential backoff, no dead letter queues mentioned in the documentation. If a service’s API call fails—network timeout, rate limit, authentication error—the library returns an error and moves on. Other registered services still receive the message (failures don’t cascade), but that failed delivery is gone unless you implement retry logic yourself.

This isn’t an oversight—it’s a design choice that permeates the project. The README includes a prominent disclaimer: “Since Notify is highly dependent on the consistency of the supported external services and the corresponding latest client libraries, we cannot guarantee its reliability nor its consistency, and therefore you should probably not use or rely on Notify in critical scenarios.” For mission-critical notifications—alerting on-call engineers about production outages, sending password reset emails, financial transaction confirmations—Notify’s fire-and-forget approach is fundamentally inadequate.

The dependency surface is another consideration. Notify wraps 20+ external SDKs, each with its own versioning, breaking changes, and maintenance cadence. When Discord’s Go SDK ships a breaking change, or AWS updates their SES client API, downstream Notify users inherit that instability. The service implementations are thin wrappers, which means they’re easy to update but also tightly coupled to upstream churn.

The README also includes a warning about misuse: “Spamming through the use of this library may get you permanently banned on most supported platforms.” This suggests users need to be mindful of rate limiting and appropriate usage patterns, which the library itself doesn’t enforce.

Verdict

Use Notify if you’re building developer tools, personal automation, or internal dashboards where notifications are helpful but not mission-critical. It excels in CI/CD pipelines alerting on build failures, monitoring systems broadcasting metrics to team chat, cron jobs reporting completion status, or side projects that need quick multi-platform reach. The straightforward setup and unified interface make it perfect for scenarios where occasional delivery failures are acceptable and debugging notification issues isn’t your core business.

Skip Notify if you’re building customer-facing features requiring delivery guarantees—password resets, two-factor authentication, transactional emails, or alerting systems where missing a notification has operational consequences. Also skip it for high-throughput production systems where rate limiting and retry logic are essential requirements, or when you need advanced features like message templating, delivery tracking, or analytics. For those cases, either invest in direct SDK integration with proper error handling or reach for specialized notification infrastructure that treats reliability as a first-class concern.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/cybersecurity/nikoksr-notify.svg)](https://starlog.is/api/badge-click/cybersecurity/nikoksr-notify)