Source code for ox.apps.mails.send

from email.message import EmailMessage
from functools import cached_property
import logging
import re
import smtplib
from typing import Any, Iterable

from django.template import Template, Context
from django.utils.html import strip_tags

from ox.apps.files.models import File
from ox.apps.content.renderers import Renderer

from .models import BaseMail, MailAccount


logger = logging.getLogger()


[docs] class MailSend: """ This class handles sending an email from a model subclassing :py:class:`~.models.BaseMail`. """ mail: BaseMail """ Outgoing mail """ renderer: Renderer """ Renderer used for user content. .. important:: The variables must match context provided by this class and the method :py:meth:`.models.BaseMail.get_recipients` of :py:attr:`mail` instance. """ account: MailAccount """ Email account used to send the message. Defaults to mail's one. """ def __init__(self, mail: BaseMail, renderer: Renderer, account: MailAccount | None = None): self.mail = mail self.renderer = renderer self.account = account or mail.account
[docs] @cached_property def templates(self) -> dict[str, Template]: """Dict of templates instance for each renderable part of the message. The keys will be: ``subject``, ``header``, ``content``, ``signature``, ``footer``. """ subscription = self.account.mail_subscription_footer if subscription: subscription = f"{{% if is_subscription %}}{subscription}{{% endif %}}" return { "subject": self.renderer.compile(self.mail.get_subject()), "content": self.renderer.compile( "<br><br>".join( v for v in ( self.mail.get_content(), self.account.mail_signature, subscription, ) if v ) ), }
[docs] def send(self, context: dict[str, Any] = {}): """ Send the mail to all mail's recipients (:py:meth:`.models.BaseMail.get_recipients`) through SMTP. Update the mail state once sent. :param context: extra context to pass down to content's Template """ if self.account.smtp_encryption == self.account.Encryption.SSL: cls = smtplib.SMTP_SSL else: cls = smtplib.SMTP with cls(self.account.smtp_host, self.account.smtp_port) as smtp: if self.account.smtp_encryption == self.account.Encryption.TLS: smtp.starttls() smtp.login(self.account.smtp_username, self.account.smtp_password) self.mail.state = BaseMail.State.SENDING self.mail.save(update_fields=["state"]) logger.info(f"Start send mail with id {self.mail.id}") recipients = self.mail.get_recipients() for recipient, extra_context in recipients: self.send_mail(smtp, recipient, {**context, **extra_context}) self.mail.state = BaseMail.State.SENT self.mail.save(update_fields=["state"])
[docs] def send_mail(self, smtp: smtplib.SMTP, recipient: str, context: dict[str, Any]): """Send mail to provided recipient. :param smtp: logged in smtp instance. :param recipient: target email. :param context: extra context data. """ context = self.mail.get_context(recipient=recipient, **context) message = self.get_message(recipient, context) logger.info(f"Send mail {self.mail.id} to {recipient}") smtp.send_message(message)
[docs] def get_message(self, recipient: str, context: Context) -> EmailMessage: """Return EmailMessage to send to provided recipient with rendered content and subject. :param contact: target contact :param context: extra context """ content = self.get_content(context) print("=" * 80, "\n", content, "=" * 80, "\n", context) content_text = self._strip_re_1.sub(" ", strip_tags(content)) content_text = self._strip_re_2.sub("\n", content_text).strip() msg = EmailMessage() msg["To"] = recipient msg["From"] = self.account.smtp_username msg["Subject"] = self.templates["subject"].render(context) msg.set_content(content_text) msg.add_alternative(content, subtype="html") self.add_attachments(msg, self.mail.attachments.all()) return msg
[docs] def get_content(self, context: Context) -> str: """Render content to HTML and return.""" return self.templates["content"].render(context)
_strip_re_1 = re.compile("[ \t]+") _strip_re_2 = re.compile("\n ")
[docs] def add_attachments(self, message: EmailMessage, files: Iterable[File]): """Add attachments to mail.""" for file in files: self.add_attachment(message, file)
[docs] def add_attachment(self, message: EmailMessage, file: File): """Add attachments to mail.""" with file.file.open() as f: file_data = f.read() mime = file.mime_type.split("/") message.add_attachment(file_data, maintype=mime[0], subtype=mime[1], filename=file.name)