112 lines
2.8 KiB
Python
112 lines
2.8 KiB
Python
import email.policy
|
|
import email.utils
|
|
from email.message import EmailMessage
|
|
|
|
import aiosmtplib
|
|
import dkim
|
|
|
|
from app.config import get_settings
|
|
from app.models import MailDomain
|
|
|
|
SMTP_POLICY = email.policy.SMTP.clone(max_line_length=998)
|
|
|
|
|
|
def build_message(
|
|
*,
|
|
sender: str,
|
|
recipients: list[str],
|
|
cc: list[str],
|
|
subject: str,
|
|
body_text: str,
|
|
body_html: str | None,
|
|
reply_to: str | None = None,
|
|
) -> EmailMessage:
|
|
message = EmailMessage(policy=SMTP_POLICY)
|
|
message["From"] = sender
|
|
message["To"] = ", ".join(recipients)
|
|
if cc:
|
|
message["Cc"] = ", ".join(cc)
|
|
if reply_to:
|
|
message["Reply-To"] = reply_to
|
|
message["Subject"] = subject
|
|
message["Date"] = email.utils.formatdate(localtime=True)
|
|
message["Message-ID"] = email.utils.make_msgid(domain=sender.split("@")[-1])
|
|
message.set_content(body_text)
|
|
if body_html:
|
|
message.add_alternative(body_html, subtype="html")
|
|
return message
|
|
|
|
|
|
def sign_message(message: EmailMessage, domain: MailDomain) -> bytes:
|
|
raw_message = message.as_bytes(policy=SMTP_POLICY)
|
|
signed_headers = [
|
|
b"from",
|
|
b"to",
|
|
b"subject",
|
|
b"date",
|
|
b"message-id",
|
|
b"mime-version",
|
|
b"content-type",
|
|
]
|
|
if message.get("Cc"):
|
|
signed_headers.insert(3, b"cc")
|
|
signature = dkim.sign(
|
|
raw_message,
|
|
domain.dkim_selector.encode(),
|
|
domain.domain_name.encode(),
|
|
domain.dkim_private_key.encode(),
|
|
include_headers=signed_headers,
|
|
canonicalize=(b"relaxed", b"relaxed"),
|
|
)
|
|
if not signature.endswith(b"\r\n"):
|
|
signature += b"\r\n"
|
|
return signature + raw_message
|
|
|
|
|
|
async def send_via_smtp(
|
|
message_bytes: bytes,
|
|
*,
|
|
sender: str,
|
|
recipients: list[str],
|
|
) -> None:
|
|
settings = get_settings()
|
|
await aiosmtplib.send(
|
|
message_bytes,
|
|
sender=sender,
|
|
recipients=recipients,
|
|
hostname=settings.smtp_host,
|
|
port=settings.smtp_port,
|
|
username=settings.smtp_username or None,
|
|
password=settings.smtp_password or None,
|
|
start_tls=settings.smtp_use_tls,
|
|
timeout=settings.smtp_timeout,
|
|
)
|
|
|
|
|
|
async def deliver_email(
|
|
*,
|
|
sender: str,
|
|
recipients: list[str],
|
|
cc: list[str],
|
|
bcc: list[str],
|
|
subject: str,
|
|
body_text: str,
|
|
body_html: str | None,
|
|
domain: MailDomain,
|
|
reply_to: str | None = None,
|
|
) -> None:
|
|
message = build_message(
|
|
sender=sender,
|
|
recipients=recipients,
|
|
cc=cc,
|
|
subject=subject,
|
|
body_text=body_text,
|
|
body_html=body_html,
|
|
reply_to=reply_to,
|
|
)
|
|
signed = sign_message(message, domain)
|
|
await send_via_smtp(
|
|
signed,
|
|
sender=sender,
|
|
recipients=[*recipients, *cc, *bcc],
|
|
)
|