---
name: notification
description: |
  Integrates SmartStack notification system into development.
  Use this skill when:
  - User wants to send notifications from a feature
  - User mentions "notify", "alert", "notification", "in-app"
  - Creating a module that requires user notifications
  - SignalR integration for real-time
  Types: In-App, Email (via Workflow), Push (future)
---

# Skill Notification SmartStack

> **Architecture:** In-App (DB + SignalR) + Email (via Workflows)

**Reference:** [_shared.md](../_shared.md) for common services

## WHEN THIS SKILL ACTIVATES

| Trigger | Example |
|---------|---------|
| Explicit request | "Notify the user when a ticket is created" |
| Event-driven | "When SLA expires, alert the user" |
| Real-time | "Display new notifications in real-time" |
| Keywords | "notify", "alert", "SignalR", "real-time" |

## FLOW

```
EVENT → INotificationService.SendNotificationAsync()
    ↓
DB (store) + SignalR Hub (real-time)
    ↓
Frontend: useSignalR() → NotificationBell
```

## NOTIFICATION TYPES

| Type | Usage |
|------|-------|
| `TicketCreated/Assigned/StatusChanged` | Support |
| `RoleAssigned/Removed/PermissionsChanged` | Admin |
| `SystemAnnouncement` | Global |
| `SlaWarning/ResponseBreached/ResolutionBreached` | SLA |

### Add a New Type
```csharp
// 1. Domain/Support/Enums/NotificationType.cs
public enum NotificationType { ..., $NEW_TYPE = XX }

// 2. Usage
await _notificationService.SendNotificationAsync(userId, NotificationType.$NEW_TYPE, title, message,
    relatedEntityType: "Entity", relatedEntityId: id, actionUrl: "/path");
```

## WORKFLOW INTEGRATION

### 1. Identify the Need
| Question | Action |
|----------|--------|
| Real-time? | → In-App + SignalR |
| Email? | → Workflow + EmailTemplate |
| Related entity? | → relatedEntityType/Id |
| Navigation? | → actionUrl |

### 2. Send Notification
```csharp
// Simple
await _notificationService.SendNotificationAsync(userId, NotificationType.TicketCreated,
    "New ticket", $"Ticket #{ticket.Number} created", ct);

// With related entity
await _notificationService.SendNotificationAsync(userId, NotificationType.TicketAssigned,
    "Ticket assigned", message,
    relatedEntityType: "Ticket", relatedEntityId: ticket.Id,
    actionUrl: $"/support/tickets/{ticket.Id}", ct);

// Multi-user
await _notificationService.SendNotificationsAsync(userIds, type, title, message, ct);

// By role
await _notificationService.SendNotificationToRoleAsync("Admin", type, title, message, ct);
```

### 3. User Preferences
```csharp
var (shouldEmail, shouldInApp, shouldPush) = await _notificationService.ShouldNotifyAsync(userId, type);
if (shouldInApp) await _notificationService.SendNotificationAsync(...);
if (shouldEmail) await _workflowService.TriggerAsync("ticket.created", variables);
```

## FRONTEND

### Hook useSignalR
```typescript
useSignalR({
  onNotification: (notification) => { toast.info(notification.title); },
  onUnreadCountUpdate: (count) => { setUnreadCount(count); },
  onPermissionsChanged: () => { queryClient.invalidateQueries(['permissions']); }
});
```

### API Notifications
```typescript
notificationsApi.getUnread(limit)
notificationsApi.getAll(page, pageSize, isRead?)
notificationsApi.getUnreadCount()
notificationsApi.markAsRead(id) / markAllAsRead()
notificationsApi.delete(id) / deleteAll()
```

### NotificationBell Component
```tsx
<NotificationBell />  // Manages useSignalR, badge unreadCount, dropdown
```

## ADVANCED PATTERNS

### SLA Warning (Hangfire Job)
```csharp
var tickets = await _context.Tickets
    .Where(t => t.SlaDeadline <= DateTime.UtcNow.AddMinutes(30) && !t.SlaWarningNotificationSent)
    .ToListAsync();
foreach (var ticket in tickets) {
    await _notificationService.SendNotificationAsync(..., NotificationType.SlaWarning, ...);
    ticket.SlaWarningNotificationSent = true;
}
```

### Grouped Notification
```csharp
if (entities.Count == 1) await SendSingle(...);
else await SendBatch($"{entities.Count} new elements", ...);
```

## CHECKLIST

```
□ Notification type identified (existing or new enum)
□ INotificationService injected
□ Send with: userId, type, title, message, relatedEntityType?, relatedEntityId?, actionUrl?
□ Frontend: useSignalR configured, toast/UI on receipt
□ If email: workflow configured
```

## ABSOLUTE RULES

| DO | DON'T |
|----|-------|
| INotificationService | Direct DB access |
| NotificationType enum | Hardcoded types |
| relatedEntityType/Id + actionUrl | Notifications without context |
| Respect user preferences | Spam notifications |
| CancellationToken | Hardcoded messages (use i18n) |

## KEY FILES

| File | Role |
|------|------|
| `Domain/Support/Notification.cs` | Entity |
| `Domain/Support/Enums/NotificationType.cs` | Types |
| `Application/Common/Interfaces/INotificationService.cs` | Interface |
| `Infrastructure/Services/SignalR/NotificationHubService.cs` | Real-time |
| `web/src/hooks/useSignalR.ts` | Frontend hook |
| `web/src/components/notifications/NotificationBell.tsx` | UI |

<success_criteria>
- INotificationService integration wired with correct NotificationType
- relatedEntityType/Id and actionUrl set for navigation context
- SignalR real-time delivery working via NotificationHubService
- User preference filtering respected
</success_criteria>
