Documentation
¶
Overview ¶
Package herald provides a unified, multi-channel notification delivery engine.
Herald supports email, SMS, push notifications, and in-app notifications with pluggable provider drivers, a template system with i18n support, and scoped configuration overrides (app → org → user).
Index ¶
- Constants
- Variables
- type ChannelType
- type Config
- type CredentialState
- type DeviceResult
- type EncryptReport
- type Herald
- func (h *Herald) Audit(ctx context.Context, ...)
- func (h *Herald) CheckRouting(ctx context.Context, cfg *scope.Config) error
- func (h *Herald) Config() Config
- func (h *Herald) CreateProvider(ctx context.Context, p *provider.Provider) error
- func (h *Herald) CredentialKeyID() string
- func (h *Herald) CredentialStatus(p *provider.Provider) []CredentialState
- func (h *Herald) DeleteDevice(ctx context.Context, appID, id string) error
- func (h *Herald) DeleteProvider(ctx context.Context, appID string, providerID id.ProviderID) error
- func (h *Herald) DeleteUserDevices(ctx context.Context, appID, userID string) error
- func (h *Herald) Drivers() *driver.Registry
- func (h *Herald) EncryptStoredCredentials(ctx context.Context, appID string) (EncryptReport, error)
- func (h *Herald) GetDevice(ctx context.Context, appID, id string) (*device.Device, error)
- func (h *Herald) GetProvider(ctx context.Context, appID string, providerID id.ProviderID) (*provider.Provider, error)
- func (h *Herald) Health(ctx context.Context) error
- func (h *Herald) ListDevices(ctx context.Context, appID, userID string) ([]*device.Device, error)
- func (h *Herald) Notify(ctx context.Context, req *NotifyRequest) ([]*SendResult, error)
- func (h *Herald) PreviewSend(ctx context.Context, req *SendRequest) (*SendPreview, error)
- func (h *Herald) ResetDefaultTemplates(ctx context.Context, appID string) error
- func (h *Herald) ResolveProvider(ctx context.Context, appID, orgID, userID, channel string) (*scope.ResolveResult, error)
- func (h *Herald) SaveDevice(ctx context.Context, d *device.Device) error
- func (h *Herald) SeedConfiguredProviders(ctx context.Context, providers []provider.Provider) error
- func (h *Herald) SeedDefaultProviders(ctx context.Context, appID string) error
- func (h *Herald) SeedDefaultTemplates(ctx context.Context, appID string) error
- func (h *Herald) Send(ctx context.Context, req *SendRequest) (*SendResult, error)
- func (h *Herald) Start(_ context.Context)
- func (h *Herald) Stop(_ context.Context)
- func (h *Herald) Store() store.Store
- func (h *Herald) UpdateProvider(ctx context.Context, appID string, providerID id.ProviderID, u ProviderUpdate) (*provider.Provider, error)
- func (h *Herald) ValidateProvider(p *provider.Provider) error
- type NotifyRequest
- type Option
- func WithChronicle(c bridge.Chronicle) Option
- func WithCredentialKey(keyID string, key []byte) Option
- func WithDefaultLocale(locale string) Option
- func WithDeviceStore(s device.Store) Option
- func WithDriver(d driver.Driver) Option
- func WithLogger(logger *slog.Logger) Option
- func WithMaxBatchSize(n int) Option
- func WithMetrics(m gometrics.Metrics) Option
- func WithPreviousCredentialKey(keyID string, key []byte) Option
- func WithStore(s store.Store) Option
- type ProviderUpdate
- type SendPreview
- type SendRequest
- type SendResult
Constants ¶
const ( ProtectionAESGCM = credential.ProtectionAESGCM ProtectionPlaintext = credential.ProtectionPlaintext )
Protection values reported per credential.
Variables ¶
var ( // ErrNoStore is returned when a Herald instance is created without a store. ErrNoStore = errors.New("herald: store is required") // ErrProviderNotFound is returned when a provider cannot be found. ErrProviderNotFound = store.ErrProviderNotFound // ErrProviderDisabled is returned when the resolved provider is disabled. ErrProviderDisabled = errors.New("herald: provider is disabled") // ErrNoProviderConfigured is returned when no provider is configured for the requested channel. ErrNoProviderConfigured = errors.New("herald: no provider configured for channel") // ErrDriverNotFound is returned when a driver is not registered. ErrDriverNotFound = errors.New("herald: driver not found") // ErrTemplateNotFound is returned when a template cannot be found. ErrTemplateNotFound = store.ErrTemplateNotFound // ErrVersionNotFound is returned when a template version cannot be found. ErrVersionNotFound = store.ErrVersionNotFound // ErrTemplateDisabled is returned when the resolved template is disabled. ErrTemplateDisabled = errors.New("herald: template is disabled") // ErrNoVersionForLocale is returned when no template version matches the requested locale. ErrNoVersionForLocale = template.ErrNoVersionForLocale // ErrTemplateRenderFailed is returned when template rendering fails. ErrTemplateRenderFailed = template.ErrTemplateRenderFailed // ErrMissingRequiredVariable is returned when a required template variable is not provided. ErrMissingRequiredVariable = template.ErrMissingRequiredVariable // ErrMessageNotFound is returned when a message cannot be found. ErrMessageNotFound = store.ErrMessageNotFound // ErrInboxNotFound is returned when an in-app notification cannot be found. ErrInboxNotFound = store.ErrNotificationNotFound // ErrPreferenceNotFound is returned when user preferences cannot be found. ErrPreferenceNotFound = store.ErrPreferenceNotFound // ErrScopedConfigNotFound is returned when no scoped config is found. ErrScopedConfigNotFound = store.ErrScopedConfigNotFound // ErrInvalidChannel is returned when an unsupported channel type is specified. ErrInvalidChannel = errors.New("herald: invalid channel type") // ErrSendFailed is returned when notification delivery fails. ErrSendFailed = errors.New("herald: send failed") // ErrOptedOut is returned when the user has opted out of this notification type. ErrOptedOut = errors.New("herald: user opted out") // ErrStoreClosed is returned when a store operation is attempted after close. ErrStoreClosed = errors.New("herald: store is closed") // ErrMigrationFailed is returned when a database migration fails. ErrMigrationFailed = errors.New("herald: migration failed") // ErrDuplicateSlug is returned when a template slug+channel+app combination already exists. ErrDuplicateSlug = store.ErrDuplicateSlug // ErrDuplicateLocale is returned when a template version for the same locale already exists. ErrDuplicateLocale = store.ErrDuplicateLocale // ErrInvalidProvider wraps a provider that failed validation. The message // names the problem and never a credential value. ErrInvalidProvider = errors.New("herald: invalid provider") // ErrNoCredentialKey is returned by operations that need a credential key // when none is configured. ErrNoCredentialKey = errors.New("herald: no credential key is configured") // that isn't configured. The message names the key ID. ErrCredentialKeyUnavailable = credential.ErrKeyUnavailable // ErrNoDeviceStore is returned by the device methods when neither the main // store nor WithDeviceStore provides a device store. ErrNoDeviceStore = errors.New("herald: no device store is configured") // ErrDeviceNotFound is returned when a device does not exist for the app. ErrDeviceNotFound = device.ErrNotFound )
Sentinel errors returned by Herald operations.
Functions ¶
This section is empty.
Types ¶
type ChannelType ¶
type ChannelType string
ChannelType represents a notification delivery channel.
const ( ChannelEmail ChannelType = "email" ChannelSMS ChannelType = "sms" ChannelPush ChannelType = "push" ChannelInApp ChannelType = "inapp" ChannelWebhook ChannelType = "webhook" ChannelChat ChannelType = "chat" )
Supported notification channels.
func ValidChannels ¶
func ValidChannels() []ChannelType
ValidChannels returns all valid channel types.
func (ChannelType) IsValid ¶
func (c ChannelType) IsValid() bool
IsValid reports whether c is a recognized channel type.
func (ChannelType) String ¶
func (c ChannelType) String() string
String returns the string representation.
type Config ¶
type Config struct {
// DefaultLocale is the default locale for template rendering (default: "en").
DefaultLocale string
// MaxBatchSize is the maximum number of notifications per batch send.
MaxBatchSize int
// TruncateBodyAt is the maximum message body length stored in the delivery log.
// Bodies longer than this are truncated. 0 means no truncation.
TruncateBodyAt int
}
Config holds the configuration for a Herald instance.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a Config with sensible defaults.
type CredentialState ¶
type CredentialState struct {
Key string `json:"key"`
Protection string `json:"protection"`
KeyID string `json:"key_id,omitempty"`
}
CredentialState is how one stored credential is protected. It never carries the value.
type DeviceResult ¶
type DeviceResult struct {
DeviceID string `json:"device_id"`
Status message.Status `json:"status"`
Error string `json:"error,omitempty"`
}
DeviceResult is the outcome for one device of a push fan-out.
type EncryptReport ¶
type EncryptReport struct {
Providers int `json:"providers"`
ValuesEncrypted int `json:"values_encrypted"`
AlreadyEncrypted int `json:"already_encrypted"`
}
EncryptReport says what EncryptStoredCredentials changed.
type Herald ¶
type Herald struct {
// contains filtered or unexported fields
}
Herald is the root notification delivery engine.
func (*Herald) Audit ¶
func (h *Herald) Audit(ctx context.Context, severity, outcome, action, resource, resourceID, actorID, tenant, category string, metadata map[string]string)
Audit records an audit event if a Chronicle backend is configured. This is used by both core Herald operations and the API layer.
func (*Herald) CheckRouting ¶
CheckRouting refuses a routing rule that names a provider outside cfg's app or on another channel. The error reads the same whether the ID is malformed, missing or another app's, so it never confirms that an ID exists elsewhere. Every writer of scoped configs calls it before saving.
func (*Herald) CreateProvider ¶
CreateProvider validates p, encrypts its credentials when a key is configured, and stores it. p is left holding what was stored.
func (*Herald) CredentialKeyID ¶
CredentialKeyID is the ID of the key new credentials are encrypted under, or "" when none is configured and credentials are stored in plaintext.
func (*Herald) CredentialStatus ¶
func (h *Herald) CredentialStatus(p *provider.Provider) []CredentialState
CredentialStatus reports how each stored credential of p is protected.
func (*Herald) DeleteDevice ¶
DeleteDevice removes one device, or returns ErrDeviceNotFound.
func (*Herald) DeleteProvider ¶
DeleteProvider removes a provider of appID.
func (*Herald) DeleteUserDevices ¶
DeleteUserDevices removes every device of a user. A user with none is not an error.
func (*Herald) EncryptStoredCredentials ¶
EncryptStoredCredentials encrypts every plaintext credential of every provider in appID. It is explicit and idempotent: nothing re-encrypts on read, and a second run changes nothing.
func (*Herald) GetProvider ¶
func (h *Herald) GetProvider(ctx context.Context, appID string, providerID id.ProviderID) (*provider.Provider, error)
GetProvider returns a provider of appID, with credentials as stored.
func (*Herald) ListDevices ¶
ListDevices returns a user's devices, oldest first.
func (*Herald) Notify ¶
func (h *Herald) Notify(ctx context.Context, req *NotifyRequest) ([]*SendResult, error)
Notify sends a notification across multiple channels using a template, under a "notify channels" span with one "send notification" span per channel.
func (*Herald) PreviewSend ¶
func (h *Herald) PreviewSend(ctx context.Context, req *SendRequest) (*SendPreview, error)
PreviewSend resolves the provider and sender for req exactly as Send would, through the same code, and sends nothing. It returns ErrNoProviderConfigured (wrapped) when nothing handles the channel, and Send's own errors for a chosen provider that's missing, in another app or on another channel, and ErrDriverNotFound when the provider's driver isn't registered.
func (*Herald) ResetDefaultTemplates ¶
ResetDefaultTemplates deletes all system templates for an app and re-seeds the factory defaults. Custom (non-system) templates are preserved.
func (*Herald) ResolveProvider ¶
func (h *Herald) ResolveProvider(ctx context.Context, appID, orgID, userID, channel string) (*scope.ResolveResult, error)
ResolveProvider reports which provider would send on channel for this app, org and user, and why. It returns nil, nil when nothing handles it.
func (*Herald) SaveDevice ¶
SaveDevice registers a device, or refreshes it when the same app, user and public key (or address, for a device without a key) is already registered. It sets Channel to "push" when empty and stamps UpdatedAt and LastSeenAt. ID and CreatedAt are written back into d.
func (*Herald) SeedConfiguredProviders ¶
SeedConfiguredProviders persists providers declared in configuration into the store, seed-if-absent: a provider is created only when no provider of the same name exists for its app. Existing records (including dashboard edits) are left untouched. A missing driver or failed Validate is logged as a warning, never an error.
func (*Herald) SeedDefaultProviders ¶
SeedDefaultProviders creates default providers for built-in zero-config drivers (e.g. inapp). This ensures channels work out of the box without requiring manual provider setup via the dashboard. Providers that need credentials (email, sms, push) are not seeded.
func (*Herald) SeedDefaultTemplates ¶
SeedDefaultTemplates creates default notification templates for an app.
func (*Herald) Send ¶
func (h *Herald) Send(ctx context.Context, req *SendRequest) (*SendResult, error)
Send delivers a notification on a single channel.
It records a "send notification" span. The span names the channel, template, driver and provider and counts recipients, sent and failed deliveries; it never carries a recipient, the data, the content or the metadata. Each driver's own vendor call is a client span under it.
func (*Herald) Start ¶
Start is a no-op for Herald (no background workers needed currently). This exists for interface compatibility with Forge extensions.
func (*Herald) UpdateProvider ¶
func (h *Herald) UpdateProvider(ctx context.Context, appID string, providerID id.ProviderID, u ProviderUpdate) (*provider.Provider, error)
UpdateProvider applies u to a provider of appID. Credentials it sets are encrypted; credentials it doesn't touch keep their stored form exactly. It refuses, and writes nothing, when an existing credential can't be decrypted for validation, and when it moves base_url or host to a new server without setting the provider's secret credentials again (see connectionTargets).
func (*Herald) ValidateProvider ¶
ValidateProvider checks p the way Send will use it: the driver exists and handles p's channel, no secret sits in settings, no connection target (base_url, host) or schema setting sits in credentials, and the driver's own Validate accepts credentials and settings merged as Send merges them. Encrypted credentials are decrypted first. Errors never contain a value.
type NotifyRequest ¶
type NotifyRequest struct {
AppID string `json:"app_id"`
EnvID string `json:"env_id,omitempty"`
OrgID string `json:"org_id,omitempty"`
UserID string `json:"user_id,omitempty"`
Template string `json:"template"`
Locale string `json:"locale,omitempty"`
To []string `json:"to"`
Data map[string]any `json:"data,omitempty"`
Channels []string `json:"channels"`
Async bool `json:"async,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
NotifyRequest sends a notification across multiple channels using a template.
type Option ¶
Option configures a Herald instance.
func WithChronicle ¶
WithChronicle sets the audit trail backend for the Herald instance.
func WithCredentialKey ¶
WithCredentialKey sets the AES-256 key that encrypts provider credentials written from now on. keyID is stored beside every value it encrypts.
func WithDefaultLocale ¶
WithDefaultLocale sets the default locale for template rendering.
func WithDeviceStore ¶
WithDeviceStore sets the store the device methods use. Give it when the main store is wrapped in a type that hides its device methods. Without it, New uses the main store when that implements device.Store.
func WithDriver ¶
WithDriver registers a notification driver with the Herald instance.
func WithLogger ¶
WithLogger sets the structured logger for the Herald instance.
func WithMaxBatchSize ¶
WithMaxBatchSize sets the maximum number of notifications per batch send.
func WithMetrics ¶
WithMetrics records herald's metrics on m: deliveries by channel, driver and result, delivery latency, suppressed recipients, rejected sends and failed message log writes. In a Forge app pass app.Metrics() (the extension does this for you) and the series scrape as forge_herald_*. A nil m records nothing, which is also the default.
func WithPreviousCredentialKey ¶
WithPreviousCredentialKey adds a key that only decrypts, so values written under a rotated-out key keep working. Give it once per old key.
type ProviderUpdate ¶
type ProviderUpdate struct {
Name *string
Priority *int
Enabled *bool
SetCredentials map[string]string
RemoveCredentials []string
SetSettings map[string]string
RemoveSettings []string
}
ProviderUpdate changes only what it names. Nil pointers and absent keys leave the stored value alone, so a caller can replace one secret without ever reading the others. A provider's channel and driver can't change.
type SendPreview ¶
SendPreview is what a send would use, without sending: the provider, why it was picked, and the sender it would go out as.
type SendRequest ¶
type SendRequest struct {
AppID string `json:"app_id"`
EnvID string `json:"env_id,omitempty"`
OrgID string `json:"org_id,omitempty"`
UserID string `json:"user_id,omitempty"`
Channel string `json:"channel"`
Template string `json:"template,omitempty"`
Locale string `json:"locale,omitempty"`
To []string `json:"to"`
Data map[string]any `json:"data,omitempty"`
Subject string `json:"subject,omitempty"`
Body string `json:"body,omitempty"`
Async bool `json:"async,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
// ProviderID sends through this provider instead of resolving one. It must belong to AppID and handle Channel; a disabled provider is allowed, because naming it is the explicit act.
ProviderID string `json:"provider_id,omitempty"`
// Push carries per-message push options, copied to the driver as is.
Push *driver.PushOptions `json:"-"`
// DeviceFilter, when set, decides per device whether a push fan-out
// (Channel "push", UserID set, To empty) delivers to it. Returning false
// gives that device the status filtered.
DeviceFilter func(*device.Device) bool `json:"-"`
}
SendRequest describes a single notification to send.
type SendResult ¶
type SendResult struct {
MessageID id.MessageID `json:"message_id"`
Status message.Status `json:"status"`
// ProviderID is always Herald's provider ID.
ProviderID string `json:"provider_id,omitempty"`
// ProviderMessageID is the vendor's ID for the message, when it gave one.
ProviderMessageID string `json:"provider_message_id,omitempty"`
Error string `json:"error,omitempty"`
// Logged is false when the message log couldn't be written. The send
// still happened; it just won't appear in the log.
Logged bool `json:"logged"`
// Devices is the per-device outcome of a push fan-out. Nil otherwise.
Devices []DeviceResult `json:"devices,omitempty"`
}
SendResult contains the outcome of a send operation.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package api provides the Forge-integrated HTTP API for Herald.
|
Package api provides the Forge-integrated HTTP API for Herald. |
|
Package bridge defines local interfaces for optional forgery extension integration.
|
Package bridge defines local interfaces for optional forgery extension integration. |
|
chronicleadapter
Package chronicleadapter bridges Herald audit events to the Chronicle extension.
|
Package chronicleadapter bridges Herald audit events to the Chronicle extension. |
|
Package credential encrypts provider credential values at rest.
|
Package credential encrypts provider credential values at rest. |
|
Package device defines the registry of devices a user receives push notifications on, and the store every backend implements for it.
|
Package device defines the registry of devices a user receives push notifications on, and the store every backend implements for it. |
|
devicetest
Package devicetest is the conformance suite every device.Store backend runs.
|
Package devicetest is the conformance suite every device.Store backend runs. |
|
Package driver defines the notification driver interface and registry.
|
Package driver defines the notification driver interface and registry. |
|
drivertest
Package drivertest provides shared HTTP mock-server helpers for driver unit tests.
|
Package drivertest provides shared HTTP mock-server helpers for driver unit tests. |
|
email
Package email provides email notification drivers.
|
Package email provides email notification drivers. |
|
inapp
Package inapp provides the in-app notification driver.
|
Package inapp provides the in-app notification driver. |
|
push
Package push provides push notification drivers.
|
Package push provides push notification drivers. |
|
sms
Package sms provides SMS notification drivers.
|
Package sms provides SMS notification drivers. |
|
drivers
|
|
|
apns
module
|
|
|
slack
module
|
|
|
webhook
module
|
|
|
contract
Package contract wires Herald into the Forge dashboard's contract path.
|
Package contract wires Herald into the Forge dashboard's contract path. |
|
Package id defines TypeID-based identity types for all Herald entities.
|
Package id defines TypeID-based identity types for all Herald entities. |
|
Package inbox defines the in-app notification entity for the inbox channel.
|
Package inbox defines the in-app notification entity for the inbox channel. |
|
internal
|
|
|
storeerr
Package storeerr holds the not-found sentinels that packages below store need to match.
|
Package storeerr holds the not-found sentinels that packages below store need to match. |
|
telemetry
Package telemetry holds what herald's spans and metrics share: the tracer, the attribute keys, the error classes and a small label vector over a go-utils metrics sink.
|
Package telemetry holds what herald's spans and metrics share: the tracer, the attribute keys, the error classes and a small label vector over a go-utils metrics sink. |
|
telemetry/metricsvec
Package metricsvec declares herald's labelled counters and histograms over a go-utils metrics sink.
|
Package metricsvec declares herald's labelled counters and histograms over a go-utils metrics sink. |
|
telemetry/telemetrytest
Package telemetrytest records what herald's telemetry emits, for tests: a span recorder installed as the global tracer provider, and a go-utils metrics sink that remembers every series registered on it.
|
Package telemetrytest records what herald's telemetry emits, for tests: a span recorder installed as the global tracer provider, and a go-utils metrics sink that remembers every series registered on it. |
|
Package message defines the notification delivery log entity.
|
Package message defines the notification delivery log entity. |
|
Package preference defines user notification preference entities.
|
Package preference defines user notification preference entities. |
|
Package provider defines the notification provider entity and its store interface.
|
Package provider defines the notification provider entity and its store interface. |
|
Package pushgateway is an HTTP gateway that registers devices and relays sealed pushes to FCM and APNs without ever seeing the notification content.
|
Package pushgateway is an HTTP gateway that registers devices and relays sealed pushes to FCM and APNs without ever seeing the notification content. |
|
Package scope defines scoped provider configuration for app/org/user overrides.
|
Package scope defines scoped provider configuration for app/org/user overrides. |
|
Package store defines the composite Store interface for all Herald persistence.
|
Package store defines the composite Store interface for all Herald persistence. |
|
memory
Package memory provides an in-memory Store implementation for testing.
|
Package memory provides an in-memory Store implementation for testing. |
|
mongo
Package mongo provides a MongoDB Store implementation using Grove ORM.
|
Package mongo provides a MongoDB Store implementation using Grove ORM. |
|
postgres
Package postgres provides a PostgreSQL Store implementation using Grove ORM.
|
Package postgres provides a PostgreSQL Store implementation using Grove ORM. |
|
sqlite
Package sqlite provides a SQLite Store implementation using Grove ORM.
|
Package sqlite provides a SQLite Store implementation using Grove ORM. |
|
storetest
Package storetest is the conformance suite every herald store backend runs.
|
Package storetest is the conformance suite every herald store backend runs. |
|
Package template defines the notification template entity with i18n support.
|
Package template defines the notification template entity with i18n support. |