Documentation
¶
Overview ¶
Package notifications sends notifications from SaaS applications through email, SMS, push and an in-app database inbox behind one API.
A notification is a type with a stable name that implements one interface per channel it supports (ToMail, ToSMS, ToPush, ToDatabase). A recipient implements Notifiable, which provides its identity and its Routes: email address, E.164 phone number and push device tokens. A Dispatcher selects the channels for each recipient, asks Preferences whether the recipient has opted out, renders the messages and delivers them:
rep, err := dispatcher.Send(ctx, []notifications.Notifiable{user}, InvoicePaid{Invoice: inv})
Every recipient and channel is attempted even when others fail. The Report has one Result per recipient and channel; the error joins the failures with errors.Join. Push tokens the provider reports as invalid are listed in the results (Report.InvalidTokens) so the application can delete them.
Channels ¶
Senders live in subpackages so that applications import only the providers they use: mail (with a built-in SMTP Mailer), sms with sms/twilio, and push with push/fcm. The database channel stores notifications through a Store; memstore and pgxstore implement it and Inbox provides the read side (list, unread count, mark read, delete).
Asynchronous delivery ¶
Send delivers inline. To deliver from a job queue, either enqueue the inputs (recipient IDs and the notification's fields) and call Send in the job, or call Prepare in the request, enqueue each returned Delivery as JSON, and call Deliver from the worker. The second form renders once and retries per channel: a failed SMS is retried without resending the email. Prepare assigns each Delivery an ID that the database channel reuses as the stored notification's ID, so a retried database delivery is stored once.
Testing ¶
Package notificationstest provides recording senders and a Dispatcher fake that implements Notifier, and package storetest is a conformance suite for Store implementations.
Example ¶
package main
import (
"context"
"fmt"
"log"
notifications "github.com/byteminus/go-notifications"
"github.com/byteminus/go-notifications/mail"
"github.com/byteminus/go-notifications/memstore"
"github.com/byteminus/go-notifications/notificationstest"
"github.com/byteminus/go-notifications/sms"
)
// Account is an application's user model.
type Account struct {
ID string
Name string
Email string
Phone string
}
func (a Account) NotifiableType() string { return "account" }
func (a Account) NotifiableID() string { return a.ID }
func (a Account) NotificationRoutes() notifications.Routes {
return notifications.Routes{Name: a.Name, Email: a.Email, Phone: a.Phone}
}
// InvoicePaid is sent by email and SMS and kept in the in-app inbox.
type InvoicePaid struct {
Number string
Amount string
}
func (InvoicePaid) Type() string { return "invoice.paid" }
func (n InvoicePaid) ToMail(_ context.Context, to notifications.Notifiable) (mail.Message, error) {
return mail.Message{
Subject: "Invoice " + n.Number + " paid",
Text: "We received " + n.Amount + ". Thank you.",
HTML: "<p>We received <strong>" + n.Amount + "</strong>. Thank you.</p>",
}, nil
}
func (n InvoicePaid) ToSMS(context.Context, notifications.Notifiable) (sms.Message, error) {
return sms.Message{Body: "Invoice " + n.Number + " paid: " + n.Amount}, nil
}
func (n InvoicePaid) ToDatabase(context.Context, notifications.Notifiable) (notifications.DatabaseMessage, error) {
return notifications.NewDatabaseMessage(map[string]string{"number": n.Number, "amount": n.Amount})
}
func main() {
mailer := ¬ificationstest.Mailer{} // use mail.NewSMTP in production
texts := ¬ificationstest.SMSSender{}
store := memstore.New() // use pgxstore in production
d, err := notifications.NewDispatcher(notifications.Config{
Mailer: mailer,
MailFrom: mail.Address{Name: "Acme Billing", Email: "billing@acme.example"},
SMS: texts,
Store: store,
// Ann has turned off SMS for invoices.
Preferences: notifications.PreferencesFunc(func(_ context.Context, to notifications.Notifiable, typ string, ch notifications.Channel) (bool, error) {
return to.NotifiableID() != "ann" || ch != notifications.ChannelSMS, nil
}),
})
if err != nil {
log.Fatal(err)
}
recipients := []notifications.Notifiable{
Account{ID: "ann", Name: "Ann", Email: "ann@example.com", Phone: "+14155550100"},
Account{ID: "bob", Name: "Bob", Email: "bob@example.com"},
}
rep, err := d.Send(context.Background(), recipients, InvoicePaid{Number: "INV-7", Amount: "$120.00"})
if err != nil {
log.Fatal(err)
}
for _, r := range rep.Results {
line := fmt.Sprintf("%s %s %s", r.Recipient, r.Channel, r.Status)
if r.SkipReason != "" {
line += " " + string(r.SkipReason)
}
fmt.Println(line)
}
fmt.Println("emails:", len(mailer.Messages()), "first to:", mailer.Messages()[0].To[0])
}
Output: account/ann mail sent account/ann sms skipped opted_out account/ann database sent account/bob mail sent account/bob sms skipped no_route account/bob database sent emails: 2 first to: "Ann" <ann@example.com>
Index ¶
- Constants
- Variables
- type Channel
- type Config
- type Cursor
- type DatabaseMessage
- type Delivery
- type DeliveryError
- type Dispatcher
- func (d *Dispatcher) Deliver(ctx context.Context, del Delivery) (Result, error)
- func (d *Dispatcher) Prepare(ctx context.Context, recipients []Notifiable, n Notification) ([]Delivery, Report, error)
- func (d *Dispatcher) Send(ctx context.Context, recipients []Notifiable, n Notification) (Report, error)
- type Inbox
- func (in *Inbox) Delete(ctx context.Context, r Recipient, id string) error
- func (in *Inbox) List(ctx context.Context, r Recipient, opts ListOptions) ([]StoredNotification, error)
- func (in *Inbox) MarkAllRead(ctx context.Context, r Recipient) (int, error)
- func (in *Inbox) MarkRead(ctx context.Context, r Recipient, id string) error
- func (in *Inbox) UnreadCount(ctx context.Context, r Recipient) (int, error)
- type InboxOptions
- type ListOptions
- type Notifiable
- type Notification
- type Notifier
- type Preferences
- type PreferencesFunc
- type Recipient
- type Report
- type Result
- type Routes
- type SkipReason
- type Status
- type Store
- type StoredNotification
- type ToDatabase
- type ToMail
- type ToPush
- type ToSMS
- type Via
Examples ¶
Constants ¶
const ( DefaultListLimit = 50 MaxListLimit = 200 )
Default and maximum page sizes for Store.List.
Variables ¶
var ( // ErrInvalidNotification is returned (wrapped) for a nil notification, // an empty notification type, or an invalid stored notification. ErrInvalidNotification = errors.New("notifications: invalid notification") // ErrInvalidRecipient is returned (wrapped) for a nil recipient or one // without a type and ID. ErrInvalidRecipient = errors.New("notifications: invalid recipient") // ErrUnknownChannel is reported for a channel name that is not built in. ErrUnknownChannel = errors.New("notifications: unknown channel") // ErrUnsupportedChannel is reported when Via selects a channel the // notification does not implement. ErrUnsupportedChannel = errors.New("notifications: notification does not support channel") // ErrChannelNotConfigured is reported when a channel is selected but the // Dispatcher has no sender (or store) for it. ErrChannelNotConfigured = errors.New("notifications: channel not configured") // ErrInvalidDelivery is returned (wrapped) by Deliver for a Delivery // whose payload does not match its channel. ErrInvalidDelivery = errors.New("notifications: invalid delivery") // ErrInvalidConfig is returned (wrapped) by NewDispatcher and NewInbox. ErrInvalidConfig = errors.New("notifications: invalid configuration") // ErrNotFound is returned when a stored notification does not exist or // belongs to another recipient. The two cases are indistinguishable on // purpose, so callers cannot probe other recipients' inboxes. ErrNotFound = errors.New("notifications: notification not found") // ErrDuplicateID is returned by Store.Insert when the ID already exists. ErrDuplicateID = errors.New("notifications: duplicate notification id") // ErrInvalidListOptions is returned (wrapped) for a malformed cursor or a // negative limit. ErrInvalidListOptions = errors.New("notifications: invalid list options") )
Functions ¶
This section is empty.
Types ¶
type Channel ¶
type Channel string
Channel names a delivery channel.
type Config ¶
type Config struct {
// Mailer enables the mail channel.
Mailer mail.Mailer
// MailFrom is used for messages that do not set From.
MailFrom mail.Address
// SMS enables the sms channel.
SMS sms.Sender
// Push enables the push channel.
Push push.Sender
// Store enables the database channel.
Store Store
// Preferences, when set, is consulted for every recipient and channel
// before rendering.
Preferences Preferences
// Concurrency is the number of deliveries Send runs at once. Zero means
// one: deliveries run sequentially.
Concurrency int
// Now stamps database notifications. Defaults to time.Now.
Now func() time.Time
}
Config configures a Dispatcher. A channel is enabled by setting its sender (or Store for the database channel); at least one is required.
type Cursor ¶
Cursor is a keyset pagination position: List returns notifications older than it in (CreatedAt, ID) order. Keyset pagination keeps pages stable while new notifications arrive.
type DatabaseMessage ¶
type DatabaseMessage struct {
Data json.RawMessage `json:"data"`
}
DatabaseMessage is the payload stored for an in-app notification. Data must be a JSON object.
func NewDatabaseMessage ¶
func NewDatabaseMessage(v any) (DatabaseMessage, error)
NewDatabaseMessage marshals v, which must encode as a JSON object, into a DatabaseMessage.
type Delivery ¶
type Delivery struct {
// ID is a UUIDv7 assigned by Prepare. The database channel uses it as
// the stored notification's ID, which makes redelivery of the same
// Delivery idempotent.
ID string `json:"id"`
NotificationType string `json:"notification_type"`
Recipient Recipient `json:"recipient"`
Channel Channel `json:"channel"`
Mail *mail.Message `json:"mail,omitempty"`
SMS *sms.Message `json:"sms,omitempty"`
Push *push.Message `json:"push,omitempty"`
Database *DatabaseMessage `json:"database,omitempty"`
}
Delivery is one rendered message for one recipient on one channel. It is JSON-serializable so that it can be queued and delivered later by a worker calling Deliver; exactly one payload field is set, matching Channel.
type DeliveryError ¶
DeliveryError describes a failed channel. Unwrap exposes the cause, so errors.Is works with provider and package sentinels alike.
type Dispatcher ¶
type Dispatcher struct {
// contains filtered or unexported fields
}
Dispatcher renders notifications and delivers them on every selected channel. It is safe for concurrent use.
func NewDispatcher ¶
func NewDispatcher(cfg Config) (*Dispatcher, error)
NewDispatcher validates cfg and returns a Dispatcher.
func (*Dispatcher) Deliver ¶
Deliver sends one prepared Delivery and reports the outcome. The returned error is the Result's Err. Delivering the same database Delivery twice stores it once.
func (*Dispatcher) Prepare ¶
func (d *Dispatcher) Prepare(ctx context.Context, recipients []Notifiable, n Notification) ([]Delivery, Report, error)
Prepare selects channels, applies Preferences and renders n for every recipient without delivering anything. It returns the deliveries to make and a Report of the channels that were skipped or failed while preparing. Enqueue the deliveries (they marshal to JSON) and call Deliver from a worker to send asynchronously. Preferences are evaluated now, not at delivery time.
Example ¶
package main
import (
"context"
"encoding/json"
"fmt"
"log"
notifications "github.com/byteminus/go-notifications"
"github.com/byteminus/go-notifications/mail"
"github.com/byteminus/go-notifications/memstore"
"github.com/byteminus/go-notifications/notificationstest"
"github.com/byteminus/go-notifications/sms"
)
// Account is an application's user model.
type Account struct {
ID string
Name string
Email string
Phone string
}
func (a Account) NotifiableType() string { return "account" }
func (a Account) NotifiableID() string { return a.ID }
func (a Account) NotificationRoutes() notifications.Routes {
return notifications.Routes{Name: a.Name, Email: a.Email, Phone: a.Phone}
}
// InvoicePaid is sent by email and SMS and kept in the in-app inbox.
type InvoicePaid struct {
Number string
Amount string
}
func (InvoicePaid) Type() string { return "invoice.paid" }
func (n InvoicePaid) ToMail(_ context.Context, to notifications.Notifiable) (mail.Message, error) {
return mail.Message{
Subject: "Invoice " + n.Number + " paid",
Text: "We received " + n.Amount + ". Thank you.",
HTML: "<p>We received <strong>" + n.Amount + "</strong>. Thank you.</p>",
}, nil
}
func (n InvoicePaid) ToSMS(context.Context, notifications.Notifiable) (sms.Message, error) {
return sms.Message{Body: "Invoice " + n.Number + " paid: " + n.Amount}, nil
}
func (n InvoicePaid) ToDatabase(context.Context, notifications.Notifiable) (notifications.DatabaseMessage, error) {
return notifications.NewDatabaseMessage(map[string]string{"number": n.Number, "amount": n.Amount})
}
func main() {
d, err := notifications.NewDispatcher(notifications.Config{
Mailer: ¬ificationstest.Mailer{},
MailFrom: mail.Address{Email: "billing@acme.example"},
Store: memstore.New(),
})
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
ann := Account{ID: "ann", Email: "ann@example.com"}
// In the request: render now and enqueue one job per delivery, ideally
// in the same transaction as the write that caused the notification.
deliveries, _, err := d.Prepare(ctx, []notifications.Notifiable{ann}, InvoicePaid{Number: "INV-8", Amount: "$5.00"})
if err != nil {
log.Fatal(err)
}
var queue [][]byte
for _, del := range deliveries {
payload, err := json.Marshal(del)
if err != nil {
log.Fatal(err)
}
queue = append(queue, payload)
}
// In the worker: decode and deliver; return the error to retry the job.
for _, payload := range queue {
var del notifications.Delivery
if err := json.Unmarshal(payload, &del); err != nil {
log.Fatal(err)
}
res, err := d.Deliver(ctx, del)
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Channel, res.Status)
}
}
Output: mail sent database sent
func (*Dispatcher) Send ¶
func (d *Dispatcher) Send(ctx context.Context, recipients []Notifiable, n Notification) (Report, error)
Send prepares n for every recipient and delivers it on every selected channel. A failure on one channel or recipient never stops the others. The Report holds one Result per recipient and channel; the error joins every failure with errors.Join and is nil when nothing failed. Skips (opt-outs, missing routes) are not failures.
Send returns an error with an empty Report only when n itself is invalid.
type Inbox ¶
type Inbox struct {
// contains filtered or unexported fields
}
Inbox is the read side of the database channel: what an application's notification bell and inbox page call. It is safe for concurrent use when its Store is.
Example ¶
package main
import (
"context"
"fmt"
"log"
notifications "github.com/byteminus/go-notifications"
"github.com/byteminus/go-notifications/mail"
"github.com/byteminus/go-notifications/memstore"
"github.com/byteminus/go-notifications/sms"
)
// Account is an application's user model.
type Account struct {
ID string
Name string
Email string
Phone string
}
func (a Account) NotifiableType() string { return "account" }
func (a Account) NotifiableID() string { return a.ID }
func (a Account) NotificationRoutes() notifications.Routes {
return notifications.Routes{Name: a.Name, Email: a.Email, Phone: a.Phone}
}
// InvoicePaid is sent by email and SMS and kept in the in-app inbox.
type InvoicePaid struct {
Number string
Amount string
}
func (InvoicePaid) Type() string { return "invoice.paid" }
func (n InvoicePaid) ToMail(_ context.Context, to notifications.Notifiable) (mail.Message, error) {
return mail.Message{
Subject: "Invoice " + n.Number + " paid",
Text: "We received " + n.Amount + ". Thank you.",
HTML: "<p>We received <strong>" + n.Amount + "</strong>. Thank you.</p>",
}, nil
}
func (n InvoicePaid) ToSMS(context.Context, notifications.Notifiable) (sms.Message, error) {
return sms.Message{Body: "Invoice " + n.Number + " paid: " + n.Amount}, nil
}
func (n InvoicePaid) ToDatabase(context.Context, notifications.Notifiable) (notifications.DatabaseMessage, error) {
return notifications.NewDatabaseMessage(map[string]string{"number": n.Number, "amount": n.Amount})
}
func main() {
ctx := context.Background()
store := memstore.New()
d, err := notifications.NewDispatcher(notifications.Config{Store: store})
if err != nil {
log.Fatal(err)
}
ann := Account{ID: "ann"}
for _, number := range []string{"INV-1", "INV-2"} {
if _, err := d.Send(ctx, []notifications.Notifiable{ann}, InvoicePaid{Number: number, Amount: "$1"}); err != nil {
log.Fatal(err)
}
}
inbox, err := notifications.NewInbox(store, notifications.InboxOptions{})
if err != nil {
log.Fatal(err)
}
me := notifications.RecipientOf(ann)
items, err := inbox.List(ctx, me, notifications.ListOptions{Limit: 20})
if err != nil {
log.Fatal(err)
}
for _, n := range items {
fmt.Println(n.Type, string(n.Data))
}
if err := inbox.MarkRead(ctx, me, items[0].ID); err != nil {
log.Fatal(err)
}
unread, err := inbox.UnreadCount(ctx, me)
if err != nil {
log.Fatal(err)
}
fmt.Println("unread:", unread)
}
Output: invoice.paid {"amount":"$1","number":"INV-2"} invoice.paid {"amount":"$1","number":"INV-1"} unread: 1
func NewInbox ¶
func NewInbox(store Store, opts InboxOptions) (*Inbox, error)
NewInbox returns an Inbox over store.
func (*Inbox) Delete ¶
Delete removes one of r's notifications. It returns ErrNotFound if r has no notification with that ID.
func (*Inbox) List ¶
func (in *Inbox) List(ctx context.Context, r Recipient, opts ListOptions) ([]StoredNotification, error)
List returns r's notifications, newest first.
func (*Inbox) MarkAllRead ¶
MarkAllRead marks all of r's unread notifications as read and returns how many changed.
type InboxOptions ¶
InboxOptions configures an Inbox.
type ListOptions ¶
type ListOptions struct {
// OnlyUnread restricts the result to unread notifications.
OnlyUnread bool
// Limit is the page size: zero means DefaultListLimit and values above
// MaxListLimit are capped.
Limit int
// Before, when set, returns only notifications after this position in
// newest-first order; pass the last item's Cursor to get the next page.
Before *Cursor
}
ListOptions filters and pages Store.List.
func (ListOptions) EffectiveLimit ¶
func (o ListOptions) EffectiveLimit() int
EffectiveLimit returns the page size a Store should use.
func (ListOptions) Validate ¶
func (o ListOptions) Validate() error
Validate reports whether the options are usable.
type Notifiable ¶
type Notifiable interface {
NotifiableType() string
NotifiableID() string
NotificationRoutes() Routes
}
Notifiable is a recipient: usually a user, sometimes a team or an anonymous address. Type and ID identify it in the inbox and to Preferences; Routes says where to reach it.
type Notification ¶
type Notification interface {
Type() string
}
Notification is something to tell a recipient. Type is a stable name such as "invoice.paid": it is stored with database notifications and passed to Preferences, so renaming it is a breaking change for stored data and opt-outs.
A notification takes part in a channel by implementing the matching interface: ToMail, ToSMS, ToPush or ToDatabase. It may also implement Via to choose channels per recipient.
type Notifier ¶
type Notifier interface {
Send(ctx context.Context, recipients []Notifiable, n Notification) (Report, error)
}
Notifier sends a notification to recipients. *Dispatcher implements it; notificationstest.Dispatcher is a recording fake for application tests.
type Preferences ¶
type Preferences interface {
Allows(ctx context.Context, to Notifiable, notificationType string, ch Channel) (bool, error)
}
Preferences decides whether a recipient accepts a notification type on a channel. Notifications that must always be delivered, such as password resets, should be allowed by the implementation regardless of opt-outs.
An error from Allows fails that channel rather than sending: when the preference store is unavailable the safe default is to respect a possible opt-out.
type PreferencesFunc ¶
type PreferencesFunc func(ctx context.Context, to Notifiable, notificationType string, ch Channel) (bool, error)
PreferencesFunc adapts a function to the Preferences interface.
func (PreferencesFunc) Allows ¶
func (f PreferencesFunc) Allows(ctx context.Context, to Notifiable, notificationType string, ch Channel) (bool, error)
Allows calls f.
type Recipient ¶
Recipient identifies a Notifiable by type and ID. It is how inbox operations name an owner without loading the full recipient.
func RecipientOf ¶
func RecipientOf(n Notifiable) Recipient
RecipientOf returns the Recipient identifying n.
type Report ¶
type Report struct {
Results []Result
}
Report collects the results of a Send or Prepare, ordered by recipient and then channel.
func (Report) Err ¶
Err joins the errors of every failed result with errors.Join, or returns nil when nothing failed.
func (Report) InvalidTokens ¶
InvalidTokens groups the push tokens to delete by recipient.
type Result ¶
type Result struct {
// DeliveryID identifies the delivery; empty when the channel failed or
// was skipped before a Delivery was prepared. For the database channel
// it is also the stored notification's ID.
DeliveryID string
NotificationType string
Recipient Recipient
// Channel is empty only for a result reporting an invalid recipient.
Channel Channel
Status Status
SkipReason SkipReason
// MessageID is the provider's identifier when it returns one (Twilio
// SID, stored notification ID). Push results leave it empty because a
// message fans out to several devices.
MessageID string
// InvalidTokens lists push device tokens the provider rejected as
// unregistered or malformed; delete them from the recipient. It can be
// non-empty on a sent result when other devices were reached.
InvalidTokens []string
// Err is a *DeliveryError when Status is StatusFailed, nil otherwise.
// For push it may coexist with successful delivery to some devices.
Err error
}
Result is the outcome of one channel for one recipient.
type Routes ¶
type Routes struct {
// Name is the display name used in email headers.
Name string `json:"name,omitempty"`
// Email is a bare address such as ann@example.com.
Email string `json:"email,omitempty"`
// Phone is an E.164 number such as +14155550100.
Phone string `json:"phone,omitempty"`
// PushTokens are the recipient's device tokens.
PushTokens []string `json:"push_tokens,omitempty"`
}
Routes holds a recipient's addresses. Empty fields mean the recipient cannot be reached on that channel, and the channel is skipped.
type SkipReason ¶
type SkipReason string
SkipReason explains a StatusSkipped result.
const ( // SkipOptedOut means Preferences did not allow the channel. SkipOptedOut SkipReason = "opted_out" // SkipNoRoute means the recipient has no address for the channel and // the rendered message named none. SkipNoRoute SkipReason = "no_route" )
The possible skip reasons.
type Status ¶
type Status string
Status is the outcome of one channel for one recipient.
const ( // StatusSent means the provider (or store) accepted the message. StatusSent Status = "sent" // StatusSkipped means nothing was attempted, for the reason given in // Result.SkipReason. A skip is not an error. StatusSkipped Status = "skipped" // StatusFailed means the attempt failed; Result.Err says why. StatusFailed Status = "failed" )
The possible statuses.
type Store ¶
type Store interface {
// Insert stores n. It returns ErrDuplicateID if n.ID exists.
Insert(ctx context.Context, n StoredNotification) error
// List returns r's notifications, newest first.
List(ctx context.Context, r Recipient, opts ListOptions) ([]StoredNotification, error)
// UnreadCount returns the number of r's unread notifications.
UnreadCount(ctx context.Context, r Recipient) (int, error)
// MarkRead sets ReadAt to at unless it is already set, in which case the
// original time is kept. It returns ErrNotFound if r has no such
// notification.
MarkRead(ctx context.Context, r Recipient, id string, at time.Time) error
// MarkAllRead marks every unread notification of r as read at at and
// returns how many changed.
MarkAllRead(ctx context.Context, r Recipient, at time.Time) (int, error)
// Delete removes a notification. It returns ErrNotFound if r has no such
// notification.
Delete(ctx context.Context, r Recipient, id string) error
}
Store persists in-app notifications. Every method that addresses an existing notification takes the owning Recipient and treats a notification owned by someone else as missing (ErrNotFound), so an ID taken from a request can never reach another recipient's inbox.
Implementations must be safe for concurrent use, validate their input with StoredNotification.Validate, Recipient.Validate and ListOptions.Validate, return times in UTC with microsecond precision, and pass the storetest conformance suite.
type StoredNotification ¶
type StoredNotification struct {
// ID is a UUID in canonical lowercase form.
ID string `json:"id"`
Recipient Recipient `json:"recipient"`
Type string `json:"type"`
Data json.RawMessage `json:"data"`
ReadAt *time.Time `json:"read_at"`
CreatedAt time.Time `json:"created_at"`
}
StoredNotification is an in-app notification as kept by a Store.
func (StoredNotification) Cursor ¶
func (n StoredNotification) Cursor() Cursor
Cursor returns the position after n, for fetching the next page.
func (StoredNotification) Unread ¶
func (n StoredNotification) Unread() bool
Unread reports whether the notification has not been read.
func (StoredNotification) Validate ¶
func (n StoredNotification) Validate() error
Validate reports whether n satisfies the constraints every Store enforces: a UUID ID, a valid recipient, a type, a JSON object as data and a creation time.
type ToDatabase ¶
type ToDatabase interface {
ToDatabase(ctx context.Context, to Notifiable) (DatabaseMessage, error)
}
ToDatabase is implemented by notifications that are stored in the recipient's in-app inbox.
type ToMail ¶
ToMail is implemented by notifications that can be sent by email. A message without recipients is addressed to the recipient's Routes.Email, and a message without From uses Config.MailFrom.
type ToPush ¶
ToPush is implemented by notifications that can be sent as push notifications. A message without Tokens is addressed to the recipient's Routes.PushTokens.
type ToSMS ¶
ToSMS is implemented by notifications that can be sent by SMS. A message without To is addressed to the recipient's Routes.Phone.
type Via ¶
type Via interface {
Via(to Notifiable) []Channel
}
Via is implemented by notifications that choose their channels per recipient. Without it, a notification goes to every channel it implements and the Dispatcher has configured. Channels named by Via must be both implemented and configured, otherwise the result for that channel is a failure: an explicit choice that cannot be honoured is a programming error worth surfacing.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package mail defines the email message type used by go-notifications, the Mailer interface that provider adapters implement, and a built-in SMTP Mailer.
|
Package mail defines the email message type used by go-notifications, the Mailer interface that provider adapters implement, and a built-in SMTP Mailer. |
|
Package memstore is an in-memory notifications.Store for tests, examples and single-process prototypes.
|
Package memstore is an in-memory notifications.Store for tests, examples and single-process prototypes. |
|
Package notificationstest provides test doubles for applications that use go-notifications.
|
Package notificationstest provides test doubles for applications that use go-notifications. |
|
Package pgxstore is a PostgreSQL notifications.Store built on pgx v5.
|
Package pgxstore is a PostgreSQL notifications.Store built on pgx v5. |
|
Package push defines the push notification message type used by go-notifications and the Sender interface that provider adapters implement.
|
Package push defines the push notification message type used by go-notifications and the Sender interface that provider adapters implement. |
|
fcm
Package fcm implements push.Sender over the Firebase Cloud Messaging HTTP v1 API, authenticating with a Google service account through golang.org/x/oauth2/google.
|
Package fcm implements push.Sender over the Firebase Cloud Messaging HTTP v1 API, authenticating with a Google service account through golang.org/x/oauth2/google. |
|
Package sms defines the SMS message type used by go-notifications and the Sender interface that provider adapters implement.
|
Package sms defines the SMS message type used by go-notifications and the Sender interface that provider adapters implement. |
|
twilio
Package twilio implements sms.Sender over the Twilio Programmable Messaging REST API using only net/http.
|
Package twilio implements sms.Sender over the Twilio Programmable Messaging REST API using only net/http. |
|
Package storetest is a conformance suite for notifications.Store implementations.
|
Package storetest is a conformance suite for notifications.Store implementations. |