From 10e8dc4e96ee0f18120fb5d8959e48a44ffe3bef Mon Sep 17 00:00:00 2001 From: Noloquideus Date: Sun, 19 Apr 2026 10:53:18 +0300 Subject: [PATCH] feat: update style of mail --- src/application/contracts/i_sender.py | 10 ++- src/infrastructure/mail/assets.py | 8 +- src/infrastructure/mail/sender.py | 74 +++++++++++++++--- .../mail/templates/email_code.html | 4 +- .../mail/templates/static/exa-logo.png | Bin 9519 -> 64210 bytes src/presentation/messaging/code.py | 8 +- 6 files changed, 82 insertions(+), 22 deletions(-) diff --git a/src/application/contracts/i_sender.py b/src/application/contracts/i_sender.py index 214591d..f2365bd 100644 --- a/src/application/contracts/i_sender.py +++ b/src/application/contracts/i_sender.py @@ -4,5 +4,13 @@ from abc import ABC, abstractmethod class ISender(ABC): @abstractmethod - async def send(self, to: str, subject: str, body: str, plain: str | None = None) -> None: + async def send( + self, + to: str, + subject: str, + body: str, + plain: str | None = None, + *, + inline_png: tuple[str, bytes] | None = None, + ) -> None: raise NotImplementedError \ No newline at end of file diff --git a/src/infrastructure/mail/assets.py b/src/infrastructure/mail/assets.py index 59c8853..da2f08a 100644 --- a/src/infrastructure/mail/assets.py +++ b/src/infrastructure/mail/assets.py @@ -1,11 +1,11 @@ -import base64 from pathlib import Path _LOGO_PATH = Path(__file__).resolve().parent / 'templates' / 'static' / 'exa-logo.png' +EXA_LOGO_CID = 'exa-logo' -def get_exa_logo_data_uri() -> str | None: + +def get_exa_logo_png() -> bytes | None: if not _LOGO_PATH.is_file(): return None - data = base64.b64encode(_LOGO_PATH.read_bytes()).decode('ascii') - return f'data:image/png;base64,{data}' + return _LOGO_PATH.read_bytes() diff --git a/src/infrastructure/mail/sender.py b/src/infrastructure/mail/sender.py index 5096de0..661d23b 100644 --- a/src/infrastructure/mail/sender.py +++ b/src/infrastructure/mail/sender.py @@ -1,5 +1,10 @@ import aiosmtplib from email.message import EmailMessage +from email.mime.image import MIMEImage +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.policy import SMTP + from src.application.contracts.i_sender import ISender @@ -23,17 +28,34 @@ class EmailSender(ISender): self._use_tls = use_tls self._timeout = timeout - async def send(self, to: str, subject: str, body: str, plain: str | None = None) -> None: - message = EmailMessage() - message["From"] = self._from_addr - message["To"] = to - message["Subject"] = subject - - if plain: - message.set_content(plain) - message.add_alternative(body, subtype="html") + async def send( + self, + to: str, + subject: str, + body: str, + plain: str | None = None, + *, + inline_png: tuple[str, bytes] | None = None, + ) -> None: + if inline_png and plain: + message = self._multipart_with_inline_png( + to=to, + subject=subject, + plain=plain, + html=body, + content_id=inline_png[0], + png_bytes=inline_png[1], + ) else: - message.set_content(body, subtype="html") + message = EmailMessage() + message['From'] = self._from_addr + message['To'] = to + message['Subject'] = subject + if plain: + message.set_content(plain) + message.add_alternative(body, subtype='html') + else: + message.set_content(body, subtype='html') await aiosmtplib.send( message, @@ -41,6 +63,34 @@ class EmailSender(ISender): port=self._port, username=self._username, password=self._password, - use_tls=True, + use_tls=self._use_tls, timeout=self._timeout, - ) \ No newline at end of file + ) + + def _multipart_with_inline_png( + self, + *, + to: str, + subject: str, + plain: str, + html: str, + content_id: str, + png_bytes: bytes, + ) -> MIMEMultipart: + root = MIMEMultipart('alternative', policy=SMTP) + root['Subject'] = subject + root['From'] = self._from_addr + root['To'] = to + + root.attach(MIMEText(plain, 'plain', 'utf-8')) + + related = MIMEMultipart('related', policy=SMTP) + related.attach(MIMEText(html, 'html', 'utf-8')) + + image = MIMEImage(png_bytes, _subtype='png', policy=SMTP) + image.add_header('Content-ID', f'<{content_id}>') + image.add_header('Content-Disposition', 'inline', filename='exa-logo.png') + related.attach(image) + + root.attach(related) + return root diff --git a/src/infrastructure/mail/templates/email_code.html b/src/infrastructure/mail/templates/email_code.html index e4e48e6..b6dc539 100644 --- a/src/infrastructure/mail/templates/email_code.html +++ b/src/infrastructure/mail/templates/email_code.html @@ -28,8 +28,8 @@
- {% if logo_data_uri %} - {{ brand }} {% else %}