Send mail through a small adapter interface. Use the console adapter locally and SMTP in production.
Console development
from flaxon.mail import Email, Mailer
from flaxon.mail.adapters.console import ConsoleAdapter
mailer = Mailer(ConsoleAdapter())
await mailer.send(Email(
from_address="noreply@example.com",
to=["developer@example.com"],
subject="Welcome",
body="Your account is ready.",
html_body="<h1>Welcome</h1>",
))SMTP production
import os
from flaxon.mail import Email, Mailer
from flaxon.mail.adapters.smtp import SMTPAdapter
mailer = Mailer(SMTPAdapter(
host=os.environ["MAIL_HOST"],
port=int(os.getenv("MAIL_PORT", "587")),
username=os.getenv("MAIL_USERNAME"),
password=os.getenv("MAIL_PASSWORD"),
use_tls=True,
))
await mailer.send(Email(
from_address=os.environ["MAIL_FROM"],
to=["user@example.com"],
subject="Invoice ready",
body="Your invoice is ready.",
))Use port 587 with STARTTLS or port 465 with use_ssl=True. Do not enable both TLS modes. Put credentials in a secret manager or environment variables.
Templates and attachments
from flaxon.mail import Attachment, Email
email = Email(
from_address="noreply@example.com",
to=["user@example.com"],
subject="Receipt",
body="Your receipt is attached.",
attachments=[Attachment("receipt.txt", b"Receipt contents")],
)
await mailer.send(email)For Jinja email templates, use EmailTemplate and TemplateEngine. Use mailer.send_many([...]) for sequential bulk delivery. For high-volume mail, enqueue sends through a task worker.
Testing
class FakeMailAdapter:
def __init__(self): self.messages = []
async def send(self, email): self.messages.append(email)
adapter = FakeMailAdapter()
mailer = Mailer(adapter)
await mailer.send(Email(from_address="test@example.com", to=["user@example.com"], subject="Test", body="Hello"))
assert adapter.messages[0].subject == "Test"