Marsad

Notification Architecture

Persisted notifications, realtime refresh events, completion email, and failure behavior in the web app.

Notification Architecture

The Web App has two separate notification mechanisms:

  • In-app notifications are PostgreSQL Notification records. Pusher events tell an open browser to refetch those records.
  • Email notifications are sent through Nodemailer for selected dataset-access, analysis, and report events.

The Model Server's Redis notifications queue is a third, separate concept: it delivers worker-status callbacks to Web App API routes. Those callbacks may cause email, but they do not create Web App Notification rows automatically.

In-App Notification Flow

Current producers are dataset-access workflows:

EventDurable audienceRealtime eventAdditional delivery
User requests dataset accessRole adminevt::adminNone
Dataset request is approvedCurrently the acting user's IDevt::user.{actingUserId}Approval email to the requester

The second row reflects current implementation, not the desired recipient model: the approval route emails the requester but creates the in-app notification for the acting user. Correcting the recipient requires an application change and a regression test.

The browser subscribes to the notifications Pusher channel and binds only the event name relevant to the current user or administrator. The event is an invalidation signal; the database query, rather than the event payload, supplies the notification list.

Email and Callback Flow

EmailTriggerCurrent failure behavior
Analysis completionA Marsad, Tanbih, or OpenRouter callback derives or receives completed.The mail helper logs delivery errors and does not rethrow them. The callback can return success even when email was not delivered. There is no durable email job or automatic retry.
Report completionA valid report callback has status completed.A mail failure returns HTTP 500 with status email_failed. The report remains generated, but successful notification is not recorded separately. A later callback retry can attempt the email again.
Dataset access grantedAn authenticated request changes a dataset request to approved.The database status is updated before email is sent. If email or the following in-app notification fails, the route returns 500 even though the approval may already be committed; retry can repeat delivery work.

Analysis failure statuses currently do not send the failure form of the analysis email, even though the mail template helper supports it.

Current Failure and Security Boundaries

The following behaviors are important when debugging or changing notifications:

  1. Realtime is best-effort. createNotification currently awaits the Pusher publish before awaiting the Prisma insert. A browser can refetch before the row is committed. If Pusher fails, the pending insert is not awaited and the notification is not durable; if insertion then fails after a successful publish, the browser receives an event with no stored notification.
  2. There is no outbox or delivery ledger. Database state, Pusher delivery, and email delivery are not transactionally coordinated. The system cannot reliably prove that a recipient received an event or email.
  3. The Pusher notification channel is currently public. Event names contain user IDs, but event-name filtering is not an authorization boundary. Do not include private content or credentials in realtime payloads. Moving notifications to authenticated private or presence channels requires authorization in /api/pusher/auth.
  4. Admin notification reads are not role-gated. The admin-list API currently verifies that a user is signed in but does not verify the admin role.
  5. Mark-as-read is not ownership-checked. The read API updates a notification by ID without verifying that the current user belongs to its user or role audience.
  6. Role notifications have one shared read field. Because a notification has one nullable readById, one administrator marking a role notification read makes that same row appear read to every administrator. Per-user read state requires a separate recipient/read-receipt relation.
  7. Callbacks can be duplicated. Model Server callback delivery and manual recovery may repeat a terminal update. Email and notification side effects therefore need an idempotency record before retries can be considered safe.

Operational Checks

When a user reports a missing or duplicate notification, correlate the application entity ID, callback response, notification record ID, Pusher event, and mail-provider result. Check in this order:

  1. Confirm that the originating dataset request or processing job reached the expected state.
  2. For worker-driven completion, confirm delivery through the Model Server notifications queue and the Web App callback response.
  3. Query the Web App Notification row for in-app messages; a Pusher event alone is not durable evidence.
  4. Check that the notification audience and event name identify the intended recipient.
  5. Check Pusher or email-provider errors without logging message contents, addresses, credentials, callback bodies, or uploaded data.
  6. Before replaying a callback or mutation, determine whether the database change already committed and whether replay can send a duplicate email.

See Integration Boundaries for callback ownership and Cross-Repository Contracts for the canonical callback routes and payloads.

On this page