Documentation
¶
Overview ¶
Package notifydelivery assembles the email-notification substrate into one startable handle: the settings, preference, and queue stores, the trigger-side enqueuer, the renderer and SMTP sender, and the send worker with its LISTEN/NOTIFY wakeup adapter.
It is the only place that names every layer of the substrate at once. Each layer is a package of its own (pkg/notification for the domain, pkg/notification/smtp for the mail-server settings, internal/notification/* for persistence, rendering, transport and the worker) and knows only the layers below it; the composition happens here.
The package must not import pkg/platform. The composition root (the HTTP server, which owns the portal and admin surfaces the feature serves) passes in the *sql.DB, DSN, encryptor, and branding values it already holds, and brackets Start/Stop around its serve loop.
Index ¶
- Constants
- type Config
- type Handle
- func (h *Handle) Enqueuer() *notification.Enqueuer
- func (h *Handle) History() notification.HistoryStore
- func (h *Handle) PortalNotifier(stores PortalStores, baseURL string) *PortalNotifier
- func (h *Handle) Prefs() notification.PrefsStore
- func (h *Handle) SendGuestLink(ctx context.Context, to, link string) error
- func (h *Handle) SendTest(ctx context.Context, to string) error
- func (h *Handle) Settings() smtp.SettingsStore
- func (h *Handle) Start(ctx context.Context)
- func (h *Handle) Stop()
- type PortalNotifier
- type PortalStores
- type ThreadGrantees
Constants ¶
const HistoryRetention = notifyworker.DefaultResolvedRetention
HistoryRetention is how long a resolved row survives before the worker's purge removes it -- the window History can report on. Both surfaces show it so a reader knows the listing is recent history, not an archive.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// DB is the shared platform database pool. Required.
DB *sql.DB
// DSN is the raw database DSN for the LISTEN connection. Empty disables
// LISTEN/NOTIFY; the worker degrades to poll-only.
DSN string
// Encryptor protects the SMTP password at rest. Required (may be a
// nil-wrapping passthrough when encryption is disabled).
Encryptor smtp.StringEncryptor
// Branding is the deployment identity emails render with.
Branding notifyrender.Branding
// DigestHourUTC is the UTC hour daily digests are scheduled for.
DigestHourUTC int
// UnsubscribeURL builds the no-login unsubscribe link for a recipient
// address (#1001). nil omits the footer link from notification emails.
UnsubscribeURL func(email string) string
}
Config carries everything the substrate needs. No platform types.
type Handle ¶
type Handle struct {
// contains filtered or unexported fields
}
Handle owns the running substrate.
func New ¶
New composes the substrate. Returns nil when cfg.DB is nil (no database: the feature is unavailable and every accessor degrades gracefully).
func (*Handle) Enqueuer ¶
func (h *Handle) Enqueuer() *notification.Enqueuer
Enqueuer returns the trigger-side entry point. Nil-safe: a nil handle returns a nil enqueuer, whose Notify drops everything.
func (*Handle) History ¶ added in v1.118.0
func (h *Handle) History() notification.HistoryStore
History returns the delivery-history reader the admin monitoring surface and each user's own notification screen read from, or nil when the feature is unavailable. It is the same PostgreSQL store the worker writes through, narrowed to its read contract.
func (*Handle) PortalNotifier ¶
func (h *Handle) PortalNotifier(stores PortalStores, baseURL string) *PortalNotifier
PortalNotifier builds the portal-facing notifier over this handle's enqueuer. Returns nil when the handle is nil (feature unavailable); the caller must then leave the portal's Notifier dependency unset.
func (*Handle) Prefs ¶
func (h *Handle) Prefs() notification.PrefsStore
Prefs returns the preference store, or nil when the feature is unavailable.
func (*Handle) SendGuestLink ¶ added in v1.106.0
SendGuestLink delivers a one-time view link email directly through the sender (#1001). The send is transactional: the recipient requested it from a share landing page, so it bypasses the queue (no digest deferral) and the preference gate (an opted-out recipient still gets the link they asked for). It uses the same stored SMTP settings and deliverability gate as every other send.
func (*Handle) SendTest ¶
SendTest delivers a test email to the given recipient using the currently stored SMTP settings, so an admin can verify the configuration end to end.
The admin API answers a failed test with fixed text that does not vary with the failure mode (#1072), so this log is the only place the underlying error survives. Every failure below is therefore recorded, with the host and port that produced it.
func (*Handle) Settings ¶
func (h *Handle) Settings() smtp.SettingsStore
Settings returns the settings store, or nil when the feature is unavailable.
type PortalNotifier ¶
type PortalNotifier struct {
// contains filtered or unexported fields
}
PortalNotifier implements portal.Notifier: it turns portal share and thread events into queued email notifications. All failures are logged, never surfaced -- a share or comment must succeed regardless of notification state.
func NewPortalNotifier ¶
func NewPortalNotifier(enq *notification.Enqueuer, stores PortalStores, baseURL string) *PortalNotifier
NewPortalNotifier builds a notifier over an enqueuer directly; tests and the handle-based constructor below share it.
func (*PortalNotifier) NotifyShare ¶
func (n *PortalNotifier) NotifyShare(ctx context.Context, share *portal.Share, ev portal.ShareEvent)
NotifyShare queues a "shared with you" email for a direct share. Token-only shares (no recipient email) queue nothing: a share targeted by user ID alone carries no email address anywhere in the system.
func (*PortalNotifier) NotifyThreadEvent ¶
func (n *PortalNotifier) NotifyThreadEvent(ctx context.Context, thread *portal.Thread, actorEmail, body string, mentioned []string)
NotifyThreadEvent queues the emails one thread event produces: a mention email for each person the body named, and a "new comment/feedback" email for everyone else attached to the target -- its owner, the thread author, and the people it is shared with.
Mentions are queued first, per recipient: the author chose those addresses, so each one costs a token of their rate limit. The general fan-out then goes out as one batch, because the size of a target's share list is a property of the item rather than something the author picked.
Only a mention that was actually queued removes someone from the general fan-out. A recipient whose mention was dropped -- they muted the mention category, or the enqueue failed -- has been told nothing, so they still get the comment notification they would have had without being named.
type PortalStores ¶
type PortalStores struct {
Assets portal.AssetStore
Collections portal.CollectionStore
Prompts portal.PromptStore
KnowledgePages knowledgepage.Store
// Grantees lists the people holding an explicit grant on a thread target.
// Optional: nil narrows thread notifications to the target owner and the
// thread author.
Grantees ThreadGrantees
}
PortalStores bundles the read access the notifier needs to resolve a thread's target (title, deep link, owner email) and the people attached to it.
type ThreadGrantees ¶ added in v1.114.0
type ThreadGrantees interface {
Grantees(ctx context.Context, targetType, targetID string) ([]string, error)
}
ThreadGrantees lists the addresses holding an explicit grant on a thread target: its owner and the recipients of active shares. Satisfied by pkg/portal/mention.Audience.