Перейти к содержанию

Actix Web

Инструкция для приложений на Actix Web (Rust). Mailexam подключается как SMTP-сервер; для отправки используется крейт lettre с асинхронным транспортом под Tokio (Actix Web 4 работает на Tokio).

Что понадобится

  • Аккаунт Mailexam и проект с SMTP-учётными данными.
  • Rust 1.70+ и проект на Actix Web 4.

Скопируйте из приветственного письма (или кабинета) для вашего проекта:

  • ВАШ_ЛОГИН — SMTP-логин (например, xxxxx);
  • ВАШ_ПАРОЛЬ — SMTP-пароль (уникальная пара к логину);
  • хост — ВАШ_ЛОГИН.mailexam.ru (совпадает с логином, см. код ниже).

1. Зависимости

В Cargo.toml:

[dependencies]
actix-web = "4"
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=ВАШ_ЛОГИН
MAILEXAM_PASSWORD=ВАШ_ПАРОЛЬ
MAILEXAM_PORT=587
MAIL_FROM=noreply@example.test

Хост собирается из логина: {MAILEXAM_LOGIN}.mailexam.ru.

Адрес отправителя

MAIL_FROM может быть любым тестовым адресом — письмо попадёт в Mailexam, а не реальному получателю.

Альтернативные порты

MAILEXAM_PORT=587

Используйте AsyncSmtpTransport::starttls_relay (см. ниже).

MAILEXAM_PORT=2525
MAILEXAM_PORT=25

Вместо starttls_relay используйте AsyncSmtpTransport::builder(...).port(25) без обязательного TLS — только если это допустимо в вашей сети.

3. Модуль отправки почты

Тот же модуль, что и для Axum: MailConfig::from_env() и send_test через lettre.

// 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.ru"),
            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. Обработчик Actix Web

// src/main.rs
mod mail;

use actix_web::{post, web, App, HttpResponse, HttpServer, Responder};
use mail::MailConfig;
use serde::Deserialize;

#[derive(Deserialize)]
struct SendRequest {
    to: String,
    subject: Option<String>,
    body: Option<String>,
}

#[post("/mail/test")]
async fn send_mail(
    config: web::Data<MailConfig>,
    payload: web::Json<SendRequest>,
) -> impl Responder {
    mail::send_test(
        &config,
        &payload.to,
        payload.subject.as_deref().unwrap_or("Actix + Mailexam"),
        payload.body.as_deref().unwrap_or("Тест Mailexam из Actix"),
    )
    .await
    .expect("smtp send");

    HttpResponse::Ok().body("ok")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    dotenvy::dotenv().ok();

    let config = web::Data::new(MailConfig::from_env());

    HttpServer::new(move || {
        App::new()
            .app_data(config.clone())
            .service(send_mail)
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

Запуск и проверка:

cargo run
curl -X POST http://127.0.0.1:8080/mail/test \
  -H 'Content-Type: application/json' \
  -d '{"to":"user@example.test","subject":"Проверка","body":"Привет"}'

Письмо появится в кабинете Mailexam → ваш проект → входящие.

5. Локальная разработка и CI

Среда Рекомендация
local .env с личным проектом Mailexam
CI Секреты MAILEXAM_LOGIN, MAILEXAM_PASSWORD в GitLab CI/CD Variables

Пример для .gitlab-ci.yml:

variables:
  MAILEXAM_LOGIN: $MAILEXAM_LOGIN
  MAILEXAM_PASSWORD: $MAILEXAM_PASSWORD
  MAILEXAM_PORT: "587"
  MAIL_FROM: "noreply@example.test"

После интеграционного теста с отправкой письма проверьте доставку через API Mailexam.

6. Типичные проблемы

Ошибка TLS или подключения

  • Хост должен быть {логин}.mailexam.ru, где {логин} — то же значение, что MAILEXAM_LOGIN из письма.
  • Логин и пароль — пара из письма; не комбинируйте данные разных проектов.
  • Для порта 587 используйте starttls_relay, не SMTPS на 465.

Письмо не в кабинете

  • Убедитесь, что смотрите входящие того же проекта Mailexam.
  • Проверьте логи воркеров Actix: ошибки SMTP видны при падении send_test.

Сборка lettre

  • Для TLS нужен feature tokio1-rustls-tls (или tokio1-native-tls при необходимости).

Порт занят

  • По умолчанию сервер слушает 8080; измените адрес в .bind(...) при конфликте.

См. также