Axum¶
适用于 Axum(Rust)应用的集成指南。Mailexam 作为 SMTP 服务器接入;发送使用 lettre crate 及基于 Tokio 的异步传输。
前置条件¶
- Mailexam 账户,以及已创建并具备 SMTP 凭据的项目。
- Rust 1.70+ 及基于 Tokio 的 Axum 项目。
从项目的欢迎邮件(或控制台)中复制:
YOUR_LOGIN— SMTP 登录名(例如xxxxx);YOUR_PASSWORD— SMTP 密码(与登录名成对的唯一凭据);- 主机 —
YOUR_LOGIN.mailexam.cn(与登录名一致;见下方代码)。
1. 依赖¶
在 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",
] }
从环境加载配置(可选):
2. 环境变量¶
项目根目录的 .env 文件(勿将密码提交到 git):
MAILEXAM_LOGIN=YOUR_LOGIN
MAILEXAM_PASSWORD=YOUR_PASSWORD
MAILEXAM_PORT=587
MAIL_FROM=noreply@example.test
主机由登录名构建:{MAILEXAM_LOGIN}.mailexam.cn。
发件人地址
MAIL_FROM 可以是任意测试地址——邮件进入 Mailexam,而非真实收件人。
备选端口¶
3. 邮件发送模块¶
// 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.cn"),
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 路由¶
// 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("来自 Axum 的 Mailexam 测试"),
)
.await
.expect("smtp send");
"ok"
}
}
}),
);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
启动并验证:
curl -X POST http://127.0.0.1:3000/mail/test \
-H 'Content-Type: application/json' \
-d '{"to":"user@example.test","subject":"测试","body":"你好"}'
邮件将出现在 Mailexam 控制台 → 您的项目 → 收件箱。
5. 本地开发与 CI¶
| 环境 | 建议 |
|---|---|
local |
带个人 Mailexam 项目的 .env |
| CI | GitLab CI/CD Variables 中的密钥 MAILEXAM_LOGIN、MAILEXAM_PASSWORD |
.gitlab-ci.yml 示例:
variables:
MAILEXAM_LOGIN: $MAILEXAM_LOGIN
MAILEXAM_PASSWORD: $MAILEXAM_PASSWORD
MAILEXAM_PORT: "587"
MAIL_FROM: "noreply@example.test"
集成测试发送邮件后,通过 Mailexam API 验证投递。
6. 常见问题¶
TLS 或连接错误
- 主机须为
{login}.mailexam.cn,其中{login}与邮件中的MAILEXAM_LOGIN相同。 - 登录名和密码为邮件成对凭据;勿混用不同项目的凭据。
- 587 端口使用
starttls_relay,而非 465 上的 SMTPS。
控制台中看不到邮件
- 确认查看的是同一 Mailexam 项目的收件箱。
- 检查处理器响应:
send_test会返回 SMTP 错误。
编译 lettre
- TLS 需要
tokio1-rustls-tls特性(或按需使用tokio1-native-tls)。