feat: update style of mail
This commit is contained in:
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td align="left" valign="middle">
|
||||
{% if logo_data_uri %}
|
||||
<img src="{{ logo_data_uri }}" alt="{{ brand }}" width="200" height="auto"
|
||||
{% if logo_src %}
|
||||
<img src="{{ logo_src }}" alt="{{ brand }}" width="200" height="auto"
|
||||
style="display:block;max-width:200px;height:auto;border:0;outline:none;text-decoration:none;" />
|
||||
{% else %}
|
||||
<div style="font-family:Inter,'Segoe UI',Arial,Helvetica,sans-serif;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 63 KiB |
@@ -3,7 +3,7 @@ from faststream.rabbit.fastapi import RabbitRouter, RabbitMessage
|
||||
from src.infrastructure.config import settings
|
||||
from src.infrastructure.logger import logger
|
||||
from src.infrastructure.mail import TemplateRenderer, get_email_sender, get_renderer
|
||||
from src.infrastructure.mail.assets import get_exa_logo_data_uri
|
||||
from src.infrastructure.mail.assets import EXA_LOGO_CID, get_exa_logo_png
|
||||
from src.infrastructure.mail.sender import EmailSender
|
||||
from datetime import datetime
|
||||
from typing import Literal, Optional
|
||||
@@ -53,7 +53,8 @@ async def consume_email_code(
|
||||
f"trace_id={trace_id}"
|
||||
)
|
||||
|
||||
logo_data_uri = get_exa_logo_data_uri()
|
||||
logo_png = get_exa_logo_png()
|
||||
logo_src = f'cid:{EXA_LOGO_CID}' if logo_png else ''
|
||||
|
||||
html = renderer.render(
|
||||
'email_code.html',
|
||||
@@ -61,7 +62,7 @@ async def consume_email_code(
|
||||
code=msg_body.payload.code,
|
||||
ttl_minutes=msg_body.payload.ttl_seconds // 60,
|
||||
brand='Экса',
|
||||
logo_data_uri=logo_data_uri,
|
||||
logo_src=logo_src,
|
||||
trace_id=trace_id,
|
||||
year=datetime.now().year,
|
||||
)
|
||||
@@ -80,4 +81,5 @@ async def consume_email_code(
|
||||
subject='Экса — код подтверждения',
|
||||
body=html,
|
||||
plain=text,
|
||||
inline_png=(EXA_LOGO_CID, logo_png) if logo_png else None,
|
||||
)
|
||||
Reference in New Issue
Block a user