Skip to content

[FEATURE] add notification mail event#886

Open
acapai wants to merge 10 commits intoClub-Alpin-Annecy:masterfrom
acapai:feat/add-notification-mail-event
Open

[FEATURE] add notification mail event#886
acapai wants to merge 10 commits intoClub-Alpin-Annecy:masterfrom
acapai:feat/add-notification-mail-event

Conversation

@acapai
Copy link
Copy Markdown

@acapai acapai commented Mar 30, 2026

Système de notifications digest pour les nouvelles collectives


Ce que propose cette branche

Cette PR introduit un système de notifications email groupées (digest) pour alerter les membres de la création de nouvelles collectives. Les utilisateurs s'abonnent via leur profil, choisissent leur fréquence, et reçoivent un email synthétique (quotidien ou hebdomadaire) listant uniquement les collectives qui correspondent à leurs filtres.

Les notifications de supervision vers les boîtes activité (comportement existant) sont préservées et non modifiées.


Fonctionnalités livrées

Fonctionnalité Détail
Abonnement opt-in Case à cocher sur le profil, désactivée par défaut
Digest quotidien ou hebdomadaire Configurable par l'utilisateur, envoi à 8h (lundi pour le weekly)
Filtrage fin Par type d'activité, type d'événement, jours de semaine
Vérification de licence à l'envoi Un abonné sans licence valide au moment du digest ne reçoit rien
Tracking des clics Chaque lien est signé par token — un clic met à jour la date d'engagement
Désabonnement one-click Lien en bas de chaque email, sans nécessiter d'être connecté
Gestion de l'inactivité Après 365 jours sans clic : préavis par email. Après 14 jours sans réaction : désabonnement automatique
CLI de maintenance flask send-new-event-digests et flask maintain-new-event-notifications
Migration Alembic 2f4d0f7a9c3b — 6 colonnes sur users, 2 tables de jonction, 1 table queue
Tests d'intégration Digest, inactivité, clic, désabonnement, préférences profil

Architecture — schéma de flux

Création d'une collective
        ↓
email_templates.py → send_new_event_notification()
        ├── → Superviseurs activité (immédiat, inchangé)
        └── → queue_new_event_notification() → table new_event_notifications

[CRON 8h quotidien]
flask send-new-event-digests
        ↓
Pour chaque abonné dû :
  - Filtre par licence, type d'événement, activité, jours
  - Génère email avec liens signés
  - Mémorise last_sent_at

[CRON hebdo]
flask maintain-new-event-notifications
        ↓
  - 365j sans clic → préavis
  - 365j + 14j → désabonnement automatique

Travail de comparaison avec la PR #877

Les deux PR convergent vers le même objectif : permettre aux membres d'être informés des nouvelles collectives correspondant à leurs intérêts, avec abonnement via le profil, filtrage par activité, et lien de désabonnement dans les emails.

Cett PR répond à ces mêmes besoins et apporte en plus :

  • Envoi asynchrone par digest — les emails ne sont pas envoyés dans la requête web mais mis en queue, puis expédiés en lot par un cron. Cela évite la contention SMTP et les timeouts lors de la création d'une collective.

  • Désabonnement one-click sans login — les routes /click/ et /unsubscribe/ sont explicitement exemptées d'authentification. Le token signé suffit. Dans l'autre branche, la route reste derrière @valid_user(), ce qui impose en pratique d'être connecté pour se désabonner depuis un email.

  • Pas de doublon d'envoi possible — un watermark last_sent_at par utilisateur et une purge de la queue garantissent qu'une même collective n'est jamais envoyée deux fois.

  • Qualification des destinataires à l'envoi — licence valide, compte actif et filtres métier sont vérifiés au moment de l'envoi du digest, pas seulement à la souscription.

  • Politique d'inactivité RGPD — après 365 jours sans clic sur un lien de notification, un préavis est envoyé. Sans réaction sous 14 jours, l'abonnement est automatiquement coupé.

  • Filtrage fin par jour de semaine — en plus des types d'activités et d'événements, l'utilisateur peut restreindre ses notifications aux collectives débutant certains jours de la semaine.

  • Outillage opérationnel complet — deux commandes CLI dédiées (flask send-new-event-digests, flask maintain-new-event-notifications), un exemple de fichier cron, et une documentation Sphinx.

  • Couverture de tests d'intégration — queue, scheduling, tokens publics, rendering HTML, purge, inactivité, préférences profil. L'autre branche couvre uniquement les méthodes modèles en tests unitaires, sans couverture des routes de notification ni de l'envoi email.


Fichiers modifiés / créés

Fichier Nature
collectives/new_event_notifications.py Nouveau — queue, digests, inactivité, CLI
collectives/models/new_event_notification.py Nouveau — modèle table queue
collectives/models/user/model.py Modifié — 6 colonnes + 2 relations
collectives/models/user/misc.py Modifié — méthodes inactivité, slot, filtrage
collectives/models/user/enum.py Modifié — NotificationFrequency
collectives/routes/profile.py Modifié — routes préférences, click, unsubscribe
collectives/forms/user.py Modifié — NotificationPreferencesForm
collectives/templates/profile/notification_preferences.html Nouveau — page préférences
collectives/email_templates.py Modifié — hook vers queue
collectives/__init__.py Modifié — enregistrement serializer + CLI
migrations/versions/2f4d0f7a9c3b_add_user_new_event_notifications.py Nouveau — migration
tests/test_new_event_notifications.py Nouveau — tests dédiés
doc/cron/collectives-notifications.cron Nouveau — exemple planification
doc/source/notifications_digest.rst Nouveau — documentation opérationnelle

