notifications

Notification templates, deliveries, preferences, and push tokens with RLS

Notifications

Multi-channel notification system with reusable templates, delivery tracking, per-user preferences, and device push tokens -- all scoped by organization with row-level security. Depends on the identity skill for organizations and users.

Tables

notification_templates

ColumnTypeDescription
iduuidPrimary key
org_iduuidFK to organizations
nametextHuman-readable template name
slugtextURL-safe identifier, unique per org. Used to look up templates in code
channelnotification_channelDelivery channel: email, sms, push, or in_app
subjecttextSubject line for email notifications. NULL for push/in_app
body_templatetextTemplate string with placeholder variables, e.g. "Hello {{name}}"
created_attimestamptzRow creation timestamp
updated_attimestamptzLast update timestamp (auto-set by trigger)
metadatajsonbArbitrary JSON for template-level settings

Unique constraint on (org_id, slug).

notification_deliveries

ColumnTypeDescription
iduuidPrimary key
org_iduuidFK to organizations
template_iduuidFK to notification_templates. NULL if template was deleted
user_iduuidFK to users. The recipient
channelnotification_channelChannel used for this delivery
subjecttextRendered subject line
bodytextRendered body content
statusdelivery_statusLifecycle state: queued, sent, delivered, failed, or read
sent_attimestamptzWhen the notification was dispatched
read_attimestamptzWhen the recipient opened or read the notification
errortextError message if delivery failed. NULL on success
created_attimestamptzRow creation timestamp
updated_attimestamptzLast update timestamp (auto-set by trigger)
metadatajsonbArbitrary JSON

notification_preferences

ColumnTypeDescription
iduuidPrimary key
org_iduuidFK to organizations
user_iduuidFK to users
channelnotification_channelChannel this preference applies to
categorytextNotification category, e.g. "marketing", "billing", "general"
is_enabledbooleanWhether the user wants to receive notifications for this channel and category
created_attimestamptzRow creation timestamp
updated_attimestamptzLast update timestamp (auto-set by trigger)
metadatajsonbArbitrary JSON

Unique constraint on (user_id, channel, category).

push_tokens

ColumnTypeDescription
iduuidPrimary key
org_iduuidFK to organizations
user_iduuidFK to users
tokentextDevice push token string
platformpush_platformPlatform: ios, android, or web
device_nametextHuman-readable device name
is_activebooleanSet to false when the token is expired or revoked
last_used_attimestamptzTimestamp of last successful push delivery
created_attimestamptzRow creation timestamp
updated_attimestamptzLast update timestamp (auto-set by trigger)
metadatajsonbArbitrary JSON

Unique constraint on (user_id, token).

Enums

notification_channel

ValueDescription
emailEmail delivery
smsSMS text message
pushNative push notification (iOS, Android, or web)
in_appIn-application notification shown in the UI

delivery_status

ValueDescription
queuedWaiting to be sent
sentDispatched to the delivery provider
deliveredConfirmed delivered to the recipient
failedDelivery failed. Check the error column for details
readOpened or read by the recipient

push_platform

ValueDescription
iosApple Push Notification service
androidFirebase Cloud Messaging
webWeb Push API

Row-Level Security

All tables have RLS enabled and are scoped to the current user's organization via get_user_org_id().

  • notification_templates: SELECT/INSERT/UPDATE for own org. DELETE requires admin.
  • notification_deliveries: SELECT/INSERT/UPDATE for own org. DELETE requires admin.
  • notification_preferences: SELECT/INSERT/UPDATE for own org. DELETE requires admin.
  • push_tokens: SELECT/INSERT/UPDATE for own org. DELETE requires admin.

Dependencies

  • identity -- organizations, users

Example Queries

Get unread in-app notifications for the current user:

SELECT nd.id, nd.body, nd.created_at
FROM notification_deliveries nd
JOIN users u ON u.id = nd.user_id
WHERE u.auth_id = auth.uid()
  AND nd.channel = 'in_app'
  AND nd.status != 'read'
ORDER BY nd.created_at DESC;

Count notifications by status for a given channel:

SELECT nd.status, count(*) AS total
FROM notification_deliveries nd
WHERE nd.org_id = get_user_org_id()
  AND nd.channel = 'email'
GROUP BY nd.status
ORDER BY total DESC;

Check a user's notification preferences:

SELECT np.channel, np.category, np.is_enabled
FROM notification_preferences np
WHERE np.user_id = '<user_id>'
  AND np.org_id = get_user_org_id()
ORDER BY np.channel, np.category;

Find all active push tokens for a user:

SELECT pt.token, pt.platform, pt.device_name, pt.last_used_at
FROM push_tokens pt
WHERE pt.user_id = '<user_id>'
  AND pt.is_active = true
  AND pt.org_id = get_user_org_id()
ORDER BY pt.last_used_at DESC NULLS LAST;

Get delivery failure rate by template over the last 30 days:

SELECT nt.name AS template_name,
       count(*) AS total_sent,
       count(*) FILTER (WHERE nd.status = 'failed') AS failed,
       round(
         100.0 * count(*) FILTER (WHERE nd.status = 'failed') / nullif(count(*), 0),
         1
       ) AS failure_pct
FROM notification_deliveries nd
JOIN notification_templates nt ON nt.id = nd.template_id
WHERE nd.org_id = get_user_org_id()
  AND nd.created_at >= now() - interval '30 days'
GROUP BY nt.name
ORDER BY failure_pct DESC;

List users who have opted out of email notifications:

SELECT u.full_name, u.email, np.category
FROM notification_preferences np
JOIN users u ON u.id = np.user_id
WHERE np.org_id = get_user_org_id()
  AND np.channel = 'email'
  AND np.is_enabled = false
ORDER BY u.full_name;