Delivery
The two ways a message leaves this service, and what is recorded about it. delivery_log
is the only record that an attempt happened, so its contents are as much the subject here
as the send.
REQ-0042 — the web job is unconditional and the email job is not
One notification becomes one job per channel:
- web, always —
channel_webon the preference row is not consulted, so a participant cannot switch the web channel off through this path; - email, when the facts name explicit recipients, or the participant set
channel_emailand left an address.
The exception is an email-only ingest (REQ-0014): a user_id beginning email-ingest:
together with explicit recipients gets no web job. Both halves are required — recipients
under a real participant, or a synthetic id with no recipients, still get one.
The web destination is web:<user> or web:<user>:<community>, and that string is what
the daily cap counts (REQ-0039), so its shape is load-bearing rather than cosmetic.
Jobs of one notification share its nudge_id, notification_id and dedup_key and
differ only in channel, destination and their own id.
Only web and email have publishers. Channel also declares telegram and
whatsapp — storable on a preference row — and asking for either raises.
REQ-0043 — explicit recipients are filtered, de-duplicated and kept in order
facts.email_recipients must be a list; entries are trimmed, matched against a simple
address pattern, de-duplicated case-insensitively keeping the first spelling, and kept in
the order given.
An unparseable address is dropped silently. A sender whose list is half typos is told
nothing; a sender whose list is entirely typos gets missing_target (REQ-0013).
Explicit recipients replace the participant's own address rather than adding to it.
REQ-0044 — a push goes to every enabled subscription of that participant
Selected by user id and enabled. A disabled subscription and another participant's are
excluded in SQL rather than by the push service rejecting them.
When the job carries a community, subscriptions for that community and those with no community are included — a browser that subscribed before the participant joined a community keeps working. When the job carries none, the community filter is not applied at all, so a community-scoped subscription receives it. Scoping down is filtered; scoping up is not.
REQ-0045 — a push carries the message, the ids, and a signed click token
{title, body, data: {url, nudge_id, rule_id, notification_id, click_tracking_token}}.
The token is verifiable and names exactly the notification it was minted for; it is
null when the job has no notification id. url defaults to /.
The token is what lets a service worker report a click from a page with no session (REQ-0056).
REQ-0046 — a subscription the push service has forgotten is disabled
404 and 410 mean the endpoint is gone for good, and it is disabled so that it is not
retried for every notification for ever — each retry being a request to somebody else's
server.
Any other failure, including an exception carrying no response, leaves the subscription
enabled: a 503 during an outage is not evidence that a browser has gone.
The same handling applies to an administrator's test push (REQ-0005).
REQ-0047 — an absent recipient and an absent configuration are failures, not errors
Recorded and returned rather than raised:
| Situation | Recorded |
|---|---|
| the participant has no enabled subscription | failed, no_subscriptions |
VAPID_PRIVATE_KEY is unset |
failed, Missing VAPID_PRIVATE_KEY |
SMTP_HOST or EMAIL_FROM is unset |
failed, Missing SMTP_HOST / Missing EMAIL_FROM |
The first is the ordinary case for anyone who never allowed notifications in their
browser, so delivery_log reports failures for every such participant and a genuine
outage looks the same.
The other two mean a service deployed without configuration keeps accepting events and silently delivers nothing.
REQ-0048 — one delivery-log row per attempt, whatever the outcome
The row's id is the job's id, and it carries the nudge id, the destination, the status,
the error and sent_at when it was sent.
The web publisher records its channel as webpush while the job's channel is web.
Nothing depends on the agreement, because the cap counts by destination prefix rather
than by channel.
REQ-0049 — email is text/plain, over STARTTLS unless SSL is configured
Subject is the rendered title, body the rendered body. SMTP_USE_SSL selects implicit
TLS and makes SMTP_USE_TLS irrelevant; the default is STARTTLS on port 587. With no
SMTP_USERNAME the login step is skipped rather than attempted empty — an
unauthenticated relay is a supported configuration.
Every exception is caught and recorded, so one channel failing never stops the other from being tried.