Notification preferences

Per-(customer × event_type × channel) opt-in/opt-out toggles. The single check that decides "should this customer receive this email?".

How it's used

The send service calls should_notify(customer, event_type, channel) before every transactional message. If it returns false, the send is short-circuited (no Resend call, 409 Conflict returned to the caller).

Resolution order

1. exact row          (customer, event_type, channel)
2. wildcard row       (customer, '<namespace>.*', channel)
3. default argument   (caller-provided fallback)

The wildcard pattern matches the namespace prefix:

Stored event_typeMatches
marketing.*marketing.weekly_digest, marketing.flash_sale, ...
order.*order.confirmation, order.shipped, ...
*All events for this channel

A specific row always wins over a wildcard. So a customer can opt out of all marketing (marketing.*: false) but still opt in to one specific marketing email (marketing.flash_sale: true).

Set a preference

curl -X PUT "$API/customers/{customer_id}/notifications.json" \
  -H "Authorization: Bearer $NEVIOS_KEY" -H "Content-Type: application/json" \
  -d '{
    "event_type": "marketing.*",
    "channel": "email",
    "enabled": false,
    "source": "unsubscribe_link",
    "reason": "User clicked unsubscribe link in 2026-04-15 newsletter"
  }'

source indicates who changed the preference (customer, staff, api, unsubscribe_link). Useful for compliance audits.

List a customer's preferences

curl "$API/customers/{customer_id}/notifications.json" \
  -H "Authorization: Bearer $NEVIOS_KEY"

Check resolution (debug)

curl "$API/customers/{customer_id}/notifications/check.json?event_type=marketing.weekly&channel=email&default=true" \
  -H "Authorization: Bearer $NEVIOS_KEY"
{
  "customer_id": "308...",
  "event_type": "marketing.weekly",
  "channel": "email",
  "enabled": false        // matched a 'marketing.*' wildcard row
}

Channels

ChannelNotes
emailPhase 1 — primary
smsReserved (carrier integrations not yet shipped)
pushReserved (mobile app push)
in_appReserved (dashboard inbox-style notifications)

Compliance use cases

  • Unsubscribe link: clicking it sets marketing.*: false for that customer
  • Account closure: the GDPR redaction flow doesn't touch preferences (the customer's email is anonymized so nothing can reach them anyway)
  • Customer self-service: PUT from the dashboard's preference panel
  • Staff override: rarely needed — set with source: "staff" for audit

Endpoints

PUT    /admin/2026-01/{handle}/customers/{customer_id}/notifications.json
GET    /admin/2026-01/{handle}/customers/{customer_id}/notifications.json
GET    /admin/2026-01/{handle}/customers/{customer_id}/notifications/check.json

Next: Email loop.