Notifications
Notifications
Périmètre : notifications in-app, email transactionnel, SMS transactionnel, templates configurables par organisation, préférences utilisateur, queues persistantes avec retry exponentiel, webhooks providers (Brevo, OVH SMS…).
/account/notifications et /account/preferences.
Les routes API se trouvent sous /api/account/notifications/*,
/api/account/notification-preferences et /api/account/notifications-pause.
Source de cette page : docs/15-domain-notifications.md.EmailTemplate / SmsTemplate), l'écran admin /workspace/admin/settings/notifications
et la route /api/workspace/admin/notifications/templates n'existent pas. Il n'y a
pas non plus de tables EmailQueue / SmsQueue dédiées : l'envoi passe par la table
générique Job (poller processDueJobs).EMAIL_PROVIDER configuré.
La valeur par défaut est console : les emails sont simplement loggés en stdout, ce
qui est sûr en développement et évite tout envoi accidentel.Vocabulaire & entités
| Terme | Entité / Code | Définition | Statut |
|---|---|---|---|
| Notification | Notification | Message in-app (recipientUserId, kind, title, body, targetUrl, payload). | implémenté |
| Canal | NotificationChannel | IN_APP, EMAIL, SMS. | implémenté |
| Type | NotificationKind (enum) | Déclencheur typé (MENTION, INVOICE_ISSUED, LEASE_AFFAIR_STATUS…). | implémenté |
| Préférence | NotificationPreference | 1 ligne par (userId, kind, channel), booléen enabled. | implémenté |
| File d'envoi | Job (générique) | Jobs notification:send-email / notification:send-sms exécutés par le poller. | implémenté |
| Template | EmailTemplate / SmsTemplate | Modèle de contenu paramétrable par organisation. | cible (modèle absent) |
| Catalogue | shared/notifications/catalog.ts | Source de vérité des types et audiences. | cible (fichier absent) |
Le modèle complet est dans
docs/04-data-model.md§12. Letypede notification est un enum PrismaNotificationKind, pas une chaîne libre.
Types de notification (NotificationKind)
NotificationKind (pas un catalogue de codes
hiérarchiques). Valeurs actuelles :MENTION COMMENT_REPLY
INVOICE_ISSUED INVOICE_PAID
OT_ASSIGNED LEASE_AFFAIR_STATUS
GENERIC
CRM_MISSED_CALL CRM_ACTION_OVERDUE CRM_ACTION_ASSIGNED
CRM_PROSPECT_PROMOTED
RATING_REQUESTED RATING_VALIDATED RATING_EXPIRING RATING_NO_GO_ON_DEAL
lease.contract.*, sales.invoice.*, ai.workflow.*…) et le catalogue centralisé
shared/notifications/catalog.ts (templates + audiences par défaut) sont une cible.Écrans & préférences
| Écran | Route | Contenu | Statut |
|---|---|---|---|
| Centre de notifications | /account/notifications | Liste paginée, marquer lu / tout lu. Tap → marque lu + redirige vers targetUrl. | implémenté |
| Icône topbar | (topbar bell icon) | Badge compteur non lus. | implémenté |
| Préférences | /account/preferences | Choix canaux in-app / email / SMS par type ; mise en pause (until). | implémenté |
| Templates admin | /workspace/admin/settings/notifications | Édition des templates par organisation. | cible (non implémenté) |
Modèle de données (points clés)
Notification: créée en in-app (createNotification) puis les jobs d'envoi email/SMS sont enqueués selon les préférences. Champs de tracking :emailMessageId,smsMessageId,emailStatus. Index(recipientUserId, readAt, createdAt).NotificationPreference: clé unique(userId, kind, channel), booléenenabled.- L'envoi différé passe par la table générique
Job(pas de tableEmailQueue/SmsQueue) : jobsnotification:send-emailetnotification:send-sms, retry avec backoff exponentiel géré parjobs.ts.
API
Les routes /api/account/* n'exigent qu'une session authentifiée — chaque
utilisateur ne voit et ne modifie que ses propres notifications et préférences.
Routes implémentées
| Méthode | Route | Permission |
|---|---|---|
GET | /api/account/notifications | authentifié |
POST | /api/account/notifications/mark-read | authentifié |
POST | /api/account/notifications/mark-all-read | authentifié |
GET | /api/account/notifications/unread-count | authentifié |
GET | /api/account/notification-preferences | authentifié |
PATCH | /api/account/notification-preferences | authentifié |
PATCH | /api/account/notifications-pause | authentifié |
POST | /api/webhooks/brevo/track | secret (fail-closed) |
Routes cible (non implémentées)
/api/notifications ni
/api/account/preferences/notifications (les vraies sont sous /api/account/notifications/*
et /api/account/notification-preferences). Pas non plus de route templates ni de
webhook SMS dédié.| Méthode | Route cible | Permission cible |
|---|---|---|
GET / POST | /api/workspace/admin/notifications/templates | admin.settings:read / :update |
POST | /api/webhooks/sms | HMAC |
/api/account/notification-preferencesAuth Met à jour une préférence de notification de l'utilisateur courant (un couple type × canal à la fois).
Corps (JSON)
NotificationKind, ex. INVOICE_ISSUED).IN_APP, EMAIL ou SMS.Requête
curl -s -X PATCH "$API/api/account/notification-preferences" \
-H "Content-Type: application/json" -H "Cookie: $SESSION" \
-d '{"kind":"INVOICE_ISSUED","channel":"SMS","enabled":false}'
Réponse
{ "kind": "INVOICE_ISSUED", "channel": "SMS", "enabled": false }
PATCH /api/account/notifications-pause avec un corps { "until": "<ISO8601>" | null }
(null lève la pause). Le marquage lu prend un corps
{ "notificationIds": ["..."] } sur POST /api/account/notifications/mark-read.Workflows
Émission d'une notification
Le service notification.service.ts expose createNotification(input), appelé par
les autres services lors de mutations sensibles.
await createNotification({
organizationId,
recipientUserId: 'usr_...',
kind: 'LEASE_AFFAIR_STATUS',
title: 'Affaire mise à jour',
body: 'Le contrat LLD-2026-000123 a changé de statut.',
targetUrl: `/workspace/lease-affairs/${affairId}`,
payload: { affairId },
})
Flux interne :
- Crée la
Notificationin-app (canal incompressible). dispatchChannelJobs: charge lesNotificationPreference(userId, kind)et, pour chaque canalenabled:EMAIL→enqueueJob('notification:send-email', { notificationId }).SMS→enqueueJob('notification:send-sms', { notificationId }).
Poller de jobs
Il n'y a pas de cron dédié aux emails : tous les jobs (dont
notification:send-email / notification:send-sms) passent par la table générique
Job, traitée par le poller toutes les 10 s (processDueJobs, jobs.ts).
Pour chaque job dû :
1. Exécute le handler (sendNotificationEmail / sendNotificationSms)
2. Succès → met à jour Notification.emailMessageId / emailStatus = 'sent'
3. Échec + attempt < maxAttempts → reschedule avec backoff exponentiel
4. Échec + attempt >= maxAttempts → FAILED + alertJobFailure
Providers email (EMAIL_PROVIDER)
console (défaut)
Valeur par défaut de EMAIL_PROVIDER. Logs en stdout uniquement — aucun envoi réel.
Garde-fou en dev pour éviter tout trafic accidentel.
smtp
EMAIL_PROVIDER=smtp — MailHog en dev, Scaleway TEM en prod (SMTP).
brevo
EMAIL_PROVIDER=brevo — Brevo HTTP API (BREVO_API_KEY). Tracking via
POST /api/webhooks/brevo/track.
Providers SMS
SMS via server/lib/sms.ts selon la configuration d'environnement.
Tracking livraison (webhook entrant)
POST /api/webhooks/brevo/track: route publique, vérifiée par un token simple en en-têtex-brevo-token(comparé àBREVO_WEBHOOK_SECRET, fail-closed si non configuré). Événementsdelivered/opened(clickedmappé suropened) /bounced→ mise à jour deNotification.emailStatus.
/api/webhooks/sms)
n'existe pas. La vérification du webhook Brevo est un token partagé, pas une
signature HMAC.Règles métier
- IN_APP toujours créée : la
Notificationin-app est le canal incompressible ; les canaux EMAIL/SMS dépendent desNotificationPreference. - Pause utilisateur : un utilisateur peut suspendre ses notifications jusqu'à
une date (
PATCH /api/account/notifications-pause). - RGPD : opt-out clair en footer pour les emails non strictement transactionnels.
Collaboration
Référence technique du domaine Collaboration — commentaires, threads, mentions, notes internes, réactions, inbox et notifications.
Administration
Référence technique du domaine Administration — console super-admin, paramètres applicatifs, gestion des organisations, webhooks sortants, audit log et supervision.