Examples
Multi-Channel Notification
One notification, fanned out to email, WhatsApp, and the database in parallel.
The appeal of a unified notification library is that one Send() reaches the user everywhere. This example shows an OrderShipped going to mail + WhatsApp + database in one call.
Setup
import (
"context"
"log"
"os"
"github.com/gopackx/go-notification/notification"
"github.com/gopackx/go-notification/channel/mail/mailgun"
"github.com/gopackx/go-notification/channel/whatsapp/waha"
"github.com/gopackx/go-notification/channel/database"
"github.com/gopackx/go-notification/message/mail"
"github.com/gopackx/go-notification/message/whatsapp"
"database/sql"
_ "github.com/lib/pq"
)
type User struct {
ID int64
Name string
Email string
Phone string // E.164
WantsMail bool
WantsWhatsApp bool
}
func (u User) RouteNotificationFor(channel string) any {
switch channel {
case "mail": return u.Email
case "whatsapp": return u.Phone
case "database": return u.ID
}
return nil
}Notification
type OrderShipped struct {
OrderID string
TrackingURL string
}
func (n OrderShipped) Via(notifiable notification.Notifiable) []string {
u := notifiable.(User)
channels := []string{"database"} // always in-app
if u.WantsMail { channels = append(channels, "mail") }
if u.WantsWhatsApp { channels = append(channels, "whatsapp") }
return channels
}
func (n OrderShipped) ToMail(_ notification.Notifiable) *mail.Message {
return mail.NewMessage().
Subject("Your order shipped").
Greeting("Hey,").
Line("Your order " + n.OrderID + " is on its way.").
Action("Track package", n.TrackingURL)
}
func (n OrderShipped) ToWhatsApp(_ notification.Notifiable) *whatsapp.Message {
return whatsapp.NewMessage().
Text("📦 Your order *" + n.OrderID + "* shipped. Track it: " + n.TrackingURL)
}
func (n OrderShipped) ToDatabase(_ notification.Notifiable) map[string]any {
return map[string]any{
"type": "order.shipped",
"order_id": n.OrderID,
"tracking_url": n.TrackingURL,
}
}Wiring the notifier
func main() {
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil { panic(err) }
notifier := notification.New(notification.Config{
OnError: func(_ context.Context, err notification.Error) {
log.Printf("send failed: channel=%s err=%v", err.Channel, err.Err)
},
})
notifier.RegisterChannel("mail", mailgun.New(mailgun.Config{
Domain: "mg.example.com",
APIKey: os.Getenv("MAILGUN_API_KEY"),
From: "noreply@example.com",
}))
notifier.RegisterChannel("whatsapp", waha.New(waha.Config{
BaseURL: "http://localhost:3000",
APIKey: os.Getenv("WAHA_API_KEY"),
SessionID: "default",
}))
dbChannel := database.New(database.Config{
DB: db,
Driver: database.DriverPostgres,
})
if err := dbChannel.AutoMigrate(context.Background()); err != nil {
panic(err)
}
notifier.RegisterChannel("database", dbChannel)
user := User{
ID: 1, Name: "Dani", Email: "dani@example.com",
Phone: "+628123456789",
WantsMail: true, WantsWhatsApp: true,
}
ctx := context.Background()
if err := notifier.Send(ctx, user, OrderShipped{
OrderID: "A-1024",
TrackingURL: "https://example.com/track/A-1024",
}); err != nil {
log.Fatal(err)
}
notifier.Close(ctx)
}What happens on send
Via()returns["database", "mail", "whatsapp"]based onuser.WantsMailanduser.WantsWhatsApp.- Worker pool enqueues three sends, one per channel.
- Each worker calls
RouteNotificationFor(channel)to get the address, then the correspondingTo<Channel>()method to render. - All three APIs are hit in parallel.
- On any failure, retry kicks in per that channel. Others continue unaffected.
Total wall time: max(mail_latency, whatsapp_latency, db_insert_latency), not the sum.