跳转至

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",
] }

从环境加载配置(可选):

dotenvy = "0.15"

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,而非真实收件人。

备选端口

MAILEXAM_PORT=587

使用 AsyncSmtpTransport::starttls_relay(见下文)。

MAILEXAM_PORT=2525
MAILEXAM_PORT=25

不使用 starttls_relay,改用 AsyncSmtpTransport::builder(...).port(25) 且不强制 TLS——仅在网络环境允许时。

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();
}

启动并验证:

cargo run
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_LOGINMAILEXAM_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)。

参见