Documentation
¶
Overview ¶
Package eligibility derives whether a message on a channel from a sending source may reach a destination (ADR 0001, 0013). Eligibility is never a stored flag on the Contact; it is computed in layers against three destination-keyed stores:
- (channel, destination) in Suppression → never (global hard floor)
- (channel, destination) unsubscribed "everything" → never
- (channel, destination) unsubscribed from source → never
- workspace requires confirmed opt-in and no Confirmation → never
- otherwise → send
The layers are defined exactly once (see layers) and consumed in two shapes that therefore cannot drift: Check decides one (workspace, destination) pair, and Predicate narrows a Contact query to the eligible audience. The rule is keyed by destination, never by Contact — a transactional destination may have no Contact.
A source of "" means a transactional send (ADR 0005): it skips layers 2–4 (you cannot opt out of, nor must you confirm, your own password reset) but still respects Suppression. Whether the workspace requires confirmed opt-in is read inside the rule, so no caller can forget the confirmation gate.
Stored destinations are normalized (lower-cased + trimmed) on write, but contact.email is not, so the Contact-side comparison folds case in SQL while Check normalizes its input with suppression.NormalizeDestination.
Index ¶
Constants ¶
const ( ReasonSuppressed = "suppressed" ReasonUnsubscribedEverything = "unsubscribed_everything" ReasonUnsubscribedSource = "unsubscribed_source" ReasonUnconfirmed = "unconfirmed" )
Reasons a destination is ineligible.
const ( SourceBroadcasts = "broadcasts" SourceEverything = "everything" )
Sending sources — the unit a destination unsubscribes from. A source is automatic, never hand-authored: it IS the sender. All broadcasts share one scope; each automation is its own; "everything" is the reserved deliberate "leave entirely" opt-out (only ever an unsubscribe scope, never a send source).
const ChannelEmail = "email"
ChannelEmail identifies the email channel, the only built one; sms is reserved.
Variables ¶
This section is empty.
Functions ¶
func AutomationSource ¶
AutomationSource is the per-automation unsubscribe scope.
func GloballyOptedOut ¶
GloballyOptedOut matches contacts whose destination is non-mailable on the given channel regardless of source: suppressed, or unsubscribed from "everything". Used for the "unsubscribed" analytics KPI — the derived, dashboard-level view of "fully opted out", since there is no stored contact status (ADR 0001). A per-source opt-out is deliberately not counted: that contact may still be mailable from other sources.
func ParseAutomationSource ¶
ParseAutomationSource is the inverse of AutomationSource: it returns the automation id when src is an "automation:<id>" scope, and false otherwise.
func Predicate ¶
Predicate narrows a Contact query to contacts whose email destination is eligible for (channel, source); "" source means transactional (Suppression only). It composes with a segment predicate (both are predicate.Contact). The correlation joins on lower(contacts.email) — never on contact_id — because the stores are destination-keyed.
Types ¶
type Decision ¶
type Decision struct {
Eligible bool
// Reason is empty when Eligible; otherwise one of the Reason* constants.
Reason string
}
Decision is the outcome of a single eligibility Check.
func Check ¶
Check decides eligibility for one destination in the scoped Workspace. source is the Sending source the message goes out under, or "" for a transactional send. An empty destination is treated as eligible — the caller decides what a missing address means (there is nothing to suppress against).
The rule is one SQL expression (layers) whose Workspace comes from s; the check fails closed (an error) for a Workspace that does not exist; the confirmation gate reads the workspace flag inside it, so no caller can forget it. It is anchored on the Workspace's Suppression query so it runs through the scoped client; HAVING TRUE turns the select into a single-group one, so exactly one row comes back whether or not the Workspace has any Suppression.