Communication — End-user view
The part users feel most directly: they receive emails, push messages, and in-app messages. Below are familiar experiences and what sits behind them.
1. Receiving messages through multiple channels
"I receive a booking-confirmation email and also see a notification in the app."
One event sends through multiple channels at the same time: email, push, SMS, in-app. Each recipient / channel is a separate OutgoingMessage — the system knows exactly whether each channel has arrived.
2. Content fills in the right information
"The email uses my name, the right booking code, and the right date."
Message content is based on a template with placeholders such as {{NAME}}, {{BOOKING_CODE}}. At send time, the system fills real data — each recipient gets personalized content without manual copywriting.
3. No duplicate messages
"The system seems to process my order again, but I only receive one email."
Before sending for real, the system claims each message. A message already "sending/sent" → repeat processing exits without calling the provider again. Result: no duplicate emails.
4. In-app inbox
"I open the app and see my notifications; parents see messages grouped for their children."
In-app messages are stored as UserMessage — one row per recipient. Parents see a combined inbox of notifications related to their children.
Inbox operations
- The inbox is read-only: only read, archive, save/unsave; no reply, thread, compose,
New message, or recipient search — content is produced by the producer, there is no endpoint to create messages from the inbox. - Messages are categorized by
UserMessageCategory(Reminder/Notification/Message); filtering is a server-side filter, not a client-side scan. The badge is one authoritative overall unread total; archived messages do not count. The panel prefetches the first page on open and stays mounted during the session. - When a new message arrives, the badge and open Inbox update near real time through SSE; SSE only reports that data changed, while REST still loads the data. Losing SSE does not block Inbox because focus/visibility refresh and polling continue to work.
- Retention: unsaved messages are deleted after 3 months from
CreatedOn(regardless of read/archive); saved messages are exempt; save/unsave is idempotent and does not touch other state. - Authorization by portal: Admin Portal =
EnterpriseUser+ currentEmployee; Enterprise Portal =EnterpriseUser+ all linked Employee profiles; Customer Portal / AimyMe mini-app = current Account; AimyMe Global View = all linked Accounts (no need to pick one). Messages out of scope → not-found (like nonexistent messages). - Privacy: list returns only a bounded safe preview; body streamed once authorized; no permanent Blob URLs exposed; logs/metrics contain no body or recipient data.
Real-world example: A parent reads the
StaffFirstAidExpirationreminder, saves the holiday-campaign message, then the producer retries both. → The inbox does not change: the reminder stays read, the holiday message stays saved, no duplicate rows.
5. Campaigns & recurring reports
"The center sends a holiday notice to all parents." / "I receive a report every week."
- Campaign: bulk send to many recipients at once.
- Report subscription: automated report on a schedule.
Both use MessagingJob + Blob to handle large data, then pass through the same send pipeline.
Notification Settings and traceability
- Admins search notifications under Customers, Staff and Staff (Subscribers Only); list and batch actions can enable delivery and copy to Site or Business Unit contacts.
- When Site and BU copy resolve to the same address, delivery creates one message. Default recipients are managed in their dedicated modal.
- Once the source is resolved, delivery keeps a source-keyed message so support can trace invoice/account/ attendee history while provider status remains authoritative for delivery.
Real-world example: Admin enables BU-contact copy for six notifications. → One BU-scoped batch action applies it; a duplicate Site/BU address is deduplicated before
OutgoingMessageis created.
Report Subscription
Roll Sheet supports scheduled runs and Run Now. Each run stores the report and sends a secure view/download link; attachments are optional and oversized files use link-only delivery. Pausing stops future scheduled runs while a valid subscription can still be run manually.
Real-world example: A report is paused for the weekend but must be sent once. → Use Run Now without resuming the schedule; history records a separate manual run.
Summary for end-users
| Experience | Powered by |
|---|---|
| Receive messages by email/push/SMS/in-app | Multiple OutgoingMessage / UserMessage records by channel |
| Content has the correct name and data | Template + render in Generator |
| No duplicate messages | Claim before send (send claim) |
| In-app inbox, grouped for parents | UserMessage partitioned by recipient |
| Read-only inbox: read/archive/save, 3-month retention | UserMessage + inbox API authorized by portal scope |
| Near-real-time badge/list updates | SSE invalidation + REST revalidation; polling is the fallback |
| Bulk notice / report delivery | Campaign / Report subscription |
Want the mechanism underneath? Read Core concepts.