Notifiable Interface
The contract your recipient types implement — how to be identified and reached per channel.
A Notifiable is anything that can receive a notification — typically your User type, but nothing stops you from implementing it on Team, Organization, or any recipient concept in your domain.
Interface
type Notifiable interface {
GetID() string
RouteNotificationFor(channel string) string
}Two methods, both returning string:
GetID()— a stable identifier for this recipient. It's stored alongside persisted notifications (the database channel) so the same recipient can be looked up later.RouteNotificationFor(channel)— the address/route for the given channel (an email, a phone number, a device token, a URL). Return an empty string to skip this channel for this recipient.
Typical implementation
type User struct {
ID string
Email string
Phone string // E.164
TelegramChatID string
DeviceToken string
}
func (u User) GetID() string { return u.ID }
func (u User) RouteNotificationFor(channel string) string {
switch channel {
case "mail": return u.Email
case "sms": return u.Phone
case "whatsapp": return u.Phone
case "chat": return u.TelegramChatID // chat is the default name for chat drivers
case "push": return u.DeviceToken
}
return ""
}Route value per channel
The route string is interpreted by the receiving driver:
| Channel name | Route string | Example |
|---|---|---|
mail | email address | "user@example.com" |
sms | phone (E.164) | "+628123456789" |
whatsapp | phone (E.164) | "+628123456789" |
chat | chat ID / channel / webhook URL (driver-dependent) | "123456789", "#ops" |
push | a single device token | "fcm-token…" |
webhook | target URL | "https://hooks…" |
database | not routed — uses GetID() | — |
The channel name passed to RouteNotificationFor is the driver's Name(),
not the provider. By default chat drivers report "chat", mail drivers
"mail", etc. If you register a driver under a custom name (via its
ChannelName/Name config field), route on that same string.
Returning an empty string
If the recipient can't receive on a channel, return "". The channel is skipped for that recipient and dispatch moves on.
func (u User) RouteNotificationFor(channel string) string {
if channel == "sms" && u.Phone == "" {
return "" // user hasn't added a phone number
}
// ...
}Multiple destinations
Routing returns a single string. To send to multiple device tokens in one push, set them in the notification's ToPush method instead of relying on the route:
func (n OrderShipped) ToPush(notification.Notifiable) *push.Message {
return push.NewMessage().
SetTokens(token1, token2, token3). // FCM multicast
SetTitle("Shipped")
}To send the same notification to multiple recipients, model each as its own Notifiable and use SendMulti:
admins := []notification.Notifiable{admin1, admin2, admin3}
notifier.SendMulti(ctx, admins, AlertFired{})Non-user notifiables
Team, Channel, Organization — anything can be a Notifiable:
type SlackChannel struct {
ID string
Webhook string
}
func (c SlackChannel) GetID() string { return c.ID }
func (c SlackChannel) RouteNotificationFor(channel string) string {
if channel == "chat" {
return c.Webhook
}
return ""
}
notifier.Send(ctx, SlackChannel{ID: "ops", Webhook: "https://hooks.slack.com/…"}, IncidentFired{})NotifiableType (optional)
For the database channel, each stored row records a notifiable_type and notifiable_id (GetID()). The type defaults to "notifiables". Override it by implementing the optional NotifiableType interface:
func (u User) GetNotifiableType() string { return "users" }notification.NotifiableTypeOf(n) returns this value, falling back to "notifiables" when the method isn't implemented.
Testing
A fake notifiable is trivial:
type fakeUser struct{ id, email string }
func (f fakeUser) GetID() string { return f.id }
func (f fakeUser) RouteNotificationFor(channel string) string {
if channel == "mail" {
return f.email
}
return ""
}Use that in unit tests to isolate notification rendering from your real user persistence.