Email

Email

Blixt provides an async SMTP mailer built on lettre. It supports both HTML emails (rendered from Askama templates) and plain-text emails. SMTP credentials are handled through SecretString and never appear in logs or debug output.

Configuration

Load mailer configuration from environment variables using MailerConfig::from_env():

use blixt::prelude::*;

let config = MailerConfig::from_env()?;
let mailer = Mailer::new(config)?;

Required environment variables

VariableDescriptionExample
SMTP_HOSTSMTP server hostnamesmtp.mailgun.org
SMTP_PORTSMTP server port (typically 587 for STARTTLS)587
SMTP_USERSMTP authentication usernamepostmaster@mg.example.com
SMTP_PASSWORDSMTP authentication passwordsecret
FROM_NAMEDisplay name in the From headerMy App
FROM_EMAILEmail address in the From headernoreply@example.com

Add these to your .env file for local development:

SMTP_HOST=smtp.mailgun.org
SMTP_PORT=587
SMTP_USER=postmaster@mg.example.com
SMTP_PASSWORD=your-smtp-password
FROM_NAME=My App
FROM_EMAIL=noreply@example.com

If any variable is missing, from_env() returns an Error::Internal with a message identifying the missing variable.

Sending HTML emails

Use Askama templates to render email content:

use askama::Template;
use blixt::prelude::*;

#[derive(Template)]
#[template(path = "emails/welcome.html")]
struct WelcomeEmail<'a> {
    name: &'a str,
    action_url: &'a str,
}

pub async fn send_welcome(mailer: &Mailer, user_email: &str, name: &str) -> Result<()> {
    let template = WelcomeEmail {
        name,
        action_url: "https://example.com/get-started",
    };

    mailer.send_html(user_email, "Welcome!", template).await
}

The template is rendered at call time. Rendering errors are returned as Error::Internal.

Place your email templates in templates/emails/ following the project layout convention:

templates/
  emails/
    welcome.html
    password_reset.html
    notification.html

Sending plain-text emails

For emails that don't need HTML formatting:

mailer.send_text(
    "user@example.com",
    "Your verification code",
    format!("Your code is: {code}"),
).await?;

Transport

The mailer uses STARTTLS for encrypted SMTP connections. The transport is built once during Mailer::new() and reused across all send calls. On initialization, the mailer logs the SMTP host, port, and from address (but not the password).

Credential safety

  • smtp_password is stored as SecretString from the secrecy crate.
  • The Debug implementation for MailerConfig prints [REDACTED] instead of the password.
  • The Debug implementation for Mailer uses finish_non_exhaustive() to omit transport internals.
  • Access the password with config.smtp_password.expose_secret() only when building SMTP credentials.

Invalid addresses

If the recipient email address is invalid, send_html and send_text return Error::BadRequest with a message like "Invalid recipient: ...". Invalid sender addresses (from FROM_EMAIL) cause Mailer::new() to return Error::Internal.

Using with background jobs

For non-blocking email delivery, combine the mailer with the background job runner:

use std::sync::Arc;
use blixt::prelude::*;

let mailer = Arc::new(Mailer::new(MailerConfig::from_env()?)?);
let runner = JobRunner::default_runner();

let mailer_clone = mailer.clone();
let to = "user@example.com".to_string();

runner.submit(job_fn("send_welcome", move || {
    let mailer = mailer_clone.clone();
    let to = to.clone();
    async move {
        mailer.send_text(&to, "Welcome", "Thanks for signing up!".into()).await
    }
})).await?;