Axum¶
Guide for applications built with Axum (Rust). Mailexam connects as an SMTP server; sending uses the lettre crate with an async transport on Tokio.
What you need¶
- A Mailexam account and a project with SMTP credentials.
- Rust 1.70+ and an Axum project on Tokio.
Copy from the welcome email (or dashboard) for your project:
YOUR_LOGIN— SMTP login (for example,xxxxx);YOUR_PASSWORD— SMTP password (a unique pair with the login);- host —
YOUR_LOGIN.mailexam.io(matches the login; see code below).
1. Dependencies¶
In Cargo.toml:
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
lettre = { version = "0.11", default-features = false, features = [
"builder",
"smtp-transport",
"tokio1-rustls-tls",
] }
To load settings from the environment (optional):
2. Environment variables¶
.env file in the project root (do not commit passwords to git):
MAILEXAM_LOGIN=YOUR_LOGIN
MAILEXAM_PASSWORD=YOUR_PASSWORD
MAILEXAM_PORT=587
MAIL_FROM=noreply@example.test
The host is built from the login: {MAILEXAM_LOGIN}.mailexam.io.
Sender address
MAIL_FROM can be any test address — the message goes to Mailexam, not to a real recipient.
Alternative ports¶
3. Mail sending module¶
// src/mail.rs
use lettre::message::header::ContentType;
use lettre::transport::smtp::authentication::Credentials;
use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor};
#[derive(Clone)]
pub struct MailConfig {
pub host: String,
pub port: u16,
pub username: String,
pub password: String,
pub from: String,
}
impl MailConfig {
pub fn from_env() -> Self {
let login = std::env::var("MAILEXAM_LOGIN").expect("MAILEXAM_LOGIN");
Self {
host: format!("{login}.mailexam.io"),
port: std::env::var("MAILEXAM_PORT")
.unwrap_or_else(|_| "587".into())
.parse()
.expect("MAILEXAM_PORT"),
username: login,
password: std::env::var("MAILEXAM_PASSWORD").expect("MAILEXAM_PASSWORD"),
from: std::env::var("MAIL_FROM")
.unwrap_or_else(|_| "noreply@example.test".into()),
}
}
}
pub async fn send_test(
config: &MailConfig,
to: &str,
subject: &str,
body: &str,
) -> Result<(), lettre::transport::smtp::Error> {
let email = Message::builder()
.from(config.from.parse().expect("MAIL_FROM"))
.to(to.parse().expect("recipient"))
.subject(subject)
.header(ContentType::TEXT_PLAIN)
.body(body.to_string())
.expect("message");
let creds = Credentials::new(config.username.clone(), config.password.clone());
let mailer = AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(&config.host)?
.port(config.port)
.credentials(creds)
.build();
mailer.send(email).await
}
4. Axum route¶
// src/main.rs
mod mail;
use axum::{routing::post, Json, Router};
use mail::MailConfig;
use serde::Deserialize;
#[derive(Deserialize)]
struct SendRequest {
to: String,
subject: Option<String>,
body: Option<String>,
}
#[tokio::main]
async fn main() {
dotenvy::dotenv().ok();
let config = MailConfig::from_env();
let app = Router::new().route(
"/mail/test",
post({
let config = config.clone();
move |Json(payload): Json<SendRequest>| {
let config = config.clone();
async move {
mail::send_test(
&config,
&payload.to,
payload.subject.as_deref().unwrap_or("Axum + Mailexam"),
payload.body.as_deref().unwrap_or("Mailexam test from Axum"),
)
.await
.expect("smtp send");
"ok"
}
}
}),
);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
Start and verify:
curl -X POST http://127.0.0.1:3000/mail/test \
-H 'Content-Type: application/json' \
-d '{"to":"user@example.test","subject":"Test","body":"Hello"}'
The message will appear in the Mailexam dashboard → your project → inbox.
5. Local development and CI¶
| Environment | Recommendation |
|---|---|
local |
.env with a personal Mailexam project |
| CI | Secrets MAILEXAM_LOGIN, MAILEXAM_PASSWORD in GitLab CI/CD Variables |
Example for .gitlab-ci.yml:
variables:
MAILEXAM_LOGIN: $MAILEXAM_LOGIN
MAILEXAM_PASSWORD: $MAILEXAM_PASSWORD
MAILEXAM_PORT: "587"
MAIL_FROM: "noreply@example.test"
After an integration test with message sending, verify delivery via the Mailexam API.
6. Common issues¶
TLS or connection error
- Host must be
{login}.mailexam.io, where{login}is the same value asMAILEXAM_LOGINfrom the email. - Login and password are a pair from the email; do not combine credentials from different projects.
- For port 587 use
starttls_relay, not SMTPS on 465.
Message not in the dashboard
- Make sure you are viewing the inbox of the same Mailexam project.
- Check the handler response: SMTP errors are returned from
send_test.
Building lettre
- For TLS you need the
tokio1-rustls-tlsfeature (ortokio1-native-tlsif needed).