Vault

Entities

Core data types used across Herald's notification subsystems.

Herald defines a set of entity types across its subsystem packages. Each entity uses an id.ID identifier with an entity-specific prefix and carries an AppID field for multi-tenant isolation.


Provider

Package: github.com/xraph/herald/provider ID prefix: hpvd_

A Provider represents a configured notification delivery backend. Each provider is scoped to an app, handles a single channel type, and is bound to a specific driver.

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,omitempty"`
    Settings    map[string]string `json:"settings,omitempty"`
    Priority    int               `json:"priority"`
    Enabled     bool              `json:"enabled"`
    CreatedAt   time.Time         `json:"created_at"`
    UpdatedAt   time.Time         `json:"updated_at"`
}
FieldDescription
ChannelThe notification channel: "email", "sms", "push", or "inapp".
DriverThe driver implementation name (e.g. "smtp", "resend", "twilio", "fcm", "inapp").
CredentialsDriver-specific credentials (API keys, SMTP passwords). Stored encrypted at rest.
SettingsDriver-specific settings (e.g. "from", "from_name", "region").
PriorityLower numbers indicate higher priority. When the scope resolver falls back to listing all providers for a channel, it picks the lowest priority enabled provider first.
EnabledWhen false, the provider is skipped during resolution.

Provider store interface

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

Supported channels and drivers

ChannelChannelType constantBuilt-in drivers
Emailherald.ChannelEmail ("email")smtp, resend
SMSherald.ChannelSMS ("sms")twilio
Pushherald.ChannelPush ("push")fcm
In-Appherald.ChannelInApp ("inapp")inapp

Template

Package: github.com/xraph/herald/template ID prefix: htpl_

A Template defines the content and structure of a notification. Templates are identified by a unique slug + channel combination within an app and support multiple locale-specific versions for internationalization.

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"`
    Versions  []Version     `json:"versions,omitempty"`
    IsSystem  bool          `json:"is_system"`
    Enabled   bool          `json:"enabled"`
    CreatedAt time.Time     `json:"created_at"`
    UpdatedAt time.Time     `json:"updated_at"`
}
FieldDescription
SlugURL-safe identifier (e.g. "auth.welcome", "order.shipped"). Unique per app + channel.
CategoryOrganizational category: "auth", "transactional", "marketing", or "system".
VariablesDeclared template variables with types, defaults, and required flags.
IsSystemSystem templates are seeded automatically and cannot be deleted via the API.
EnabledWhen false, sending with this template returns ErrTemplateDisabled.

Category constants

const (
    CategoryAuth          = "auth"
    CategoryTransactional = "transactional"
    CategoryMarketing     = "marketing"
    CategorySystem        = "system"
)

Variable

A Variable describes an expected placeholder in the template content:

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

When Required is true and the caller does not provide the variable in the Data map, rendering returns ErrMissingRequiredVariable.

Template Version

ID prefix: htpv_

A Version represents a locale-specific content variant of a template. Each version contains the actual rendered content (subject, HTML, text, title) for a single locale.

type Version struct {
    ID         id.TemplateVersionID `json:"id"`
    TemplateID id.TemplateID        `json:"template_id"`
    Locale     string               `json:"locale"`
    Subject    string               `json:"subject,omitempty"`
    HTML       string               `json:"html,omitempty"`
    Text       string               `json:"text,omitempty"`
    Title      string               `json:"title,omitempty"`
    Active     bool                 `json:"active"`
    CreatedAt  time.Time            `json:"created_at"`
    UpdatedAt  time.Time            `json:"updated_at"`
}
FieldDescription
LocaleBCP 47 locale tag (e.g. "en", "fr", "pt-BR"). Unique per template.
SubjectEmail subject line (email channel).
HTMLHTML body (email channel).
TextPlain-text body (email, SMS channels).
TitleNotification title (push, in-app channels).
ActiveWhen false, this version is skipped during locale resolution.

Template store interface

type Store interface {
    // Template CRUD
    CreateTemplate(ctx context.Context, t *Template) error
    GetTemplate(ctx context.Context, templateID id.TemplateID) (*Template, error)
    GetTemplateBySlug(ctx context.Context, appID string, slug string, channel string) (*Template, error)
    UpdateTemplate(ctx context.Context, t *Template) error
    DeleteTemplate(ctx context.Context, templateID id.TemplateID) error
    ListTemplates(ctx context.Context, appID string) ([]*Template, error)
    ListTemplatesByChannel(ctx context.Context, appID string, channel string) ([]*Template, error)

    // Version CRUD
    CreateVersion(ctx context.Context, v *Version) error
    GetVersion(ctx context.Context, versionID id.TemplateVersionID) (*Version, error)
    UpdateVersion(ctx context.Context, v *Version) error
    DeleteVersion(ctx context.Context, versionID id.TemplateVersionID) error
    ListVersions(ctx context.Context, templateID id.TemplateID) ([]*Version, error)
}

Message

Package: github.com/xraph/herald/message ID prefix: hmsg_

A Message represents a delivery log entry for a sent or queued notification. Every call to Herald.Send creates a message record that tracks the full lifecycle of the delivery attempt.

