notification

package
v1.107.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Overview

Package notification is the platform's email-notification substrate: a typed admin SMTP settings store, per-user notification preferences, a durable delivery queue, and the enqueue path consulted by share and thread-comment triggers. Delivery (worker + SMTP send + branded rendering) also lives here; the platform wires it through the internal/platform/notifydelivery seam.

Index

Constants

View Source
const (
	// CategoryShare covers direct shares of assets, collections, and prompts.
	CategoryShare = "share"
	// CategoryComment covers thread comments and feedback events.
	CategoryComment = "comment"
)

Notification categories. A category maps to a per-user preference toggle and an email template family.

View Source
const (
	// ModeOff drops notifications at enqueue time.
	ModeOff = "off"
	// ModeImmediate queues one email per event.
	ModeImmediate = "immediate"
	// ModeDaily batches a user's events into one digest email per day.
	ModeDaily = "daily"
)

Delivery modes for user preferences.

View Source
const (
	// StatusPending marks a row waiting to be claimed.
	StatusPending = "pending"
	// StatusSending marks a row claimed by a worker (lease via locked_until).
	StatusSending = "sending"
	// StatusSent marks a delivered row.
	StatusSent = "sent"
	// StatusFailed marks a row that exhausted its attempts.
	StatusFailed = "failed"
)

Queue row statuses.

View Source
const (
	// KindAsset marks a shared asset.
	KindAsset = "asset"
	// KindCollection marks a shared collection.
	KindCollection = "collection"
	// KindPrompt marks a shared prompt.
	KindPrompt = "prompt"
	// KindComment marks a thread comment.
	KindComment = "comment"
	// KindFeedback marks a thread feedback event.
	KindFeedback = "feedback"
)

Payload item kinds.

View Source
const (
	// TLSModeStartTLS negotiates STARTTLS on a plaintext connection (port 587).
	TLSModeStartTLS = "starttls"
	// TLSModeImplicit opens a TLS connection directly (port 465).
	TLSModeImplicit = "implicit"
	// TLSModeNone sends in plaintext. Only for closed-network relays.
	TLSModeNone = "none"
)

SMTP TLS modes.

View Source
const (
	// DefaultPollEvery is the fallback poll interval when LISTEN/NOTIFY does
	// not wake the worker.
	DefaultPollEvery = 30 * time.Second
	// DefaultLease bounds one delivery attempt; an expired lease returns the
	// row to claimable state for crash recovery.
	DefaultLease = 2 * time.Minute
	// DefaultMaxAttempts is the delivery attempt budget per row.
	DefaultMaxAttempts = 5

	// DefaultResolvedRetention keeps sent/failed rows for operator
	// inspection before the purge removes them.
	DefaultResolvedRetention = 30 * 24 * time.Hour
	// DefaultPendingTTL bounds how long an undelivered row stays relevant.
	// Beyond it the event is stale (nobody wants a share email from last
	// month when SMTP is finally configured) and the purge drops it.
	DefaultPendingTTL = 7 * 24 * time.Hour
)

Worker defaults.

View Source
const NotifyChannel = "notifications"

NotifyChannel is the pg_notify channel that wakes the send worker when a row is enqueued. Producers fire it best-effort; the worker also polls.

View Source
const SettingsSectionSMTP = "smtp"

SettingsSectionSMTP is the platform_settings section key for SMTP.

Variables

View Source
var (
	// ErrNotFound is returned when a requested row does not exist.
	ErrNotFound = errors.New("notification: not found")
	// ErrNoWork is returned by claim methods when no due row is available.
	ErrNoWork = errors.New("notification: no work available")
	// ErrSMTPNotConfigured is returned by delivery actions when SMTP is
	// absent, disabled, or missing a host; the caller should surface it as
	// a configuration conflict, not a delivery failure.
	ErrSMTPNotConfigured = errors.New("notification: smtp is disabled or not configured")
)

Sentinel errors returned by the stores in this package.

Functions

func NextDigestTime

func NextDigestTime(now time.Time, hourUTC int) time.Time

NextDigestTime returns the next occurrence of hourUTC:00 strictly after now. The result is in UTC.

func PortalLink(baseURL, route string) string

PortalLink builds an absolute portal SPA link, or empty when no public base URL is configured.

func RecipientsExcluding

func RecipientsExcluding(actor string, candidates ...string) []string

