125 lines
3.7 KiB
Python
125 lines
3.7 KiB
Python
# 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/<template_base>.html
|
||
templates/<template_base>.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
|