Vault

Go Package Reference

Complete Go package index for Herald.

All Herald packages are importable from the module github.com/xraph/herald. This page is a comprehensive index of every package, its key types, and its primary functions.

Package Index

PackageImport PathDescription
heraldgithub.com/xraph/heraldRoot package with engine, send/notify, config, and sentinel errors
idgithub.com/xraph/herald/idTypeID-based entity identifiers
providergithub.com/xraph/herald/providerNotification provider entity and store interface
templategithub.com/xraph/herald/templateTemplate definitions, versions, rendering
messagegithub.com/xraph/herald/messageDelivery log entity and store interface
inboxgithub.com/xraph/herald/inboxIn-app notification entity and store interface
preferencegithub.com/xraph/herald/preferenceUser notification preferences
scopegithub.com/xraph/herald/scopeScoped config, resolver, and scope types
drivergithub.com/xraph/herald/driverDriver interface, registry, and outbound message types
driver/emailgithub.com/xraph/herald/driver/emailSMTP and Resend email drivers
driver/smsgithub.com/xraph/herald/driver/smsTwilio SMS driver
driver/pushgithub.com/xraph/herald/driver/pushFCM push notification driver
driver/inappgithub.com/xraph/herald/driver/inappIn-app notification driver
storegithub.com/xraph/herald/storeComposite store interface definition
store/memorygithub.com/xraph/herald/store/memoryIn-memory store for development/testing
store/postgresgithub.com/xraph/herald/store/postgresPostgreSQL backend via Grove ORM
store/sqlitegithub.com/xraph/herald/store/sqliteSQLite backend via Grove ORM
store/mongogithub.com/xraph/herald/store/mongoMongoDB backend via Grove ORM
apigithub.com/xraph/herald/apiForge-integrated HTTP API handler
extensiongithub.com/xraph/herald/extensionForge extension with lifecycle integration

Root Package: github.com/xraph/herald

Herald Engine

type Herald struct { /* unexported fields */ }

func New(opts ...Option) (*Herald, error)

func (h *Herald) Send(ctx context.Context, req *SendRequest) (*SendResult, error)
func (h *Herald) Notify(ctx context.Context, req *NotifyRequest) ([]*SendResult, error)
func (h *Herald) SeedDefaultTemplates(ctx context.Context, appID string) error
func (h *Herald) Start(ctx context.Context)
func (h *Herald) Stop(ctx context.Context)
func (h *Herald) Health(ctx context.Context) error
func (h *Herald) Store() store.Store
func (h *Herald) Drivers() *driver.Registry
func (h *Herald) Config() Config

Options

type Option func(*Herald) error

func WithStore(s store.Store) Option
func WithDriver(d driver.Driver) Option
func WithLogger(logger *slog.Logger) Option
func WithDefaultLocale(locale string) Option
func WithMaxBatchSize(n int) Option

Config

type Config struct {
    DefaultLocale  string // default: "en"
    MaxBatchSize   int    // default: 100
    TruncateBodyAt int    // default: 1000
}

func DefaultConfig() Config

Request and Result Types

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"`
}

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"`
}

type SendResult struct {
    MessageID  id.MessageID   `json:"message_id"`
    Status     message.Status `json:"status"`
    ProviderID string         `json:"provider_id,omitempty"`
    Error      string         `json:"error,omitempty"`
}

Channel Types

type ChannelType string

const (
    ChannelEmail ChannelType = "email"
    ChannelSMS   ChannelType = "sms"
    ChannelPush  ChannelType = "push"
    ChannelInApp ChannelType = "inapp"
)

Sentinel Errors

var (
    ErrNoStore                 = errors.New("herald: store is required")
    ErrProviderNotFound        = errors.New("herald: provider not found")
    ErrProviderDisabled        = errors.New("herald: provider is disabled")
    ErrNoProviderConfigured    = errors.New("herald: no provider configured for channel")
    ErrDriverNotFound          = errors.New("herald: driver not found")
    ErrTemplateNotFound        = errors.New("herald: template not found")
    ErrTemplateDisabled        = errors.New("herald: template is disabled")
    ErrNoVersionForLocale      = errors.New("herald: no template version for locale")
    ErrTemplateRenderFailed    = errors.New("herald: template rendering failed")
    ErrMissingRequiredVariable = errors.New("herald: missing required template variable")
    ErrMessageNotFound         = errors.New("herald: message not found")
    ErrInboxNotFound           = errors.New("herald: in-app notification not found")
    ErrPreferenceNotFound      = errors.New("herald: user preference not found")
    ErrScopedConfigNotFound    = errors.New("herald: scoped config not found")
    ErrInvalidChannel          = errors.New("herald: invalid channel type")
    ErrSendFailed              = errors.New("herald: send failed")
    ErrOptedOut                = errors.New("herald: user opted out")
    ErrStoreClosed             = errors.New("herald: store is closed")
    ErrMigrationFailed         = errors.New("herald: migration failed")
    ErrDuplicateSlug           = errors.New("herald: duplicate template slug")
    ErrDuplicateLocale         = errors.New("herald: duplicate locale version")
)