RecipientsExcluding returns the de-duplicated candidate list minus the actor (case-insensitive) and empties. Used to fan a thread event out to the target owner and thread author without self-notification.

func Snippet

func Snippet(s string) string

Snippet bounds a message body for an email excerpt without splitting a multi-byte rune.

func UnsubToken added in v1.106.0

func UnsubToken(key []byte, email string) string

UnsubToken returns the opaque token an email footer's unsubscribe link carries for recipient. The token binds the address with an HMAC so only a holder of the emailed link can opt that address out; it deliberately never expires, since a stale footer link that stops working strands exactly the recipient the link exists for. It grants nothing but the opt-out: prefs are keyed by bare email, so it works identically for account holders and recipients with no account (#1001).

func ValidMode

func ValidMode(m string) bool

ValidMode reports whether m is one of the delivery modes.

func VerifyUnsubToken added in v1.106.0

func VerifyUnsubToken(key []byte, tok string) (email string, ok bool)

VerifyUnsubToken validates tok and returns the recipient address it was minted for. ok is false for a malformed token or a MAC mismatch.

Types

type Branding

type Branding struct {
	// Name is the portal brand/title, e.g. "ACME Data Platform".
	Name string
	// BaseURL is the portal's public base URL used for deep links.
	BaseURL string
	// ImplementorName and ImplementorURL render in the footer when set.
	ImplementorName string
	ImplementorURL  string
	// LogoPNG is the raster logo from portal.logo_email, resolved once at
	// startup. When non-empty it is attached to every message as an inline
	// (cid:) part and rendered above the wordmark; recipients never fetch it
	// remotely, so it survives the image blocking most clients apply by
	// default. Empty renders the wordmark alone.
	LogoPNG []byte
}

Branding carries the deployment identity emails render with. Values come from the same PortalConfig the portal UI uses, so emails match the portal. The brand name always renders as a styled wordmark linked to BaseURL; the portal's SVG logo is never usable here because email clients strip inline SVG, so LogoPNG carries a separate raster asset when the operator supplies one.

type Email

type Email struct {
	To      string
	Subject string
	HTML    string
	Text    string
	// LogoPNG is the inline logo the sender must attach under logoContentID
	// when non-empty. The HTML references it but cannot carry the bytes.
	LogoPNG []byte
}

Email is one rendered message ready for an SMTP sender.

type Enqueuer

type Enqueuer struct {
	// contains filtered or unexported fields
}

Enqueuer is the trigger-side entry point: it consults the recipient's preferences and either drops the event, queues it for immediate delivery, or schedules it into the recipient's next daily digest window.

Enqueue is a single cheap DB insert; callers on a request path must log a returned error and continue — a share or comment never fails because its notification could not be queued.

func NewEnqueuer

func NewEnqueuer(prefs PrefsStore, queue QueueStore, digestHourUTC int) *Enqueuer

NewEnqueuer creates an Enqueuer. digestHourUTC is the hour of day (0-23, UTC) daily digests are scheduled for. Close releases the limiter's background goroutine.

func (*Enqueuer) Close

func (e *Enqueuer) Close()

Close stops the limiter's background eviction goroutine. Nil-safe.

func (*Enqueuer) Notify

func (e *Enqueuer) Notify(ctx context.Context, recipient, category string, p Payload) error

Notify queues one notification for recipient according to their preferences. Events targeting nobody (empty recipient) or the actor themselves are dropped silently, as are events the recipient opted out of. A nil Enqueuer (feature not wired, e.g. no database) drops everything.

type Listener

type Listener struct {
	// contains filtered or unexported fields
}

Listener is the LISTEN side of the queue's LISTEN/NOTIFY adapter. Enqueue fires NOTIFY; this goroutine receives it and wakes the worker so immediate notifications go out without waiting for the poll interval.

func NewListener

func NewListener(dsn string, notifiers ...notifier) *Listener

NewListener constructs a Listener for the supplied DSN. It does not connect until Start is called.

func (*Listener) Start

func (l *Listener) Start(_ context.Context) error

Start opens the LISTEN connection and spawns the receive goroutine. An error means delivery latency degrades to the worker's poll interval; the caller decides whether that is fatal.

func (*Listener) Stop

func (l *Listener) Stop()

Stop closes the LISTEN connection and waits for the receive goroutine.

type Notification