type Message struct {
    ID          id.MessageID      `json:"id"`
    AppID       string            `json:"app_id"`
    EnvID       string            `json:"env_id,omitempty"`
    TemplateID  string            `json:"template_id,omitempty"`
    ProviderID  string            `json:"provider_id,omitempty"`
    Channel     string            `json:"channel"`
    Recipient   string            `json:"recipient"`
    Subject     string            `json:"subject,omitempty"`
    Body        string            `json:"body,omitempty"`
    Status      Status            `json:"status"`
    Error       string            `json:"error,omitempty"`
    Metadata    map[string]string `json:"metadata,omitempty"`
    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"`
}
FieldDescription
EnvIDOptional environment identifier for environment-scoped delivery logs.
TemplateIDThe template slug used to render this message (empty for raw sends).
ProviderIDThe provider that handled delivery.
RecipientThe destination address (email, phone number, device token, user ID).
BodyTruncated to Config.TruncateBodyAt characters (default 4096).
StatusCurrent delivery status (see lifecycle below).
ErrorError message if delivery failed.
AsyncWhether the send was dispatched asynchronously.
AttemptsNumber of delivery attempts.

Status lifecycle

Messages progress through a defined status lifecycle:

queued --> sending --> sent --> delivered
                  \-> failed
                  \-> bounced
const (
    StatusQueued    Status = "queued"
    StatusSending   Status = "sending"
    StatusSent      Status = "sent"
    StatusFailed    Status = "failed"
    StatusBounced   Status = "bounced"
    StatusDelivered Status = "delivered"
)
StatusDescription
queuedMessage created but not yet dispatched to the driver.
sendingDriver send in progress.
sentDriver confirmed acceptance (does not guarantee inbox delivery).
failedDriver returned an error. The Error field contains details.
bouncedDelivery bounced (hard bounce from the receiving server).
deliveredConfirmed delivery to the recipient (when provider supports delivery tracking).

ListOptions

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

Message store interface

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

Notification (Inbox)

Package: github.com/xraph/herald/inbox ID prefix: hinb_

A Notification is an in-app inbox item delivered to a specific user. When a message is sent on the "inapp" channel, Herald automatically creates both a Message (delivery log) and a Notification (user-facing inbox item).

type Notification struct {
    ID        id.InboxID        `json:"id"`
    AppID     string            `json:"app_id"`
    EnvID     string            `json:"env_id,omitempty"`
    UserID    string            `json:"user_id"`
    Type      string            `json:"type"`
    Title     string            `json:"title"`
    Body      string            `json:"body,omitempty"`
    ActionURL string            `json:"action_url,omitempty"`
    ImageURL  string            `json:"image_url,omitempty"`
    Read      bool              `json:"read"`
    ReadAt    *time.Time        `json:"read_at,omitempty"`
    Metadata  map[string]string `json:"metadata,omitempty"`
    ExpiresAt *time.Time        `json:"expires_at,omitempty"`
    CreatedAt time.Time         `json:"created_at"`
}
FieldDescription
UserIDThe user this notification belongs to.
TypeNotification type slug (matches the template slug used to send it).
ActionURLOptional deep link or URL the user can navigate to.
ImageURLOptional image or avatar URL for rich inbox display.
ReadWhether the user has read this notification.
ReadAtTimestamp of when the notification was marked as read.
ExpiresAtOptional expiration time after which the notification is hidden.

Inbox store interface

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

Preference

Package: github.com/xraph/herald/preference ID prefix: hprf_

A Preference represents a user's notification opt-in/opt-out settings for an app. Preferences are keyed by notification type slug and allow per-channel granularity.

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

The Overrides map is keyed by notification type slug (e.g. "auth.welcome", "order.shipped"). Each value is a ChannelPreference that controls per-channel opt-in/opt-out:

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

A nil pointer means "use default" (opted in). An explicit false means the user has opted out of that channel for that notification type.

Checking opt-out status

The IsOptedOut method checks whether a user has explicitly opted out:

func (p *Preference) IsOptedOut(notifType string, channel string) bool
pref, _ := store.GetPreference(ctx, "myapp", "user-42")

if pref.IsOptedOut("marketing.newsletter", "email") {
    // User opted out of marketing emails -- skip delivery.
}

Herald checks preferences automatically during Send. If a user has opted out, the send is silently skipped and a result with "user opted out" is returned.

Preference store interface

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

Scoped Config

Package: github.com/xraph/herald/scope ID prefix: hscf_

A Config (scoped configuration) defines per-scope provider and sender overrides. Scoped configs allow different parts of your application (app-wide, per-organization, per-user) to use different providers and sender addresses.

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,omitempty"`
    SMSProviderID   string            `json:"sms_provider_id,omitempty"`
    PushProviderID  string            `json:"push_provider_id,omitempty"`
    FromEmail       string            `json:"from_email,omitempty"`
    FromName        string            `json:"from_name,omitempty"`
    FromPhone       string            `json:"from_phone,omitempty"`
    DefaultLocale   string            `json:"default_locale,omitempty"`
    CreatedAt       time.Time         `json:"created_at"`
    UpdatedAt       time.Time         `json:"updated_at"`
}

Scope types

const (
    ScopeApp  ScopeType = "app"
    ScopeOrg  ScopeType = "org"
    ScopeUser ScopeType = "user"
)
FieldDescription
ScopeThe level at which this config applies: "app", "org", or "user".
ScopeIDThe identifier for this scope (app ID, org ID, or user ID).
EmailProviderIDOverride provider for the email channel.
SMSProviderIDOverride provider for the SMS channel.
PushProviderIDOverride provider for the push channel.
FromEmailOverride sender email address.
FromNameOverride sender display name.
FromPhoneOverride sender phone number (SMS).
DefaultLocaleOverride default locale for template rendering.

The ProviderIDFor method returns the provider ID override for a given channel:

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

Scoped config store interface

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, configID id.ScopedConfigID) error
    ListScopedConfigs(ctx context.Context, appID string) ([]*Config, error)
}

On this page