Email

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"