id

TypeID-based identity types for all Herald entities. IDs are K-sortable (UUIDv7-based), globally unique, and URL-safe in the format prefix_suffix.

type Prefix string

const (
    PrefixProvider        Prefix = "hpvd"
    PrefixTemplate        Prefix = "htpl"
    PrefixTemplateVersion Prefix = "htpv"
    PrefixMessage         Prefix = "hmsg"
    PrefixInbox           Prefix = "hinb"
    PrefixPreference      Prefix = "hprf"
    PrefixScopedConfig    Prefix = "hscf"
)

type ID struct { /* wraps typeid.TypeID */ }

func New(prefix Prefix) ID
func Parse(s string) (ID, error)
func ParseWithPrefix(s string, expected Prefix) (ID, error)
func MustParse(s string) ID

func (i ID) String() string
func (i ID) Prefix() Prefix
func (i ID) IsNil() bool
// Implements: TextMarshaler, TextUnmarshaler, driver.Valuer,
//             sql.Scanner, json.Marshaler, json.Unmarshaler,
//             bson.ValueMarshaler, bson.ValueUnmarshaler

Type Aliases and Convenience Constructors

type ProviderID        = ID
type TemplateID        = ID
type TemplateVersionID = ID
type MessageID         = ID
type InboxID           = ID
type PreferenceID      = ID
type ScopedConfigID    = ID

func NewProviderID() ID
func NewTemplateID() ID
func NewTemplateVersionID() ID
func NewMessageID() ID
func NewInboxID() ID
func NewPreferenceID() ID
func NewScopedConfigID() ID

Convenience Parsers (type-safe)

func ParseProviderID(s string) (ID, error)
func ParseTemplateID(s string) (ID, error)
func ParseTemplateVersionID(s string) (ID, error)
func ParseMessageID(s string) (ID, error)
func ParseInboxID(s string) (ID, error)
func ParsePreferenceID(s string) (ID, error)
func ParseScopedConfigID(s string) (ID, error)
func ParseAny(s string) (ID, error)

provider

type Provider struct {
    ID          id.ProviderID     `json:"id"`
    AppID       string            `json:"app_id"`
    Name        string            `json:"name"`
    Channel     string            `json:"channel"`
    Driver      string            `json:"driver"`
    Credentials map[string]string `json:"credentials"`
    Settings    map[string]string `json:"settings"`
    Priority    int               `json:"priority"`
    Enabled     bool              `json:"enabled"`
    CreatedAt   time.Time         `json:"created_at"`
    UpdatedAt   time.Time         `json:"updated_at"`
}

type Store interface {
    CreateProvider(ctx context.Context, p *Provider) error
    GetProvider(ctx context.Context, id id.ProviderID) (*Provider, error)
    UpdateProvider(ctx context.Context, p *Provider) error
    DeleteProvider(ctx context.Context, id id.ProviderID) error
    ListProviders(ctx context.Context, appID, channel string) ([]*Provider, error)
    ListAllProviders(ctx context.Context, appID string) ([]*Provider, error)
}

template

type Template struct {
    ID        id.TemplateID `json:"id"`
    AppID     string        `json:"app_id"`
    Slug      string        `json:"slug"`
    Name      string        `json:"name"`
    Channel   string        `json:"channel"`
    Category  string        `json:"category"`
    Variables []Variable    `json:"variables,omitempty"`
    IsSystem  bool          `json:"is_system"`
    Enabled   bool          `json:"enabled"`
    Versions  []Version     `json:"versions,omitempty"`
    CreatedAt time.Time     `json:"created_at"`
    UpdatedAt time.Time     `json:"updated_at"`
}

type Version struct {
    ID         id.TemplateVersionID `json:"id"`
    TemplateID id.TemplateID        `json:"template_id"`
    Locale     string               `json:"locale"`
    Subject    string               `json:"subject"`
    HTML       string               `json:"html"`
    Text       string               `json:"text"`
    Title      string               `json:"title"`
    Active     bool                 `json:"active"`
    CreatedAt  time.Time            `json:"created_at"`
    UpdatedAt  time.Time            `json:"updated_at"`
}

type Variable struct {
    Name     string `json:"name"`
    Type     string `json:"type"`
    Required bool   `json:"required"`
    Default  string `json:"default,omitempty"`
}

type RenderedContent struct {
    Subject string
    HTML    string
    Text    string
    Title   string
}

