> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cel-eleague.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Messaging notifications emails

# Messaging, notifications, emails et push

Cette page décrit la chaîne messaging/notification vérifiée dans les modules Convex.

Sources principales :

* `convex/messaging/events.ts`
* `convex/messaging/bus.ts`
* `convex/messaging/notifications.ts`
* `convex/email/outbox.ts`
* `convex/email/dispatch.ts`
* `convex/email/resend.ts`
* `convex/social/recipient_groups.ts`

## Modèle d’événements

Le format d’entrée est `MessagingEvent` (validator dans `events.ts`) et les événements sont transformés en plan d’envoi (in-app + emails) par `buildPlan` dans `bus.ts`.

Événements observés (non exhaustif des payloads, mais exhaustif des `kind` listés dans le validator) :

* `auth.password_reset_requested`
* `auth.email_verification_requested`
* `feedback.submission_succeeded`
* `feedback.submission_failed`
* `ea.api.health_alerted`
* `ea.potential_draw_review_required`
* `user.unbanned`
* `user.banned`
* `user.reactivated`
* `user.deleted`
* `person_sanction.activated`
* `person_sanction.ended`
* `admin.bet_bulk_job.updated`
* `admin.bet_bulk_job.partial_failure_alerted`
* `admin.user_premium.updated`
* `bet.status_changed`
* `ea.score.confirmed`
* `ea.score.unavailable`
* `ea.score.ambiguous`
* `ea.score.error`
* `transfer.invitation.*`
* `transfer.join_request.*`
* `transfer.premium_break.executed`
* `transfer.player.left_source_club`
* `totw.published`
* `season_awards.published`
* `match.scheduling.*`
* `match.dispute.reported`
* `match.dispute.resolved`
* `match.report.reported`
* `match.postponement.*`
* `match.result.auto_validated`
* `match.result.diverged`
* `match.result.admin_validated`
* `match.result.contested`
* `league.calendar.deleted`
* `article.published`

### Zone non vérifiée

* Pour les événements en notation étoilée (`transfer.invitation.*`, `match.postponement.*`, `match.scheduling.*`), seules les variantes codées dans `events.ts` et traitées dans `bus.ts` sont fiables ici.

## Bus dispatch

`dispatch` (Mutation interne) :

* construit le plan via `buildPlan`,
* applique `deliverInApp`,
* résout les destinataires email,
* planifie l’action email `internal.email.dispatch.send` via scheduler `runAfter(0, ...)`.

`dispatchAction` (Action interne) :

* même logique de plan,
* écrit in-app via `recordInApp` mutation,
* envoie emails via `internal.email.dispatch.send` en action (pas de wrapper scheduler).

Le résumé de retour contient :

* `inApp.recipients|inserted|duplicates|skipped|notificationIds`
* `email.requested|queued|failed`

### Fanout in-app et règles de sécurité

* `inAppTarget` supportés : `user`, `users`, `group` (voir `inAppTarget` dans `notifications.ts`).
* Cibles de groupe via `resolveRecipientGroupMembers` (`social/recipient_groups.ts`).
* Dedup :
  * dédup par `(userId, dedupKey)`,
  * pour `users`, la key est suffixée par `:<userId>` pour éviter collision inter-destinataires.
* `match.scheduling.*`, `match.postponement.*`, `league.calendar.deleted` ont des cibles explicites imposées (pas de groupe) via `assertSafeInAppTarget`.
* `assertTargetedInAppPlan` exige pour ces événements :
  * exactement une enveloppe in-app,
  * cibles utilisateur explicites cohérentes avec les `recipientUserIds` / `managerUserIds`,
  * cap `MAX_MATCH_TARGETED_IN_APP_RECIPIENTS = 40`.

## Push

* push planifié par `schedulePushDeliveries` en lots,
* `PUSH_DELIVERY_BATCH_SIZE = 200`,
* chaque lot via `scheduler.runAfter(0, dispatchInAppNotifications)`.

## Outbox / retry email

### Queue

`enqueueEmailBatch` (`email/outbox.ts`) :

* déduplique par `dedupKey` (index `by_dedupKey`),
* si existant + status != `FAILED` : skip,
* si existant + `FAILED` : requeue (status `PENDING`, attemptCount reset),
* sinon insert `PENDING`,
* déclenche `processDueEmailsAction` avec limite par défaut `DEFAULT_BATCH_SIZE = 20` (max `MAX_BATCH_SIZE = 50`).

### Traitement batch

`processDueEmailsAction` :

* `claimDueEmails` prend les due rows `PENDING` et passe en `SENDING`,
* envoie via `fetch("https://api.resend.com/emails")`,
* en succès : `markEmailSent`,
* en échec : `markEmailAttemptFailed`,
* si taille du lot atteinte, auto replanifie le batch suivant.

### Backoff/retry

* max tentatives: `MAX_RETRY_ATTEMPTS = 8`,
* délai progressif `BASE_RETRY_DELAY_MS = 60_000` avec jitter + cap `MAX_RETRY_DELAY_MS = 6h`,
* status:
  * réessai jusqu’au max : `PENDING` + `nextAttemptAt`,
  * au max atteint : `FAILED`.

`markEmailAttemptFailed` planifie de nouveau `processDueEmailsAction` jusqu’à épuisement.

## Envoi effectif

`email/dispatch.ts` expose `send` pour la couche unifiée.

`sendEmailBatchCore` (`email/resend.ts`) :

* `Promise.allSettled` par destinataire,
* journalisation monitoring via `recordSendAttempts` vers `email_delivery_logs`,
* retourne `{ requested, queued, failed }`.

Legacy `sendEmailBatch` reste présent mais délègue au même core (`sendEmailBatchCore`) pour compatibilité admin.

### Zone non vérifiée

* Le détail de l’intégration push (provider/service worker / permissions navigateur) n’est pas couvert dans les fichiers cités ici.
