# app/modules/loyalty/tasks/notifications.py """ Async email notification dispatch for loyalty events. All loyalty notification emails are sent asynchronously via this Celery task to avoid blocking request handlers. The task opens its own DB session and calls EmailService.send_template() which handles language resolution, store overrides, Jinja2 rendering, and EmailLog creation. """ import logging from celery import shared_task logger = logging.getLogger(__name__) @shared_task( name="loyalty.send_notification_email", bind=True, max_retries=3, default_retry_delay=60, ) def send_notification_email( self, template_code: str, to_email: str, to_name: str | None = None, variables: dict | None = None, store_id: int | None = None, customer_id: int | None = None, language: str | None = None, ): """ Send a loyalty notification email asynchronously. Args: template_code: Email template code (e.g. 'loyalty_enrollment') to_email: Recipient email address to_name: Recipient display name variables: Template variables dict store_id: Store ID for branding and template overrides customer_id: Customer ID for language resolution language: Explicit language override (otherwise auto-resolved) """ from app.core.database import SessionLocal from app.modules.messaging.services.email_service import EmailService db = SessionLocal() try: email_service = EmailService(db) email_log = email_service.send_template( template_code=template_code, to_email=to_email, to_name=to_name, language=language, variables=variables or {}, store_id=store_id, customer_id=customer_id, ) logger.info( f"Loyalty notification sent: {template_code} to {to_email} " f"(log_id={email_log.id if email_log else 'none'})" ) except Exception as exc: logger.error( f"Loyalty notification failed: {template_code} to {to_email}: {exc}" ) db.rollback() raise self.retry(exc=exc) finally: db.close()