Notification preferences.
Per-actor, per-kind opt-out. The producer checks the preference before inserting a notification row, so muted kinds never land in the inbox or the unread-count — they're simply not written.
Six notification kinds today: follow, reaction, comment_on_post, reply_to_comment, mention, vote. The catalogue grows in lockstep with the schema; new kinds default to enabled for every actor.
1
Read the current settings
One row per known kind. Kinds the actor has not explicitly toggled report enabled: true with an epoch updated_at, so your UI always renders the full catalogue without a separate metadata call.
curl -H "Authorization: Bearer pcft_live_..." \
"https://social.productcraft.co/v1/communities/<communityId>/actors/<actorId>/notification-prefs"{
"data": [
{ "actor_id": "...", "kind": "follow", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "reaction", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "comment_on_post", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "reply_to_comment", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "mention", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "vote", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" }
]
}2
Mute a kind
Idempotent upsert keyed on (actor, kind). The cache (60s LRU) is invalidated on every write, so the next notification fired against this actor sees the new preference within milliseconds — no waiting for the cache to expire.
curl -X PATCH \
-H "Authorization: Bearer pcft_live_..." \
-H "Content-Type: application/json" \
-d '{"enabled": false}' \
"https://social.productcraft.co/v1/communities/<communityId>/actors/<actorId>/notification-prefs/follow"{
"actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
"kind": "follow",
"enabled": false,
"updated_at": "2026-05-11T19:42:00.000Z"
}3
Re-enable
Same endpoint, { "enabled": true }. The row stays — only the boolean flips. You can also delete the row directly via SQL if you ever need to bulk reset, but the API only exposes the toggle.
curl -X PATCH \
-H "Authorization: Bearer pcft_live_..." \
-H "Content-Type: application/json" \
-d '{"enabled": true}' \
"https://social.productcraft.co/v1/communities/<communityId>/actors/<actorId>/notification-prefs/follow"Behaviour
What changes downstream
- Write-time suppression. When a kind is muted, the producer (`tryEmit`) silently skips the insert. The notification never appears in the inbox or the unread-count.
- Stacks with existing suppressions. Self-actions and blocked-pair actions are still skipped first — preferences are an additional gate, not a replacement.
- Fail-open on a DB hiccup. If the preference lookup itself errors out, the producer defaults to delivering — a transient outage must not silently mute the inbox.
- Existing notifications are untouched. Muting a kind only stops new ones from being written. Rows already in the inbox stay where they are; clear them via the existing mark-read endpoints.
Permissions
PAK scopes
- GET requires
social.notify.readonpcft:agora:community/<communityId>. - PATCH requires
social.notify.updateon the same URN.
Both scopes are part of the default owner and admin system roles; member does not have social.notify.update by default. If your customer-backend signs in as an admin or an owner-scoped PAK, you're good.