type Store interface {
    CreateTemplate(ctx context.Context, t *Template) error
    GetTemplate(ctx context.Context, id id.TemplateID) (*Template, error)
    GetTemplateBySlug(ctx context.Context, appID, slug, channel string) (*Template, error)
    UpdateTemplate(ctx context.Context, t *Template) error
    DeleteTemplate(ctx context.Context, id id.TemplateID) error
    ListTemplates(ctx context.Context, appID string) ([]*Template, error)
    ListTemplatesByChannel(ctx context.Context, appID, channel string) ([]*Template, error)
    CreateVersion(ctx context.Context, v *Version) error
    GetVersion(ctx context.Context, id id.TemplateVersionID) (*Version, error)
    UpdateVersion(ctx context.Context, v *Version) error
    DeleteVersion(ctx context.Context, id id.TemplateVersionID) error
    ListVersions(ctx context.Context, templateID id.TemplateID) ([]*Version, error)
}

message

type Status string

const (
    StatusQueued    Status = "queued"
    StatusSending   Status = "sending"
    StatusSent      Status = "sent"
    StatusFailed    Status = "failed"
    StatusBounced   Status = "bounced"
    StatusDelivered Status = "delivered"
)

type Message struct {
    ID          id.MessageID      `json:"id"`
    AppID       string            `json:"app_id"`
    EnvID       string            `json:"env_id"`
    TemplateID  string            `json:"template_id"`
    ProviderID  string            `json:"provider_id"`
    Channel     string            `json:"channel"`
    Recipient   string            `json:"recipient"`
    Subject     string            `json:"subject"`
    Body        string            `json:"body"`
    Status      Status            `json:"status"`
    Error       string            `json:"error"`
    Metadata    map[string]string `json:"metadata"`
    Async       bool              `json:"async"`
    Attempts    int               `json:"attempts"`
    SentAt      *time.Time        `json:"sent_at,omitempty"`
    DeliveredAt *time.Time        `json:"delivered_at,omitempty"`
    CreatedAt   time.Time         `json:"created_at"`
}

type ListOptions struct {
    Channel string
    Status  Status
    Offset  int
    Limit   int
}

type Store interface {
    CreateMessage(ctx context.Context, m *Message) error
    GetMessage(ctx context.Context, id id.MessageID) (*Message, error)
    UpdateMessageStatus(ctx context.Context, id id.MessageID, status Status, errMsg string) error
    ListMessages(ctx context.Context, appID string, opts ListOptions) ([]*Message, error)
}

inbox

type Notification struct {
    ID        id.InboxID        `json:"id"`
    AppID     string            `json:"app_id"`
    EnvID     string            `json:"env_id"`
    UserID    string            `json:"user_id"`
    Type      string            `json:"type"`
    Title     string            `json:"title"`
    Body      string            `json:"body"`
    ActionURL string            `json:"action_url"`
    ImageURL  string            `json:"image_url"`
    Read      bool              `json:"read"`
    ReadAt    *time.Time        `json:"read_at,omitempty"`
    Metadata  map[string]string `json:"metadata"`
    ExpiresAt *time.Time        `json:"expires_at,omitempty"`
    CreatedAt time.Time         `json:"created_at"`
}

type Store interface {
    CreateNotification(ctx context.Context, n *Notification) error
    GetNotification(ctx context.Context, id id.InboxID) (*Notification, error)
    DeleteNotification(ctx context.Context, id id.InboxID) error
    MarkRead(ctx context.Context, id id.InboxID) error
    MarkAllRead(ctx context.Context, appID, userID string) error
    UnreadCount(ctx context.Context, appID, userID string) (int, error)
    ListNotifications(ctx context.Context, appID, userID string, limit, offset int) ([]*Notification, error)
}

preference

type ChannelPreference struct {
    Email *bool `json:"email,omitempty"`
    SMS   *bool `json:"sms,omitempty"`
    Push  *bool `json:"push,omitempty"`
    InApp *bool `json:"inapp,omitempty"`
}

type Preference struct {
    ID        id.PreferenceID                `json:"id"`
    AppID     string                         `json:"app_id"`
    UserID    string                         `json:"user_id"`
    Overrides map[string]ChannelPreference   `json:"overrides"`
    CreatedAt time.Time                      `json:"created_at"`
    UpdatedAt time.Time                      `json:"updated_at"`
}

func (p *Preference) IsOptedOut(templateSlug, channel string) bool

type Store interface {
    GetPreference(ctx context.Context, appID, userID string) (*Preference, error)
    SetPreference(ctx context.Context, p *Preference) error
    DeletePreference(ctx context.Context, appID, userID string) error
}

scope

type ScopeType string

const (
    ScopeApp  ScopeType = "app"
    ScopeOrg  ScopeType = "org"
    ScopeUser ScopeType = "user"
)

