# backend/common/emails.py import logging from typing import Iterable, Mapping, Optional from django.conf import settings from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.utils.html import strip_tags logger = logging.getLogger(__name__) def send_templated_mail( subject: str, to: Iterable[str], template_base: str, context: Mapping, from_email: Optional[str] = None, attachments: Optional[list[tuple[str, bytes, str]]] = None, ) -> int: """ Отправляет письмо в HTML + plain текстах по шаблонам: templates/.html templates/.txt (опционально) attachments: список троек (name, content_bytes, mimetype) """ to_list = list(to) logger.info( "Email delivery started", extra={ "event": "email.delivery.started", "template": template_base, "recipient_count": len(to_list), "attachment_count": len(attachments or []), }, ) try: html_body = render_to_string(f"{template_base}.html", context) logger.debug( "Email HTML template rendered", extra={ "event": "email.template.rendered", "template": template_base, "body_bytes": len(html_body.encode()), }, ) except Exception: logger.exception( "Email HTML template rendering failed", extra={ "event": "email.template.failed", "template": template_base, "format": "html", }, ) raise # .txt файл опционален — если его нет, берём strip_tags от html try: text_body = render_to_string(f"{template_base}.txt", context) logger.debug( "Email text template rendered", extra={ "event": "email.template.rendered", "template": template_base, "format": "text", }, ) except Exception: text_body = strip_tags(html_body) logger.debug( "Email text body generated from HTML", extra={ "event": "email.template.fallback", "template": template_base, "format": "text", }, ) email = EmailMultiAlternatives( subject=subject, body=text_body, from_email=from_email or settings.DEFAULT_FROM_EMAIL, to=to_list, ) email.attach_alternative(html_body, "text/html") if attachments: for name, data, mimetype in attachments: email.attach(name, data, mimetype) logger.debug( "Email attachment added", extra={ "event": "email.attachment.added", "attachment_bytes": len(data), "mime_type": mimetype, }, ) try: result = email.send(fail_silently=False) logger.info( "Email delivery completed", extra={ "event": "email.delivery.completed", "template": template_base, "recipient_count": len(to_list), "sent_count": result, }, ) return result except Exception as error: logger.exception( "Email delivery failed", extra={ "event": "email.delivery.failed", "template": template_base, "recipient_count": len(to_list), "error_type": type(error).__name__, }, ) raise