type Notification struct {
	ID           int64      `json:"id"`
	Recipient    string     `json:"recipient"`
	Category     string     `json:"category"`
	Payload      Payload    `json:"payload"`
	Digest       bool       `json:"digest"`
	Status       string     `json:"status"`
	Attempts     int        `json:"attempts"`
	LastError    string     `json:"last_error,omitempty"`
	ScheduledFor time.Time  `json:"scheduled_for"`
	SentAt       *time.Time `json:"sent_at,omitempty"`
	CreatedAt    time.Time  `json:"created_at"`
}

Notification is one queued delivery.

type Payload

type Payload struct {
	// Kind is one of the Kind* constants.
	Kind string `json:"kind"`
	// ItemID identifies the shared or commented item.
	ItemID string `json:"item_id"`
	// ItemTitle is the human-readable name of the item.
	ItemTitle string `json:"item_title"`
	// Actor is the email of the person who shared or commented.
	Actor string `json:"actor"`
	// Message is an optional comment/feedback snippet.
	Message string `json:"message,omitempty"`
	// Link is the absolute portal deep link for the item.
	Link string `json:"link,omitempty"`
}

Payload carries the event details a template needs to render an email. It is stored as the queue row's JSONB payload.

type PostgresPrefsStore

type PostgresPrefsStore struct {
	// contains filtered or unexported fields
}

PostgresPrefsStore implements PrefsStore backed by user_notification_prefs.

func NewPostgresPrefsStore

func NewPostgresPrefsStore(db *sql.DB) *PostgresPrefsStore

NewPostgresPrefsStore creates a PostgreSQL-backed preferences store.

func (*PostgresPrefsStore) Get

func (s *PostgresPrefsStore) Get(ctx context.Context, email string) (Prefs, error)

Get returns the stored preferences or DefaultPrefs when absent.

func (*PostgresPrefsStore) Set

func (s *PostgresPrefsStore) Set(ctx context.Context, email string, u PrefsUpdate) (Prefs, error)

Set applies u over the user's current (or default) preferences and upserts the result.

type PostgresQueueStore

type PostgresQueueStore struct {
	// contains filtered or unexported fields
}

PostgresQueueStore implements QueueStore backed by the notifications table.

func NewPostgresQueueStore

func NewPostgresQueueStore(db *sql.DB) *PostgresQueueStore

NewPostgresQueueStore creates a PostgreSQL-backed notification queue.

func (*PostgresQueueStore) ClaimDigest

func (s *PostgresQueueStore) ClaimDigest(ctx context.Context, lease time.Duration) ([]Notification, error)

ClaimDigest claims all due digest rows for the recipient with the oldest due digest row. Concurrent workers racing for the same recipient are safe: the loser's UPDATE matches zero rows and reports ErrNoWork.

func (*PostgresQueueStore) ClaimImmediate

func (s *PostgresQueueStore) ClaimImmediate(ctx context.Context, lease time.Duration) (*Notification, error)

ClaimImmediate claims the next due non-digest row.

func (*PostgresQueueStore) Enqueue

Enqueue inserts a pending notification row and fires a best-effort pg_notify so a listening worker wakes without waiting for the next poll.

func (*PostgresQueueStore) Fail

func (s *PostgresQueueStore) Fail(ctx context.Context, ids []int64, sendErr string) error

Fail marks claimed rows permanently failed.

func (*PostgresQueueStore) MarkSent

func (s *PostgresQueueStore) MarkSent(ctx context.Context, ids []int64) error

MarkSent transitions claimed rows to sent.

func (*PostgresQueueStore) PurgeOld

func (s *PostgresQueueStore) PurgeOld(ctx context.Context, resolvedRetention, pendingTTL time.Duration) (int64, error)

PurgeOld deletes resolved rows past retention and unresolved rows past their delivery-relevance window.

func (*PostgresQueueStore) Retry

func (s *PostgresQueueStore) Retry(ctx context.Context, ids []int64, sendErr string, backoff time.Duration) error

Retry returns claimed rows to pending, scheduled after the backoff.

type PostgresSettingsStore

type PostgresSettingsStore struct {
	// contains filtered or unexported fields
}

PostgresSettingsStore implements SettingsStore backed by the platform_settings table. Secrets inside a section value are encrypted with the injected StringEncryptor before they reach the database.

func NewPostgresSettingsStore

func NewPostgresSettingsStore(db *sql.DB, enc StringEncryptor) *PostgresSettingsStore

NewPostgresSettingsStore creates a PostgreSQL-backed settings store. The encryptor may be nil-safe (encryption disabled) but must not be nil.

