Documentation
¶
Overview ¶
Package notifier provides stored, at-least-once notification delivery.
Create destinations and a dispatcher, enqueue items, then run a delivery cycle:
transport, err := email.NewTransport[Alert](email.Config{
Host: "smtp.example.com", Port: "465",
Username: user, Password: pass, From: "alerts@example.com",
})
onCall, err := transport.Recipient("email:on-call", "on-call@example.com", render)
dispatcher, err := notifier.NewDispatcher(
memory.New[Alert](), notifier.DispatcherConfig{}, onCall)
err = dispatcher.Enqueue(ctx, notifier.Item[Alert]{
ID: 1, Payload: Alert{Text: "disk almost full"},
})
report, err := dispatcher.Run(ctx)
Run claims queued work, sends it, and records each outcome. Retryable failures remain queued. Permanent destination failures quarantine that destination.
for _, result := range report.Results {
if errors.Is(result.SendErr, notifier.ErrQuarantine) {
log.Printf("destination %s quarantined", result.Destination)
}
}
Multiple destinations must all succeed unless DispatcherConfig.FirstSuccess is set, in which case their order defines the fallback chain. The Store holds unfinished work; memory.New is process-local and sqlite.New survives restarts.
The first Run registers the plan and probes destinations that support checks. If delivery succeeds but recording fails, Run reports OutcomeDeliveredUnrecorded and the item may be delivered again.
Index ¶
Constants ¶
const ( // OutcomeUnknown is not a valid delivery resolution. OutcomeUnknown = core.OutcomeUnknown // OutcomeDelivered records successful provider acceptance. OutcomeDelivered = core.OutcomeDelivered // OutcomeRetryableFailure keeps the batch eligible for later delivery. OutcomeRetryableFailure = core.OutcomeRetryableFailure // OutcomeFailedPermanent terminalizes the affected failure scope. OutcomeFailedPermanent = core.OutcomeFailedPermanent // OutcomeDeliveredUnrecorded means delivery succeeded but recording failed; it may repeat. OutcomeDeliveredUnrecorded = core.OutcomeDeliveredUnrecorded )
Variables ¶
var ( // ErrInvalidDispatcherConfig marks invalid dispatcher configuration. ErrInvalidDispatcherConfig = errors.New("invalid dispatcher configuration") // ErrInvalidDestinationBinding marks an invalid destination list. ErrInvalidDestinationBinding = errors.New("invalid destination binding") )
var ( // ErrStoreStaleLeaseToken marks an outcome written after losing its lease. ErrStoreStaleLeaseToken = core.ErrStoreStaleLeaseToken // ErrStorePayloadConflict marks reuse of an Item.ID with a different payload. ErrStorePayloadConflict = core.ErrStorePayloadConflict // ErrStoreWorkDoesntExist marks an outcome for an unknown batch. ErrStoreWorkDoesntExist = core.ErrStoreWorkDoesntExist // ErrStoreInvalidTransition marks a write that contradicts stored state. ErrStoreInvalidTransition = core.ErrStoreInvalidTransition // ErrStoreBusy marks transient backend contention, which is retried. ErrStoreBusy = core.ErrStoreBusy ErrStoreUnavailable = core.ErrStoreUnavailable )
var ( // ErrRetryable marks a failure that may succeed on a later attempt. ErrRetryable = core.ErrRetryable // ErrPermanent marks a failure that will not succeed on a later attempt. ErrPermanent = core.ErrPermanent // ErrQuarantine marks a permanent failure that disables the destination. ErrQuarantine = core.ErrQuarantine )
Delivery errors retain the provider cause for errors.Is and errors.As. ErrQuarantine also matches ErrPermanent.
var ErrDestinationImplementationMissing = errors.New(
"dispatcher destination implementation missing",
)
ErrDestinationImplementationMissing marks work with no bound destination.
Functions ¶
This section is empty.
Types ¶
type Destination ¶
type Destination[T any] interface { // ID returns the stable identifier used in delivery plans. ID() core.DestinationID // Send delivers one batch and returns only once the provider has accepted or rejected it. Send(ctx context.Context, batch []T) error }
Destination sends batches to one addressable endpoint. Use email.Transport.Recipient, telegram.Client.Chat, or another transport in this module.
type Dispatcher ¶
type Dispatcher[T any] struct { // contains filtered or unexported fields }
Dispatcher delivers queued items for one destination plan. Run calls are serialized; sends are concurrent and outcomes are persisted serially. Durable delivery state lives in the Store.
func NewDispatcher ¶
func NewDispatcher[T any]( store Store[T], config DispatcherConfig, destinations ...Destination[T], ) (*Dispatcher[T], error)
NewDispatcher validates and binds destinations in order. Their IDs and order define the delivery plan and FirstSuccess fallback order.
func (*Dispatcher[T]) Enqueue ¶
func (d *Dispatcher[T]) Enqueue(ctx context.Context, items ...Item[T]) error
Enqueue persists items for later delivery; it does not send them. Reusing an Item.ID with the same payload is a no-op; a different payload returns ErrStorePayloadConflict.
func (*Dispatcher[T]) Run ¶
func (d *Dispatcher[T]) Run(ctx context.Context) (Report, error)
Run performs one delivery cycle. An empty queue is not an error. Concurrent calls wait for the active cycle or return when their context is canceled. The first call registers the plan and probes supported destinations before delivery. Before claiming, it retries still-leased writes for successful deliveries. Once its plan is empty, Run may drain another pending plan; unbound work fails permanently.
func (*Dispatcher[T]) Start ¶
func (d *Dispatcher[T]) Start( ctx context.Context, interval time.Duration, onCycle func(Report, error), ) (wait func(context.Context) error)
Start runs the dispatcher on a schedule until ctx is cancelled, calling onCycle on every cycle and returns the function that can be called to cancel the loop.
wait := dispatcher.Start(ctx, time.Minute, func(_ notifier.Report, err error) {
if err != nil {
slog.Error("delivery cycle failed", "error", err)
}
})
defer wait(shutdownCtx)
type DispatcherConfig ¶
type DispatcherConfig struct {
// Workers limits batches claimed and sent concurrently. Defaults to GOMAXPROCS.
Workers int
// FirstSuccess stops at the first accepting destination. By default, all must succeed.
FirstSuccess bool
// MaxItemsPerWork limits one Destination.Send batch. Defaults to 100.
MaxItemsPerWork int
// AttemptLimit includes the initial send. Defaults to 5; the maximum is 20.
AttemptLimit int
// AttemptTimeout bounds each send. Timeouts are retryable. Defaults to 30s.
AttemptTimeout time.Duration
// InitialBackoff is doubled after each retry. Defaults to 1s; the maximum is 1h.
InitialBackoff time.Duration
// JitterPercent varies backoff by ±N percent. Defaults to 20; the maximum is 100.
JitterPercent int
// DisableJitter uses the nominal backoff without variation.
DisableJitter bool
// PersistFailureDetail stores provider error text for permanent failures.
// It defaults off because provider errors may contain recipient data.
PersistFailureDetail bool
// ResolveTimeout bounds outcome persistence, including retries. Defaults to 5s.
ResolveTimeout time.Duration
// ProbeWorkers limits concurrent checks before the first delivery. Defaults to 4.
ProbeWorkers int
// ProbeTimeout bounds each check. A timeout leaves stored state unchanged. Defaults to 10s.
ProbeTimeout time.Duration
// SkipProbing disables the automatic probe before the first Run. Defaults to false;
// skipping makes delivery to an unreachable destination less reliable.
SkipProbing bool
}
DispatcherConfig controls dispatch concurrency, retries, and destination probes. Zero values select the defaults below; invalid numeric values return ErrInvalidDispatcherConfig.
type Prober ¶
Prober is a Destination that can check reachability before delivery. Dispatcher probes any bound destination implementing it, including a wrapper embedding a plain Destination to add a custom Probe.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package email sends notification batches over implicit-TLS SMTP.
|
Package email sends notification batches over implicit-TLS SMTP. |
|
internal
|
|
|
core
Package core defines notifier's internal persistence model.
|
Package core defines notifier's internal persistence model. |
|
store
|
|
|
memory
Package memory provides a process-local implementation of core.Store.
|
Package memory provides a process-local implementation of core.Store. |
|
sqlite
Package sqlite implements core.Store on caller-owned, driver-neutral SQLite.
|
Package sqlite implements core.Store on caller-owned, driver-neutral SQLite. |
|
Package telegram sends notification batches through the Telegram Bot API.
|
Package telegram sends notification batches through the Telegram Bot API. |