Points d'attention pour la review

  • Les routes /profile/user/notifications/click/<token> et /profile/user/notifications/unsubscribe/<token> sont volontairement exemptes d'authentification — elles sont accessibles depuis un email. La sécurité repose sur le token signé itsdangerous.URLSafeTimedSerializer.
  • L'heure d'envoi (8h00) est hardcodée dans collectives/models/user/misc.py — non configurable via app.config ni Configuration.
  • Le cron doit être configuré côté serveur — voir doc/cron/collectives-notifications.cron.

Refs #877
Closes #873

acapai added 10 commits March 31, 2026 17:53
Add the first functional part of the new collective-notification system.

Users can now configure notification preferences from their profile:
- enable or disable notifications
- choose a daily or weekly digest frequency
- filter by event types
- filter by activity types
- restrict notifications to selected weekdays

The commit adds the backing user fields, enum, ORM relations, form, profile route, template entry point and migrations, plus test coverage for updating preferences.

Reviewed-by: andriacap
…ribers immediately

Change the new collective notification flow so subscriber notifications are no longer sent immediately at event creation time.

This commit introduces a dedicated new_event_notifications persistence table and its ORM model to store newly created events that should later appear in notification digests.

The existing send_new_event_notification() flow is updated to:
- enqueue the created event for later subscriber digest processing
- keep the immediate operational email to configured activity addresses
- stop sending direct subscriber emails at creation time

A focused event test is added to verify the new behavior:
- a matching subscribed user no longer receives an immediate email
- a non-matching subscribed user also receives nothing immediately
- the event is persisted in the notification queue
- the operational activity email is still sent

Reviewed-by: andriacap
…enance

Add the operational part of the new collective notification system on top of the previously introduced preference and queue layers.

This commit introduces:
- digest selection logic that resolves pending queued events per user
- filtering of digest recipients at send time, including an active-license check
- plain-text digest message generation with event links and unsubscribe instructions
- one-click public routes to track digest link clicks and disable notifications without authentication
- inactivity handling that warns users after one year without email link usage, then disables the subscription after a grace period
- Flask CLI commands to send digests and maintain inactive subscriptions
- serializer registration in the application factory for signed notification links
- cleanup of notification preferences when a user account is anonymized/deleted

Focused tests are added for:
- skipping expired licenses at digest send time
- click tracking and one-click unsubscribe links
- warning then disabling inactive notification subscriptions

Reviewed-by: andriacap
Add documentation for the new digest-based collective notification system and the scheduled jobs required to operate it in production.

This commit documents:
- the new notification behavior introduced in this branch
- the difference between queued user digests and immediate operational activity emails
- the two Flask CLI commands used to send digests and maintain inactive subscriptions
- the inactivity policy applied to stale notification subscriptions
- the one-click unsubscribe behavior available from notification emails
- a sample cron file showing how to schedule the maintenance commands
- deployment guidance and a link from the main documentation index

Reviewed-by: andriacap
Improve the digest notification flow for production usage and larger recipient volumes.

This commit hardens the public mail actions by switching notification link signing to timed tokens and enforcing an explicit expiration window for:
- click tracking links
- one-click unsubscribe links

This prevents archived or forwarded mail links from remaining valid indefinitely.

It also reduces the database cost of digest delivery:
- due subscribers are resolved once per batch
- candidate queued notifications are loaded once and reused across users
- per-user matching is performed from a shared in-memory candidate set
- obsolete queue rows are purged once they are no longer needed by active subscribers

This matters because the previous implementation reloaded the queued notification set for each subscribed user, which does not scale well when the recipient population grows.

The commit also makes the transaction boundary explicit for event creation:
- queue_new_event_notification() now only adds the queue entry to the SQLAlchemy session
- manage_event() commits after send_new_event_notification(event), so persistence remains guaranteed without hiding a commit inside the helper

Additional tests cover:
- expiration of public notification links
- purge of delivered queued notifications
- existing digest behaviors remain validateddocker compose -f docker-compose.dev.yml up -d --build
- add an inline, collapsible notification preferences section on the user profile page
- improve the notification summary header with compact metadata, icons, and horizontal overflow handling
- route admin user creation through a dedicated form variant to avoid exposing raw notification internals
- add user-facing French labels for notification-related fields in admin edit views
- adjust notification digest scheduling logic for fixed daily and weekly send slots
- update profile notification handling and related redirects
- extend tests to cover the new scheduling and notification flows

Reviewed-by: andriacap
- update digest sending to persist sent state only after SMTP success
- add mail headers support for List-Unsubscribe on notification emails
- fix HTML digest links so href values stay valid
- replace unsubscribe-on-GET with confirmation GET + state-changing POST
- add a dedicated RFC 8058 one-click unsubscribe endpoint
- add confirmation template for unsubscribe flow
- extend notification tests for token expiry, safe unsubscribe, one-click flow and SMTP failure handling
- update fake SMTP to match the new mailer contract in tests

Reviewed-by: andriacap
- switch notification filters to checkbox-based multi-selects
- add dedicated notification preferences page and reusable form partial
- add dropdown multi-selects with tags, quick unselect and clear-all actions
- prevent impossible event-type/activity combinations when saving preferences
- improve notification summary layout in profile
- add profile tests for rendering and preference normalization

Reviewed-by: andriacap
Reviewed-by: andriacap
@acapai acapai force-pushed the feat/add-notification-mail-event branch from 775d46d to 766366a Compare March 31, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Proposition de fonctionnalités permettant de s'abonner à une notification par mail pour de types d'activités

1 participant