func (*PostgresSettingsStore) GetSMTP

GetSMTP returns the stored SMTP settings with the password decrypted.

func (*PostgresSettingsStore) SetSMTP

func (s *PostgresSettingsStore) SetSMTP(ctx context.Context, in SMTPSettings, author string) error

SetSMTP upserts the SMTP settings. An empty incoming password keeps the previously stored (still encrypted) one so the admin UI never round-trips the secret.

type Prefs

type Prefs struct {
	Email           string    `json:"email"`
	Mode            string    `json:"mode"`
	SharesEnabled   bool      `json:"shares_enabled"`
	CommentsEnabled bool      `json:"comments_enabled"`
	UpdatedAt       time.Time `json:"updated_at"`
}

Prefs is one user's notification preferences. Absence of a stored row means DefaultPrefs applies (immediate delivery, all categories on), per the platform's important-features-default-on convention.

func DefaultPrefs

func DefaultPrefs(email string) Prefs

DefaultPrefs returns the preferences applied to a user with no stored row.

type PrefsAPI

type PrefsAPI struct {
	Store PrefsStore
	// UserEmail resolves the authenticated user's email from the request,
	// returning "" when unauthenticated.
	UserEmail func(*http.Request) string
}

PrefsAPI serves the self-scoped notification preference REST endpoints. It registers onto the portal's authenticated mux via a registrar hook (the datahubapi pattern) so the feature lives in this package; the composition root supplies UserEmail to resolve the authenticated caller. Server-side self-scope: the caller's email is the only key ever read or written.

func (*PrefsAPI) Register

func (a *PrefsAPI) Register(mux *http.ServeMux)

Register mounts the preference endpoints on mux.

type PrefsRequest

type PrefsRequest struct {
	Mode            *string `json:"mode,omitempty"`
	SharesEnabled   *bool   `json:"shares_enabled,omitempty"`
	CommentsEnabled *bool   `json:"comments_enabled,omitempty"`
}

PrefsRequest is the body for updating the caller's preferences. Omitted fields are left unchanged.

type PrefsResponse

type PrefsResponse struct {
	Mode            string `json:"mode"`
	SharesEnabled   bool   `json:"shares_enabled"`
	CommentsEnabled bool   `json:"comments_enabled"`
}

PrefsResponse is the user-facing preferences shape.

type PrefsStore

type PrefsStore interface {
	// Get returns the user's preferences, falling back to DefaultPrefs when
	// no row exists. It never returns ErrNotFound.
	Get(ctx context.Context, email string) (Prefs, error)
	// Set upserts the user's preferences, applying u over the current values.
	Set(ctx context.Context, email string, u PrefsUpdate) (Prefs, error)
}

PrefsStore persists per-user notification preferences.

type PrefsUpdate

type PrefsUpdate struct {
	Mode            *string `json:"mode,omitempty"`
	SharesEnabled   *bool   `json:"shares_enabled,omitempty"`
	CommentsEnabled *bool   `json:"comments_enabled,omitempty"`
}

PrefsUpdate carries the fields of a preferences write; nil fields keep the current (or default) value.

type QueueStore

type QueueStore interface {
	// Enqueue inserts a pending row and nudges the send worker.
	Enqueue(ctx context.Context, n Notification) error
	// ClaimImmediate claims the next due non-digest row under a lease,
	// returning ErrNoWork when none is due.
	ClaimImmediate(ctx context.Context, lease time.Duration) (*Notification, error)
	// ClaimDigest claims every due digest row for one recipient under a
	// lease, returning ErrNoWork when none is due.
	ClaimDigest(ctx context.Context, lease time.Duration) ([]Notification, error)
	// MarkSent transitions claimed rows to sent.
	MarkSent(ctx context.Context, ids []int64) error
	// Retry returns claimed rows to pending after backoff, recording the error.
	Retry(ctx context.Context, ids []int64, sendErr string, backoff time.Duration) error
	// Fail marks claimed rows permanently failed, recording the error.
	Fail(ctx context.Context, ids []int64, sendErr string) error
	// PurgeOld bounds table growth: it deletes resolved (sent/failed) rows
	// older than resolvedRetention and unresolved rows older than
	// pendingTTL. The latter also guarantees that enabling SMTP on a
	// deployment that queued for months does not deliver an ancient
	// backlog. Returns the number of rows deleted.
	PurgeOld(ctx context.Context, resolvedRetention, pendingTTL time.Duration) (int64, error)
}

