Send a document-download email to the requesting user over SES SMTP 🟥

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-08 09:25:56 +00:00
parent 135dd9abda
commit 7466b78e54
6 changed files with 152 additions and 0 deletions

View file

View file

@ -0,0 +1,60 @@
"""SES-over-SMTP implementation of the outbound-email port (ADR-0059).
Sends via `smtplib` against the SES SMTP endpoint using the credentials
provisioned in terraform (`modules/ses`, Secrets Manager
`${stage}/ses/smtp_credentials`). The SMTP transport is injected so the sender
is testable without a live server.
"""
from __future__ import annotations
import smtplib
from email.message import EmailMessage
from types import TracebackType
from typing import Any, Callable, Optional, Protocol, Type
class SmtpTransport(Protocol):
"""The minimal SMTP surface the sender uses — satisfied structurally by
`smtplib.SMTP` and by test fakes."""
def __enter__(self) -> "SmtpTransport": ...
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc: Optional[BaseException],
tb: Optional[TracebackType],
) -> Optional[bool]: ...
def starttls(self) -> Any: ...
def login(self, user: str, password: str) -> Any: ...
def send_message(self, message: EmailMessage) -> Any: ...
def _default_smtp(host: str, port: int) -> SmtpTransport:
return smtplib.SMTP(host, port)
class SesSmtpEmailSender:
def __init__(
self,
*,
host: str,
port: int,
username: str,
password: str,
from_address: str,
smtp_factory: Callable[[str, int], SmtpTransport] = _default_smtp,
) -> None:
self._host = host
self._port = port
self._username = username
self._password = password
self._from_address = from_address
self._smtp_factory = smtp_factory
def send(self, *, to: str, subject: str, body: str) -> None:
raise NotImplementedError

View file

View file

@ -0,0 +1,15 @@
"""The outbound-email port (ADR-0059).
A narrow interface the orchestrator depends on to notify a user, so the
transport (SES over SMTP today) stays swappable and testable behind it.
"""
from __future__ import annotations
from typing import Protocol
class EmailSender(Protocol):
def send(self, *, to: str, subject: str, body: str) -> None:
"""Send a plain-text email to ``to``."""
...

View file

View file

@ -0,0 +1,77 @@
"""The SES-over-SMTP email sender (ADR-0059) — delivers a plain-text message to
the requesting user via an injected SMTP transport."""
from __future__ import annotations
from email.message import EmailMessage
from types import TracebackType
from typing import Any, Optional, Type
from infrastructure.email.ses_smtp_email_sender import SesSmtpEmailSender
class _FakeSmtp:
"""Records what the sender hands the transport, standing in for a live SES
SMTP connection."""
def __init__(self, host: str, port: int) -> None:
self.host = host
self.port = port
self.messages: list[EmailMessage] = []
self.started_tls = False
self.login_args: Optional[tuple[str, str]] = None
def __enter__(self) -> "_FakeSmtp":
return self
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc: Optional[BaseException],
tb: Optional[TracebackType],
) -> Optional[bool]:
return None
def starttls(self) -> Any:
self.started_tls = True
def login(self, user: str, password: str) -> Any:
self.login_args = (user, password)
def send_message(self, message: EmailMessage) -> Any:
self.messages.append(message)
def test_send_delivers_a_plaintext_message_with_the_right_envelope() -> None:
# Arrange
created: list[_FakeSmtp] = []
def factory(host: str, port: int) -> _FakeSmtp:
smtp = _FakeSmtp(host, port)
created.append(smtp)
return smtp
sender = SesSmtpEmailSender(
host="email-smtp.eu-west-2.amazonaws.com",
port=587,
username="AKIA_SMTP_USER",
password="smtp-password",
from_address="noreply@domna.homes",
smtp_factory=factory,
)
# Act
sender.send(
to="user@example.com",
subject="Your document download is ready",
body="Download it here: https://signed-url",
)
# Assert — one message, correctly addressed, body carried through.
assert len(created) == 1
assert len(created[0].messages) == 1
message = created[0].messages[0]
assert message["To"] == "user@example.com"
assert message["From"] == "noreply@domna.homes"
assert message["Subject"] == "Your document download is ready"
assert "https://signed-url" in message.get_content()