Email

Z8ter ships transactional email support with pluggable providers (z8ter.email). Handlers send through an async EmailService that never blocks the event loop.

Enabling email

builder = AppBuilder()
builder.use_config(".env")
builder.use_email()  # provider resolved from config
app = builder.build()

Configuration via .env:

EMAIL_PROVIDER=console        # console (default) or smtp
EMAIL_FROM=noreply@example.com

# Required when EMAIL_PROVIDER=smtp
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=apikey
SMTP_PASSWORD=secret
SMTP_USE_TLS=true             # STARTTLS (default)
SMTP_USE_SSL=false            # implicit SSL (e.g., port 465)

The console provider logs messages instead of delivering them — ideal while developing flows like registration or password reset.

You can also pass a provider explicitly (any object with a send(message) method satisfies the EmailProvider contract):

from z8ter.email import SMTPEmailProvider

builder.use_email(
    provider=SMTPEmailProvider(host="smtp.example.com", port=587),
    default_from="noreply@example.com",
)

Sending email

The service is available at request.app.state.email:

class Register(View):
    async def post(self, request):
        email = request.app.state.email
        await email.send_email(
            to="user@example.com",
            subject="Welcome!",
            text="Thanks for signing up.",
            html="<p>Thanks for signing up.</p>",
        )

Template-based email

Email bodies can be rendered from the same Jinja template directory as your pages:

templates/
  emails/
    welcome.html
    welcome.txt
await email.send_template(
    to=user["email"],
    subject="Welcome!",
    template="emails/welcome.html",
    text_template="emails/welcome.txt",   # optional; derived from HTML if omitted
    context={"name": user["name"]},
)

Verification and password-reset emails

Combine the email service with TokenManager (see Security) to implement email verification and password reset:

from z8ter.auth.tokens import TokenManager

tokens = TokenManager(config("APP_SESSION_KEY"))
token = tokens.generate_email_verification_token(user["id"], user["email"])
await email.send_email(
    to=user["email"],
    subject="Verify your email",
    text=f"Click to verify: https://example.com/verify?token={token}",
)

Testing

Use the in-memory provider and assert on its outbox:

from z8ter.email import InMemoryEmailProvider

provider = InMemoryEmailProvider()
builder.use_email(provider=provider, default_from="noreply@test")
...
assert provider.outbox[0].subject == "Verify your email"