Milestone 8: Notifications and snippets
Purpose: Agents receive in-app and email notifications for assignments, replies, mentions, SLA, and system events; notification preferences; snippets in reply composer.
Exit state: In-app notification panel and unread count; email notifications via Postmark (react-email); preferences; snippets CRUD and picker in composer.
Spec reference: §2.10 Notification System, §2.10b Email Templates, §5.9 Notification, §5.15 Snippet, §5.20 NotificationPreference, §6.10 Notifications panel, §6.6 SnippetPicker.
Prerequisites: M07 (Tickets — assignment and reply exist).
8.1 Notifications data and API
Tasks
-
Schema
- Already in M01: notifications (tenant_id, user_id, type, payload jsonb, read_at, created_at); notification_preferences (user_id, tenant_id, event_type, email_enabled, in_app_enabled). Section 5.9, 5.20.
-
tRPC
- Create notification (internal: worker or tRPC): insert row.
- List notifications: by user_id + tenant_id; filter unread (read_at null); sort created_at desc; limit for panel (e.g. 20).
- Mark read: single id or "mark all read" (update read_at for user+tenant).
- Unread count: count where user_id + tenant_id and read_at null.
-
Fan-out from events
- When ticket assigned: create notification for assignee (type ticket_assigned); trigger email if preference allows. Section 2.10.
- When new inbound message on ticket: if ticket has assignee, create notification for assignee (type ticket_reply); trigger email.
- When @mention in internal note: parse mention nodes; create notification for mentioned user(s); trigger email. Section 2.10 (mention).
- When SLA breach/near-breach: create notification for assignee and admins (M11 will trigger); trigger email.
- When inbox error (OAuth failure): create notification for tenant admins; trigger email (inbox-error template). Section 2.10.
- Trial/billing: day-25, day-30, day-37 emails to admins (M10); can create in-app notification too.
- Implement helper:
createNotification({ tenantId, userId, type, payload })and optionallysendEmail(type, recipient, data). Call from worker and tRPC where events occur.
Acceptance criteria
| Criterion | Status |
|---|---|
| Notifications created on assignment, reply, mention, inbox error; list and unread count correct. | Pending |
| Mark read (single and all) works; list shows in panel (M08.3). | Pending |
| Creating a notification publishes to Redis so SSE clients receive notification.new; panel and badge update in real time (Section 4.11). | Pending |
8.2 Transactional email (Postmark + react-email)
Tasks
-
react-email templates
- Create components for: invite, password-reset, ticket-assigned, ticket-reply, mention, sla-breach, inbox-error, trial-ending, trial-expired, trial-locked, payment-failed, subscription-cancelled. Section 2.10b. Each accepts props (e.g. org_name, ticket_url); render to HTML + plain text.
-
Rendering and send
- In worker or shared package: import template, render with data, POST to Postmark API (From, To, Subject, HtmlBody, TextBody). Use POSTMARK_API_KEY and POSTMARK_FROM_ADDRESS from config.
- Invite email: send on invite create (M09); password-reset on request (M02). Ticket-assigned, ticket-reply, mention: send when creating notification if user preference email_enabled. Sla-breach: from SLA cron (M11). Inbox-error: on OAuth failure in worker. Trial/billing: from trial cron (M10).
-
Base layout
- Shared BaseLayout for all templates: logo, footer with legal/unsubscribe link, consistent styling. Section 2.10b.
Acceptance criteria
| Criterion | Status |
|---|---|
| All templates render without error; send via Postmark succeeds in dev/staging. | Pending |
| Invite and password-reset emails sent from M02/M09; ticket-assigned and ticket-reply sent when notification created and preference allows. | Pending |
8.3 In-app notification panel
Tasks
-
Bell icon and badge
- In app shell (M03): notification bell in top bar; badge with unread count (from tRPC unread count). Poll every 30s or use same refetchInterval as list. Section 4.11.
-
Panel (Sheet or Popover)
- On click: open panel with list of notifications (scrollable). Each item: icon by type, text (e.g. "Ticket #42 assigned to you"), link to ticket or page, relative time, unread dot. "Mark all as read" button. Optional "View all" link to
/app/[slug]/notifications(full page) if implemented. - Clicking notification: navigate to ticket (or relevant page); mark that notification read.
- On click: open panel with list of notifications (scrollable). Each item: icon by type, text (e.g. "Ticket #42 assigned to you"), link to ticket or page, relative time, unread dot. "Mark all as read" button. Optional "View all" link to
Acceptance criteria
| Criterion | Status |
|---|---|
| Unread count visible on bell; panel opens and shows list; mark read and mark all read work; click navigates and marks read. | Pending |
| Unread count and panel list update in real time via SSE (notification.new); optional polling fallback when SSE unavailable. | Pending |
8.4 Notification preferences
Tasks
-
Schema and defaults
- notification_preferences: (user_id, tenant_id, event_type, email_enabled, in_app_enabled). Unique (user_id, tenant_id, event_type). If no row, default both true. Section 5.20.
-
tRPC
- Get preferences for user+tenant (list of event_type with email_enabled, in_app_enabled). Update preference (upsert row).
-
UI
- Profile or Settings: "Notifications" section; matrix or list of event types (ticket assigned, reply received, mention, SLA breach, inbox error, etc.) with toggles for Email and In-app. Save on change or Save button.
Acceptance criteria
| Criterion | Status |
|---|---|
| User can view and update notification preferences per event type; defaults apply when no row. | Pending |
| Sending respects preferences (email only if email_enabled; in-app only if in_app_enabled). | Pending |
8.5 Snippets (canned responses)
Tasks
-
CRUD
- tRPC: list snippets (tenant); create (name, body); update; delete. Section 5.15. Snippets table tenant-scoped; body is text or HTML.
-
Settings → Snippets
- List snippets: name, body preview, edit/delete. "New snippet" modal: name input, Tiptap or textarea for body; save. Section 6.10. Role: Agent + Admin can manage per spec.
-
Picker in composer
- In reply composer (M07): "Snippets" button or dropdown; open popover with list of snippet names; on select insert body into editor (replace or append); user can edit before send. Section 2.4, 6.6.
Acceptance criteria
| Criterion | Status |
|---|---|
| Admin/Agent can create and edit snippets in Settings; snippets appear in composer picker; insert and edit before send works. | Pending |
| Snippets are tenant-scoped. | Pending |
Milestone 8 sign-off
| Criterion | Status |
|---|---|
| All tasks in 8.1–8.5 complete. | Pending |
| All acceptance criteria met. | Pending |
| Notifications (in-app + email) and snippets fully functional. | Pending |
| E2E: Notifications and snippets flows (see INDEX — Testing strategy). | Pending |
| Ready for M09 (Invites and team). | Pending |