type Config struct {
    ID              id.ScopedConfigID `json:"id"`
    AppID           string            `json:"app_id"`
    Scope           ScopeType         `json:"scope"`
    ScopeID         string            `json:"scope_id"`
    EmailProviderID string            `json:"email_provider_id"`
    SMSProviderID   string            `json:"sms_provider_id"`
    PushProviderID  string            `json:"push_provider_id"`
    FromEmail       string            `json:"from_email"`
    FromName        string            `json:"from_name"`
    FromPhone       string            `json:"from_phone"`
    DefaultLocale   string            `json:"default_locale"`
    CreatedAt       time.Time         `json:"created_at"`
    UpdatedAt       time.Time         `json:"updated_at"`
}

func (c *Config) ProviderIDFor(channel string) string

type Store interface {
    GetScopedConfig(ctx context.Context, appID string, scopeType ScopeType, scopeID string) (*Config, error)
    SetScopedConfig(ctx context.Context, cfg *Config) error
    DeleteScopedConfig(ctx context.Context, id id.ScopedConfigID) error
    ListScopedConfigs(ctx context.Context, appID string) ([]*Config, error)
}

Resolver

type Resolver struct { /* unexported fields */ }

func NewResolver(scopeStore Store, providerStore provider.Store, logger *slog.Logger) *Resolver

type ResolveResult struct {
    Provider *provider.Provider
    Config   *Config
}

func (r *Resolver) ResolveProvider(ctx context.Context, appID, orgID, userID, channel string) (*ResolveResult, error)

Resolution order: user -> org -> app -> first enabled provider by priority.

driver

type Driver interface {
    Name() string
    Channel() string
    Send(ctx context.Context, msg *OutboundMessage) (*DeliveryResult, error)
    Validate(credentials map[string]string) error
}

type OutboundMessage struct {
    To       string
    From     string
    FromName string
    Subject  string
    HTML     string
    Text     string
    Title    string
    Data     map[string]string
}

type DeliveryResult struct {
    ProviderMessageID string
    Status            string
}

type Registry struct { /* unexported fields */ }

func NewRegistry() *Registry
func (r *Registry) Register(d Driver)
func (r *Registry) Get(name string) (Driver, error)
func (r *Registry) List() []Driver

Built-in Drivers

DriverPackageNameChannel
SMTPDriverherald/driver/email"smtp""email"
ResendDriverherald/driver/email"resend""email"
TwilioDriverherald/driver/sms"twilio""sms"
FCMDriverherald/driver/push"fcm""push"
Driverherald/driver/inapp"inapp""inapp"

store

type Store interface {
    provider.Store    // 6 methods
    template.Store    // 12 methods
    message.Store     // 4 methods
    inbox.Store       // 7 methods
    preference.Store  // 3 methods
    scope.Store       // 4 methods

    Migrate(ctx context.Context) error
    Ping(ctx context.Context) error
    Close() error
}

Store Implementations

BackendConstructorDriver Dependency
Memorymemory.New() *StoreNone
PostgreSQLpostgres.New(db *grove.DB) *Storegrove/drivers/pgdriver
SQLitesqlite.New(db *grove.DB) *Storegrove/drivers/sqlitedriver
MongoDBmongo.New(db *grove.DB) *Storegrove/drivers/mongodriver

api

type ForgeAPI struct { /* unexported fields */ }

func NewForgeAPI(s store.Store, h *herald.Herald, logger forge.Logger) *ForgeAPI
func (a *ForgeAPI) RegisterRoutes(router forge.Router)

extension

type Extension struct { /* embeds forge.BaseExtension */ }

func New(opts ...ExtOption) *Extension
func (e *Extension) Register(fapp forge.App) error
func (e *Extension) Init(fapp forge.App) error
func (e *Extension) Start(ctx context.Context) error
func (e *Extension) Stop(ctx context.Context) error
func (e *Extension) Health(ctx context.Context) error
func (e *Extension) Herald() *herald.Herald
func (e *Extension) API() *api.ForgeAPI
func (e *Extension) RegisterRoutes(router forge.Router)
func (e *Extension) BasePath() string

type ExtOption func(*Extension)

func WithStore(s store.Store) ExtOption
func WithBasePath(path string) ExtOption
func WithConfig(cfg Config) ExtOption
func WithHeraldOption(opt herald.Option) ExtOption
func WithDriver(d driver.Driver) ExtOption
func WithDisableRoutes() ExtOption
func WithDisableMigrate() ExtOption
func WithRequireConfig(require bool) ExtOption
func WithGroveDatabase(name string) ExtOption

type Config struct {
    herald.Config
    BasePath       string `yaml:"base_path"`
    DisableRoutes  bool   `yaml:"disable_routes"`
    DisableMigrate bool   `yaml:"disable_migrate"`
    GroveDatabase  string `yaml:"grove_database"`
    RequireConfig  bool
}

func DefaultConfig() Config

On this page