From 74732c6e5a47c3e0a13aa63ad590196706fe3e04 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Wed, 8 Jul 2026 09:27:09 +0000 Subject: [PATCH] =?UTF-8?q?Send=20a=20document-download=20email=20to=20the?= =?UTF-8?q?=20requesting=20user=20over=20SES=20SMTP=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- infrastructure/email/ses_smtp_email_sender.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/infrastructure/email/ses_smtp_email_sender.py b/infrastructure/email/ses_smtp_email_sender.py index 9318f100d..ba53d23bc 100644 --- a/infrastructure/email/ses_smtp_email_sender.py +++ b/infrastructure/email/ses_smtp_email_sender.py @@ -11,7 +11,7 @@ from __future__ import annotations import smtplib from email.message import EmailMessage from types import TracebackType -from typing import Any, Callable, Optional, Protocol, Type +from typing import Any, Callable, Optional, Protocol, Type, cast class SmtpTransport(Protocol): @@ -35,7 +35,10 @@ class SmtpTransport(Protocol): def _default_smtp(host: str, port: int) -> SmtpTransport: - return smtplib.SMTP(host, port) + # `smtplib.SMTP` satisfies SmtpTransport structurally; its stdlib stubs + # declare wider signatures (extra optional args, an SMTP-typed context + # manager), so the cast bridges the real transport to the port's surface. + return cast(SmtpTransport, smtplib.SMTP(host, port)) class SesSmtpEmailSender: @@ -57,4 +60,12 @@ class SesSmtpEmailSender: self._smtp_factory = smtp_factory def send(self, *, to: str, subject: str, body: str) -> None: - raise NotImplementedError + message = EmailMessage() + message["From"] = self._from_address + message["To"] = to + message["Subject"] = subject + message.set_content(body) + with self._smtp_factory(self._host, self._port) as smtp: + smtp.starttls() + smtp.login(self._username, self._password) + smtp.send_message(message)