Alerts and delivery channels: the Channel port, recipients, and the rule that an automation without a configured channel is an error.
A business needs to send notifications (Alerts) to recipients when events occur. The notify library provides the core contract:
Alert: what happened (with amsgtype.Typeseverity)Source: what produced the alert (the module and record ID)Kind: a catalog entry explaining an alert codeArea: a business area an alert belongs toRecipient: who receives itChannel: how they receive itRegistry: configured channels the application can use
Modules register areas and kinds at startup so a screen can list and explain every possible alert without free-text lookup.
registry.RegisterArea(notify.Area{
Code: "supplies",
Name: lang.Text("Insumos"),
})
registry.RegisterKind(notify.Kind{
Code: "stock.low",
Name: lang.Text("Stock bajo"),
Help: lang.Text("Se avisa cuando el total de un ítem cae bajo su mínimo"),
Area: "supplies",
})Any module can raise an alert by using the events.Publisher it already has:
notify.Raise(publisher, notify.Alert{
Code: "stock.low",
Level: msgtype.Warning,
Subject: "Stock Low",
Body: "Item 123 is below minimum.",
})An alert manager then subscribes to these alerts to deliver them to the registry:
subscription, err := notify.Subscribe(subscriber, func(a notify.Alert) {
// Look up who needs to receive this alert, and which channels it should use
// Then call registry.Deliver(...)
}, func(err error) {
// Handle error
})
// subscription.Cancel() stops receiving alerts.package main
import (
"webtyp.com/msgtype"
"webtyp.com/notify"
"webtyp.com/notify/notifytest"
)
func main() {
registry := ¬ify.Registry{}
// Register channels
registry.Register(¬ifytest.Recorder{ChannelName: "email-main"})
// Validate automations at start-up
err := registry.Validate([]notify.Automation{
{Code: "stock.low", Channels: []string{"email-main"}},
})
if err != nil {
panic(err)
}
// Deliver an alert
alert := notify.Alert{
Code: "stock.low",
Level: msgtype.Warning,
Subject: "Stock Low",
Body: "Item 123 is below minimum.",
Source: notify.Ref{Type: "supply_item", ID: "123"},
}
recipient := notify.Recipient{ID: "usr1", Email: "admin@example.com"}
results := registry.Deliver(alert, recipient, []string{"email-main"})
for _, res := range results {
if res.Err != nil {
// handle delivery failure
}
}
}Channels can also be loaded from dynamic configurations via drivers. A driver declares its label and the configuration fields it needs from the person setting it up:
// Field is one value a driver needs to build a channel.
type Field struct {
Key string // e.g. "from", "api_token"
Label lang.Text
Help lang.Text
Secret bool
Required bool
}
type Driver interface {
Kind() string
Label() lang.Text
Fields() []Field
New(c Config, secrets SecretResolver) (Channel, error)
}// Loading channels from stored configurations without a restart
registry.Load([]notify.Config{
{Name: "email-marketing", Kind: "email", Enabled: true},
}, secretResolver)See ARCHITECTURE.md for more details.