Osy#betaRun it on Osyrin: osyrin.com

Kits · Service desk

Notifications

Telling a person something needs them — a bell with a live count and a list (read and unread, mark all read), a daily mail digest of what they have not seen or a mention mailed at once (their own choice, in their own language), delivered durably after the commit, deduplicated while unread, and never about a record the recipient may not read under their own security.

Package
Osysharp.Notifications
Install
use Osysharp.Notifications@1;
Group
Service desk
Tests
23 in the kit’s own suite
Builds on
Identity, Mail, Email templates, Clock — the team's time zone and week
Used by
Inbox notifications — who the inbox names, told, Privacy for notifications

Telling a person that something needs them. A bell beside their name with a live count and a list behind it — read and unread, All / Unread, "Mark all read", each row opening where it happened — and, for what they did not see in the app, a mail: a daily digest, or a mention at once. Each person chooses; each person reads it in their own language.

Use case. A support team where a ticket is offered to Rahul while he is on another screen; a colleague writes @Rahul Kapoor can you check the refund? in a note; the customer writes again on a ticket he follows; a promise on his ticket is at risk. Each reaches his bell at once, and what he has not read by morning is in one mail. The same kit tells an approver that a purchase waits for them, or a privacy officer that a request is due — it never knows what a ticket is.

osy

use Osysharp.Notifications@1;

policy ReceivesNotifications => IsAgent;                        // who has a bell
partial entity Notification { security { allow create when IsAgent; } }   // who may tell from their own request
app.Notifications = new NotificationsSetup { AppName = "Harbour" };

[Event("Customer wrote again")]                                 // what happened — your own event, or any kit's
public sealed class CustomerWrote {
  public Ticket Ticket;
  public override string Sentence() { return $"{Ticket.Customer.DisplayName} wrote again."; }   // said when READ
}

[Title($"#{Number} · {Title}")] entity Ticket { … }                       // what a ticket is called
[Route("/w/{view}/t/{number}")] [Opens(Ticket, view = "mine")] component TicketPage(…) { … }   // where it opens

Notify(rahul, new CustomerWrote { Ticket = ticket });           // about the record the event is about
[On] void TellReviewers(ArticleSentForReview e) { NotifyAll(reviewers, e); }   // a kit's event, told as it is
Mention(rahul, ticket, by: Session.CurrentUser);                // a mention is the kit's own
NotificationBell();                                             // in the shell's actions

What it does

Durable deliverya notification is written in the caller's commit and delivered after it by a workflow (retried on a throw) — nobody hears about something rolled back
Only what they may readthe record's own security, as the recipient (Records.MayRead) — asked at delivery; the bell reads each record as its reader and the digest asks once per person (Records.Readable), so a record they lost access to leaves the bell and the mail
About any recorda notification's About is an Entity: what it is called and where it opens are the record's own [Title] and the page that [Opens] it — the kit asks nothing of the app about them — and it goes with its record ([OnDelete(Remove)])
One unread per thinga second notification with the same key joins the unread one ("2 times"), held by the database
The bellNotificationBell() — count in its accessible name, a panel wide, a sheet on a phone, empty and caught-up states designed
Mailper person, on their own settings page: mentions at once (after a short wait, only if still unread) and the rest daily; daily only; or nothing — always in their language
The event, not its sentencea notification keeps the event itself and says it with the event's own Sentence() when it is read — in the reader's language, with the event's records read as they may read them; an event that says nothing of its own says its label
Retentionread ones after 30 days, unread after 180, never-shown after a day — an hourly sweep
Privacythe event it keeps is Personal.Content; with Osysharp.Privacy, Osysharp.Privacy.Notifications — which arrives by itself — counts, copies, restricts and erases a person's notifications and preferences

Screens

The bell open, wide
On a phone
A mention, offered as you type
The daily digest

Its tests

The kit's test app is a small studio's shared drafts, so the kit is shown working outside a helpdesk; the Harbour helpdesk uses it end to end. Who may read a notification is osy docs stdlib-records.

Get it

Add the kit to your app’s manifest:

osy

app MyApp {
  use Osysharp.Notifications@1;
}

Then run osy lock: it downloads the release, verifies it by its content address and pins that version in osyrin.lock. osy kits Osysharp.Notifications shows what the kit is for, from inside your app.

Reference: osy docs ui-notifications-kit.