Skip to Content

Notifications

The notify package provides a unified notification dispatch system that supports multiple delivery channels: email (via the mailer package), SMS (via Twilio), Slack (via incoming webhooks), and database (for in-app notification centers). Each channel is an independent struct that implements a small ChannelSender interface, so you can add your own channels without modifying the package.

Import

import "github.com/gofastadev/gofasta/pkg/notify"

Key Types

Channel

// Channel represents a notification delivery channel. type Channel string const ( ChannelEmail Channel = "email" ChannelSMS Channel = "sms" ChannelSlack Channel = "slack" ChannelDatabase Channel = "database" )

Notification

// Notification holds the content to be sent across channels. type Notification struct { Subject string // email subject / SMS prefix Body string // plain text body HTMLBody string // HTML body (for email) Template string // email template name (optional) Data map[string]any // template data Channels []Channel // which channels to use (empty = all registered) }

Recipient

// Recipient represents who receives the notification. type Recipient struct { ID string // user ID (for database channel) Email string // for email channel Phone string // for SMS channel Name string // display name }

ChannelSender

// ChannelSender is implemented by each channel (email, SMS, Slack, database). type ChannelSender interface { Channel() Channel Send(ctx context.Context, recipient Recipient, notification Notification) error }

Notifier

// Notifier dispatches notifications to multiple channels. type Notifier struct { /* unexported fields */ }

Key Functions

FunctionSignatureDescription
NewNotifierfunc NewNotifier(logger *slog.Logger, senders ...ChannelSender) *NotifierCreates a notifier wired to the given channel senders
(n *Notifier).Sendfunc (n *Notifier) Send(ctx context.Context, recipient Recipient, notification Notification) errorDispatches the notification to every channel listed in notification.Channels (or all registered channels if empty)
(n *Notifier).RegisterChannelfunc (n *Notifier) RegisterChannel(sender ChannelSender)Adds a channel sender at runtime
NewEmailChannelfunc NewEmailChannel(sender mailer.EmailSender) *EmailChannelEmail channel backed by the mailer package
NewSMSChannelfunc NewSMSChannel(accountSID, authToken, fromNumber string) *SMSChannelTwilio-backed SMS channel
NewSlackChannelfunc NewSlackChannel(webhookURL string) *SlackChannelSlack incoming-webhook channel
NewDatabaseChannelfunc NewDatabaseChannel(db *gorm.DB) *DatabaseChannelPersists notifications to the notifications table for in-app delivery

Usage

Setting Up a Notifier

import ( "log/slog" "github.com/gofastadev/gofasta/pkg/mailer" "github.com/gofastadev/gofasta/pkg/notify" ) // Build your channels first (each channel is independent). emailCh := notify.NewEmailChannel(smtpSender) // any mailer.EmailSender slackCh := notify.NewSlackChannel("https://hooks.slack.com/services/...") smsCh := notify.NewSMSChannel(accountSID, authToken, "+15551234567") dbCh := notify.NewDatabaseChannel(db) // *gorm.DB // Wire them into a notifier. notifier := notify.NewNotifier(slog.Default(), emailCh, slackCh, smsCh, dbCh)

Sending a Notification to All Registered Channels

err := notifier.Send(ctx, notify.Recipient{ ID: "user-123", Email: "user@example.com", Phone: "+15551234567", Name: "Jane Doe", }, notify.Notification{ Subject: "Order confirmed", Body: "Your order #12345 has been confirmed.", HTMLBody: "<h1>Order confirmed</h1><p>Your order #12345 has been confirmed.</p>", Data: map[string]any{"order_id": "12345"}, }, )

When Notification.Channels is empty, the notifier fans out to every registered channel. Individual channel failures are logged and the last error is returned.

Targeting Specific Channels

// Slack-only alert err := notifier.Send(ctx, notify.Recipient{Name: "ops"}, notify.Notification{ Subject: "Deploy started", Body: "Release v1.4.2 is deploying to production.", Channels: []notify.Channel{notify.ChannelSlack}, }, )

Using an Email Template

When Notification.Template is set, the email channel renders the named template from the mailer’s template directory, passing Data as the template context. Otherwise it falls back to HTMLBody, then wraps Body in a <p> tag.

err := notifier.Send(ctx, recipient, notify.Notification{ Subject: "Welcome", Template: "welcome", Data: map[string]any{ "Name": recipient.Name, "AppName": "Acme", }, Channels: []notify.Channel{notify.ChannelEmail}, })

Registering a Channel at Runtime

notifier.RegisterChannel(notify.NewSlackChannel(altWebhook))

Writing a Custom Channel

Implement ChannelSender — two methods — and register it with the notifier.

type WebhookChannel struct { url string hc *http.Client } func (c *WebhookChannel) Channel() notify.Channel { return "webhook" } func (c *WebhookChannel) Send(ctx context.Context, r notify.Recipient, n notify.Notification) error { body, _ := json.Marshal(map[string]any{ "to": r.Email, "subject": n.Subject, "body": n.Body, }) req, _ := http.NewRequestWithContext(ctx, http.MethodPost, c.url, bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp, err := c.hc.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 400 { return fmt.Errorf("webhook returned %d", resp.StatusCode) } return nil } notifier.RegisterChannel(&WebhookChannel{url: "https://example.com/hook", hc: http.DefaultClient})

Notification in a Service Layer

func (s *OrderService) ConfirmOrder(ctx context.Context, orderID string) error { order, err := s.repo.FindByID(ctx, orderID) if err != nil { return err } order.Status = "confirmed" if err := s.repo.Update(ctx, order); err != nil { return err } return s.notifier.Send(ctx, notify.Recipient{ID: order.UserID, Email: order.CustomerEmail}, notify.Notification{ Subject: "Order confirmed", Body: fmt.Sprintf("Your order #%s has been confirmed.", order.ID), Template: "order_confirmed", Data: map[string]any{"OrderID": order.ID, "Total": order.Total}, Channels: []notify.Channel{notify.ChannelEmail, notify.ChannelDatabase}, }, ) }

Database Channel Schema

The database channel writes rows to a notifications table with this GORM model:

type DBNotification struct { ID uuid.UUID `gorm:"type:uuid;primary_key" json:"id"` UserID string `gorm:"not null;index" json:"userId"` Subject string `gorm:"not null" json:"subject"` Body string ` json:"body"` Data string `gorm:"type:text" json:"data"` // JSON-encoded ReadAt *time.Time ` json:"readAt,omitempty"` CreatedAt time.Time `gorm:"not null" json:"createdAt"` }

Run a migration to create this table before using the database channel.

  • Mailer — the email channel delegates to mailer.EmailSender
  • Queue — enqueue Send calls for async delivery
  • WebSocket — push real-time notifications to connected clients
  • Slack — Slack channel delegates to slack.Sender
  • WhatsApp — WhatsApp channel delegates to whatsapp.Sender
  • Push Notifications — Mobile push channel delegates to push.Sender
Last updated on