QueueStore persists and claims queued notifications.

type Renderer

type Renderer struct {
	// contains filtered or unexported fields
}

Renderer renders queued notifications into branded multipart emails.

func NewRenderer

func NewRenderer(b Branding) (*Renderer, error)

NewRenderer parses the embedded templates for the given branding.

func (*Renderer) Render

func (r *Renderer) Render(ns []Notification) (*Email, error)

Render renders one email covering the given notifications. A single notification renders the per-event template; multiple render the digest layout. All notifications must target the same recipient.

func (r *Renderer) RenderGuestLink(to, link string) (*Email, error)

RenderGuestLink renders the one-time view link email a share recipient requests from the landing page (#1001). It is transactional, not a notification: the recipient asked for it, so it carries no unsubscribe footer and its delivery bypasses the queue and preference gating.

func (*Renderer) RenderTest

func (r *Renderer) RenderTest(to string) (*Email, error)

RenderTest renders the admin "send test email" message used to verify a new SMTP configuration end to end.

func (*Renderer) SetUnsubscribeURLFn added in v1.106.0

func (r *Renderer) SetUnsubscribeURLFn(fn func(email string) string)

SetUnsubscribeURLFn installs the builder for the footer's no-login unsubscribe link. The composition root supplies it when it can mint the HMAC tokens the endpoint verifies; without it emails keep only the signed-in preferences link.

type SMTPSender

type SMTPSender struct{}

SMTPSender implements Sender over SMTP via go-mail. A fresh connection is dialed per send: notification volume is human-scale and a stateless dial keeps credential and TLS changes immediate.

func NewSMTPSender

func NewSMTPSender() *SMTPSender

NewSMTPSender creates the production SMTP sender.

func (*SMTPSender) Send

func (*SMTPSender) Send(ctx context.Context, settings SMTPSettings, email Email) error

Send renders no content itself; it wraps email into a multipart message (plaintext body + HTML alternative) and delivers it.

type SMTPSettings

type SMTPSettings struct {
	Enabled  bool   `json:"enabled"`
	Host     string `json:"host"`
	Port     int    `json:"port"`
	Username string `json:"username"`
	Password string `json:"password,omitempty"`
	// From is the sender address, e.g. "platform@example.com".
	From string `json:"from"`
	// FromName is the optional display name for the From address.
	FromName string `json:"from_name"`
	// TLSMode is one of the TLSMode* constants.
	TLSMode string `json:"tls_mode"`
	// UpdatedBy and UpdatedAt describe the last admin write.
	UpdatedBy string    `json:"updated_by,omitempty"`
	UpdatedAt time.Time `json:"updated_at"`
}

SMTPSettings is the admin-configured mail server connection. The password is encrypted at rest and never returned by the admin API.

func (*SMTPSettings) View

func (s *SMTPSettings) View() SMTPSettingsView

View maps stored settings to the password-free read shape.

type SMTPSettingsInput

type SMTPSettingsInput struct {
	Enabled  bool   `json:"enabled" example:"true"`
	Host     string `json:"host" example:"smtp.example.com"`
	Port     int    `json:"port" example:"587"`
	Username string `json:"username,omitempty" example:"mailer@example.com"`
	Password string `json:"password,omitempty" example:"app-password"`
	From     string `json:"from" example:"platform@example.com"`
	FromName string `json:"from_name,omitempty" example:"Data Platform"`
	TLSMode  string `json:"tls_mode,omitempty" example:"starttls"`
}

SMTPSettingsInput is the write shape for the admin SMTP configuration. An empty password keeps the currently stored one.

func (*SMTPSettingsInput) Settings

func (in *SMTPSettingsInput) Settings() SMTPSettings

Settings maps the validated input to store settings.

func (*SMTPSettingsInput) Validate

func (in *SMTPSettingsInput) Validate() string

Validate normalizes defaults in place and returns a non-empty message when the input is invalid. Omitted fields take defaults (port 587, STARTTLS) so a minimal `{"enabled": false}` disable call works.

type SMTPSettingsView

type SMTPSettingsView struct {
	Enabled     bool      `json:"enabled" example:"true"`
	Host        string    `json:"host" example:"smtp.example.com"`
	Port        int       `json:"port" example:"587"`
	Username    string    `json:"username" example:"mailer@example.com"`
	PasswordSet bool      `json:"password_set" example:"true"`
	From        string    `json:"from" example:"platform@example.com"`
	FromName    string    `json:"from_name" example:"Data Platform"`
	TLSMode     string    `json:"tls_mode" example:"starttls"`
	UpdatedBy   string    `json:"updated_by,omitempty" example:"admin@example.com"`
	UpdatedAt   time.Time `json:"updated_at"`
}

SMTPSettingsView is the read shape for the admin SMTP configuration. The password is write-only: the view reports only whether one is stored.

func UnconfiguredSMTPView

func UnconfiguredSMTPView() SMTPSettingsView

UnconfiguredSMTPView is the read shape served before SMTP has ever been configured: disabled, with the transport defaults prefilled.

type Sender

type Sender interface {
	Send(ctx context.Context, settings SMTPSettings, email Email) error
}

Sender delivers one rendered email using the given SMTP settings. Settings are passed per call so admin changes apply to the next send without any worker restart.

type SettingsStore

type SettingsStore interface {
	// GetSMTP returns the stored SMTP settings with the password decrypted,
	// or ErrNotFound when SMTP has never been configured.
	GetSMTP(ctx context.Context) (*SMTPSettings, error)
	// SetSMTP upserts the SMTP settings, encrypting the password at rest.
	// An empty incoming password preserves the previously stored one so the
	// admin UI can stay write-only without re-sending the secret.
	SetSMTP(ctx context.Context, s SMTPSettings, author string) error
}

SettingsStore persists admin-editable platform settings sections. SMTP is the first section; future global settings reuse the same table.

type StringEncryptor

type StringEncryptor interface {
	Encrypt(plaintext string) (string, error)
	Decrypt(ciphertext string) (string, error)
}

StringEncryptor encrypts and decrypts a single string secret. Satisfied by fieldcrypt.RestFieldEncryptor; a nil-safe passthrough is acceptable when encryption is disabled.

type TestEmailRequest

type TestEmailRequest struct {
	To string `json:"to" example:"admin@example.com"`
}

TestEmailRequest is the body for the admin send-test-email action.

func (*TestEmailRequest) Validate

func (r *TestEmailRequest) Validate() string

Validate returns a non-empty message when the recipient is invalid.

type UnsubscribeHandler added in v1.106.0

type UnsubscribeHandler struct {
	Prefs PrefsStore
	// Key signs and verifies the footer tokens; see UnsubToken.
	Key []byte
	// BrandName heads the confirmation page.
	BrandName string
}

UnsubscribeHandler serves GET /portal/notifications/unsubscribe?tok=..., the no-login opt-out linked from every notification email footer. A valid token writes delivery mode "off" for the address it names; the share and comment enqueue paths already honor that mode, so the recipient receives no further notification emails. One-time view links are unaffected: those are transactional sends the recipient asks for, not notifications.

func (*UnsubscribeHandler) ServeHTTP added in v1.106.0

func (h *UnsubscribeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP verifies the token and records the opt-out.

type Worker

type Worker struct {
	// contains filtered or unexported fields
}

Worker drains the notification queue: it claims due rows, renders branded emails, and delivers them over SMTP. It follows the indexjobs worker shape (poll ticker + LISTEN/NOTIFY wakeup, lease-based claiming, retry with exponential backoff). When SMTP is unconfigured or disabled the worker leaves rows pending without burning delivery attempts.

func NewWorker

func NewWorker(cfg WorkerConfig) *Worker

NewWorker creates a send worker, applying defaults for zero config values.

func (*Worker) Notify

func (w *Worker) Notify()

Notify wakes the worker without waiting for the next poll tick. Safe to call from any goroutine; a flurry of calls coalesces into one wakeup.

func (*Worker) Start

func (w *Worker) Start(_ context.Context)

Start launches the worker loop. Idempotent.

func (*Worker) Stop

func (w *Worker) Stop()

Stop terminates the worker loop and waits for in-flight work. Idempotent. An abandoned claimed row is safe: its lease expires and it is reclaimed.

type WorkerConfig

type WorkerConfig struct {
	Queue    QueueStore
	Settings SettingsStore
	Renderer *Renderer
	Sender   Sender
	// PollEvery, Lease, and MaxAttempts default to the package constants
	// when zero.
	PollEvery   time.Duration
	Lease       time.Duration
	MaxAttempts int
}

WorkerConfig configures the send worker.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL