Add async email notifications for 5 loyalty lifecycle events, using
the existing messaging module infrastructure (EmailService, EmailLog,
store template overrides).
- New seed script: scripts/seed/seed_email_templates_loyalty.py
Seeds 5 templates × 4 locales (en/fr/de/lb) = 20 rows. Idempotent.
Renamed existing script to seed_email_templates_core.py.
- Celery task: loyalty.send_notification_email — async dispatch with
3 retries and 60s backoff. Opens own DB session.
- Notification service: LoyaltyNotificationService with 5 methods
that resolve customer/card/program into template variables and
enqueue via Celery (never blocks request handlers).
- Enrollment: sends loyalty_enrollment + loyalty_welcome_bonus (if
bonus > 0) after card creation commit.
- Stamps: sends loyalty_reward_ready when stamp target reached.
- Expiration task: sends loyalty_points_expiring 14 days before expiry
(tracked via new last_expiration_warning_at column to prevent dupes),
and loyalty_points_expired after points are zeroed.
- Migration loyalty_005: adds last_expiration_warning_at to cards.
- 8 new unit tests for notification service dispatch.
- Fix: rate limiter autouse fixture in integration tests to prevent
state bleed between tests.
Templates: loyalty_enrollment, loyalty_welcome_bonus,
loyalty_points_expiring, loyalty_points_expired, loyalty_reward_ready.
All support store-level overrides via the existing email template UI.
Birthday + re-engagement emails deferred to future marketing module
(cross-platform: OMS, loyalty, hosting).
342 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>