Skip to main content

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.
Last modified on June 24, 2026