Notifications
The Notify engine.
Every notification in Zvessa has a topic and goes through the person's own settings.
Topics #
| Topic | What | Starts |
|---|---|---|
| talk.message, talk.reply, talk.mention | Chats | on |
| calls.ring (urgent), calls.missed | Calls | on |
| social.reply, social.mention | Feeds | on |
| social.like, social.follow, social.repost | Feeds | digest |
| reviews.new, reviews.reply, reviews.report | Reviews | on |
| apps.<appId> | One mini-app's notes | on |
| space.announce | Managers' announcements | on |
| your own, e.g. orders.new | blueprint.notifications.topics | on |
A topic's group is the part before the dot. A rule for talk covers every talk topic; * covers all.
What a person chooses #
- Mode per topic or group: on (right away), digest (one summary a day), off; for every space or one space. The most specific rule wins: this space beats every space, the exact topic beats its group, the group beats
*. - Quiet hours (
22:00–07:00): held and sent as one summary when they end. Calls still ring unless "Hold calls too". - Pause for 1 h, 8 h or a day.
- Digest time (default 18:00, or the space's
digestAt), in the person's own time zone.
What a space sets (blueprint, propose → approve → undo) #
"notifications": {
"topics": [{ "id": "orders.new", "name": "New orders", "about": "…", "mode": "on" }],
"defaults": { "social": "digest", "orders.new": "on" },
"digestAt": "18:00",
"kidsQuiet": { "from": "20:00", "to": "07:00" },
"inboxPage": "notifications"
}
Blocks #
notify.settings— the viewer's own controls. Props:title,sections(pause, quiet, digest, topics, spaces),topics.notify.inbox— newest first; tap opens the exact page, switching space. Props:title,scope(all, space),limit,topics.notify.bell— unread count, opens the inbox. Props:label,page,scope.notify.send— managers send to members. Props:title,topic,to(role ids),link.
Kits: Notifications (a page with inbox + settings), Announcements (a page with the send form). Without either, the bell and summaries open the built-in /notifications screen.
Where a tap goes #
link is an app path: /p/<page>, /c/<conversation>?thread=<id>, /dm/<user>, /post/<id>, /u/<handle>, /tag/<tag>, /s/<id>, /ai, /notifications, or the short form page:<pageId>. Anything else is dropped.
AI and MCP #
| Tool | Scope | Does |
|---|---|---|
my_notifications | me | Topics here with their mode and where it comes from; quiet hours, pause, digest, spaces |
change_my_notifications | me | { rules: [{ topic, mode, space? }], quiet, digestAt, pause }; applies now, undoable |
undo_my_notifications | me | Back to before the last change |
my_inbox | me | Recent notifications |
send_notification | notify | Managers only, 10 an hour per space; outside AIs only |
The in-app AI has me. Space settings change with propose_change on /notifications.
From a kit (server) #
import { notify } from "convex/notify";
await notify(ctx, { userIds, topic: "orders.new", workspaceId, title, body, link: "page:orders", group: `order:${id}`, from });
Mini-apps: notify.members({ title, body?, to?, page? }) sends on apps.<appId>.