Banner image for Notifications
Core Concepts 7 min read

Notifications

Receive in-app and email alerts when something in Catalio needs your attention — mentions, comments, proposal queues, component sync events, and announcements — with per-type preferences and quiet hours

Updated
On this page

A Notification is an in-app or email alert delivered to a single user when something in Catalio needs their attention. Notifications route through both channels — the in-app bell icon and the user’s email — based on per-type preferences each user controls in Settings → Notifications.

Every notification is scoped to a single user within a single Organization, so multi-tenancy is enforced at the data layer: a user only ever sees notifications for the organization they are currently working in.

Notification Types

Catalio emits five notification types, each tied to a specific platform event:

Type Triggered When
component_sync A connected Repository finishes a sync and surfaces new Components
mention Another user mentions you in a comment or discussion
comment A new comment is posted on a Catalio entity you own or follow
announcement A system-wide announcement is published by an admin
proposals_pending One or more Change Proposals are awaiting your review or approval

The notification_type field on each record uses these exact atom values, and they drive both the routing (which preference toggle applies) and the UI rendering (icon, color).

Delivery Channels

Each notification can be delivered via two channels, controlled independently:

In-app — Appears in the bell icon dropdown and the /notifications index. Delivered in real time via Phoenix PubSub on the topic notifications:{user_id}, so an open browser tab sees the new notification without a refresh.

Email — Delivered to the user’s registered email address via the background email worker. Email delivery respects quiet hours (see below) and the configured digest frequency.

A single notification record can route to one channel, both, or neither, depending on the user’s preferences for that type at the moment it is created.

User Preferences

Every user has a notification_preferences map on their profile, configured at Settings → Notifications. The defaults are:

Preference Default Value What It Controls
inapp_proposals_pending true In-app notification when a change proposal needs review
email_proposals_pending true Email notification when a change proposal needs review
inapp_component_sync true In-app notification when a repository sync surfaces Components
email_component_sync false Email notification for the same event
inapp_mentions true In-app notification when you are @-mentioned
email_mentions false Email notification when you are @-mentioned
inapp_announcements true In-app notification for system announcements
email_announcements false Email notification for system announcements
inapp_realtime true Toggle real-time PubSub delivery (vs. waiting for a refresh)
email_digest_frequency "never" Roll up emails into a digest (e.g., daily) instead of per-event
quiet_hours_enabled false Suppress email delivery during a configured window
quiet_hours_start "22:00" Start of the quiet window (UTC, 24-hour format)
quiet_hours_end "08:00" End of the quiet window (UTC, 24-hour format)

Defaults lean toward “in-app yes, email no” for ambient events (component sync, mentions, announcements) and “in-app + email” for the two events that typically require a response — proposals pending review.

Quiet Hours

When quiet_hours_enabled is true, email notifications generated during the configured window are suppressed. In-app notifications still arrive in the bell — the user can read them when they next open Catalio — but no email is sent during the window. The window can wrap midnight (e.g., 22:00 → 08:00 is treated as overnight), and times are currently evaluated in UTC.

Quiet hours apply to direct email delivery only; the digest frequency setting independently controls whether emails accumulate into a roll-up.

Lifecycle and Actions

Notifications have a minimal, immutable-ish lifecycle:

Plaintext
created → (unread) → mark_as_read → (read)
↘ mark_all_as_read → (read)
↘ destroy → (removed)

The read_at timestamp is nil for unread notifications and set to the current time when the user marks the record read. The two read actions are:

  • :mark_as_read — Update a single notification.
  • :mark_all_as_read — Bulk-update every unread notification belonging to the user (within their current organization). This action broadcasts a single :all_notifications_read PubSub message rather than one per record, so the in-app inbox clears efficiently.

There is no “mark unread” action — once read, a notification stays read. Users who want to revisit a notification can find it in the /notifications index filtered by All or Read.

Key Fields

Field Purpose
title Short headline shown in the bell dropdown and at the top of the notification card
body Longer descriptive text rendered below the title
notification_type One of component_sync, mention, comment, announcement, proposals_pending — drives icon and routing
metadata Contextual map (e.g., link, resource ID, mention source) — used by the UI to deep-link to the source entity
read_at nil until the user marks the notification read; set to a timestamp afterward
user_id The recipient — every notification has exactly one
organization_id Multi-tenant scope; the organization the notification belongs to
created_by_id Optional — the user (if any) whose action triggered the notification
inserted_at When the notification was created — drives the time_ago calculation shown in the UI

Authorization

Notification policies enforce that:

  • Only the recipient (user_id) can read, update, or destroy their own notifications — verified via relates_to_actor_via(:user).
  • Creation is open: any actor can create a notification, because notifications are generated by background workers and platform events, not by direct user action.
  • The bulk :mark_all_as_read generic action filters internally by user_id so a caller can only clear their own inbox.

Real-Time Delivery

The Notification resource is wired to declarative Ash.Notifier.PubSub. On :create and :mark_as_read, the framework publishes a Phoenix.Socket.Broadcast message to notifications:{user_id} via CatalioWeb.Endpoint. The notification settings index LiveView subscribes to that topic at mount time and updates the bell badge and the visible list as messages arrive.

Phoenix.PubSub.broadcast/3 is used for the :all_notifications_read message because it is a generic action (not a create/update/destroy), and Ash notifiers cannot fire from generic actions.

Relationships at a Glance

Related Concept Relationship
Users Each notification has exactly one recipient (user_id) and optionally one trigger (created_by_id)
Organizations Notifications are organization-scoped — multi-tenancy is enforced via the organization_id attribute
Change Proposals Generate proposals_pending notifications when they enter the review queue
Repositories Generate component_sync notifications when a sync completes

Best Practices

Leave the proposals_pending defaults alone unless you are flooded.

The two proposals_pending defaults are the only ones that ship with email enabled. They are intentionally noisier because pending proposals block forward motion on an Initiative — if you mute them, your inbox cleans up but your delivery slows down. If volume becomes a problem, switch email_digest_frequency from "never" to a daily digest rather than muting outright.

Use quiet hours instead of muting categories.

Quiet hours preserve in-app delivery (the bell still updates) while suppressing email outside working hours. Muting a category turns off both channels permanently — that is rarely what you want.

Mark all as read at the end of a working session.

The bell badge is most useful when it represents actual new attention items. Users who let unread notifications accumulate lose the signal-to-noise advantage. The /notifications page has a one-click Mark all as read button for exactly this.

Trust the deep-links in metadata.

Most notifications carry a metadata.link (or equivalent) that routes directly to the source entity — the requirement that was commented on, the proposal awaiting review, the repository that just synced. Following the link is faster than navigating through the entity tree.

Next Steps


Pro Tip: If a teammate is missing notifications they should be receiving, check three things in order: (1) their notification_preferences toggles for that type, (2) whether quiet_hours_enabled is active during the event window, and (3) whether email_digest_frequency has rolled the event into a pending digest rather than sending immediately.

Support

  • Documentation: Continue exploring core concepts to understand what generates each notification type
  • In-App Help: The AI assistant can explain why a specific notification was delivered or suppressed
  • Email: support@catalio.ai
  • Community: Share notification-tuning patterns with other Catalio users