agentic.email/backend/app/services/mail_sender.py
2026-10-06 01:51:23 +05:30

128 lines
3.3 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,
in_reply_to: str | None = None,
references: list[str] | None = None,
message_id: 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)
generated_id = message_id or email.utils.make_msgid(domain=sender.split("@")[-1])
message["Message-ID"] = generated_id
if in_reply_to:
message["In-Reply-To"] = in_reply_to
if references:
message["References"] = " ".join(references)
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,
local_hostname=settings.smtp_client_hostname,
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,
in_reply_to: str | None = None,
references: list[str] | None = None,
message_id: str | None = None,
) -> str:
message = build_message(
sender=sender,
recipients=recipients,
cc=cc,
subject=subject,
body_text=body_text,
body_html=body_html,
reply_to=reply_to,
in_reply_to=in_reply_to,
references=references,
message_id=message_id,
)
signed = sign_message(message, domain)
await send_via_smtp(
signed,
sender=sender,
recipients=[*recipients, *cc, *bcc],
)
return str(message["Message-ID"])