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

FastAPI

Инструкция для приложений на FastAPI (Python 3.10+). Mailexam подключается как SMTP-сервер через smtplib; отправку можно вынести в отдельный модуль и вызывать из async-обработчика.

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

  • Аккаунт Mailexam и проект с SMTP-учётными данными.
  • Python 3.10+ и виртуальное окружение.

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

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

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

python3 -m venv .venv
source .venv/bin/activate
pip install fastapi uvicorn python-dotenv email-validator

Файл requirements.txt:

fastapi>=0.115
uvicorn[standard]>=0.32
python-dotenv>=1.0
email-validator>=2.0

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
MAILEXAM_PORT=2525
MAILEXAM_PORT=25

В коде для порта 25 не вызывайте starttls() (см. модуль ниже).

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

Тот же подход, что в Flask:

# mail.py
import os
import smtplib
from email.message import EmailMessage


def smtp_host() -> str:
    login = os.environ["MAILEXAM_LOGIN"]
    return f"{login}.mailexam.ru"


def send_test(*, to: str, subject: str, body: str) -> None:
    login = os.environ["MAILEXAM_LOGIN"]
    password = os.environ["MAILEXAM_PASSWORD"]
    port = int(os.environ.get("MAILEXAM_PORT", "587"))
    mail_from = os.environ.get("MAIL_FROM", "noreply@example.test")

    msg = EmailMessage()
    msg["From"] = mail_from
    msg["To"] = to
    msg["Subject"] = subject
    msg.set_content(body)

    with smtplib.SMTP(smtp_host(), port, timeout=30) as smtp:
        if port in (587, 2525):
            smtp.starttls()
        smtp.login(login, password)
        smtp.send_message(msg)

smtplib синхронный — в async-приложении вызывайте его через asyncio.to_thread, чтобы не блокировать event loop.

4. Маршрут FastAPI

# main.py
import asyncio

from dotenv import load_dotenv
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

from mail import send_test

load_dotenv()

app = FastAPI()


class SendRequest(BaseModel):
    to: EmailStr = "user@example.test"
    subject: str = "FastAPI + Mailexam"
    body: str = "Тест Mailexam из FastAPI"


@app.post("/mail/test")
async def mail_test(payload: SendRequest):
    await asyncio.to_thread(
        send_test,
        to=str(payload.to),
        subject=payload.subject,
        body=payload.body,
    )
    return {"status": "ok"}

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

uvicorn main:app --reload --host 127.0.0.1 --port 8000
curl -X POST http://127.0.0.1:8000/mail/test \
  -H 'Content-Type: application/json' \
  -d '{"to":"user@example.test","subject":"Проверка","body":"Привет"}'

Документация REST API: http://127.0.0.1:8000/docs

Письмо появится в кабинете 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 вызывайте smtp.starttls() перед login().

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

  • Убедитесь, что смотрите входящие того же проекта Mailexam.
  • Проверьте ответ /mail/test и логи uvicorn.

Блокировка event loop

  • Не вызывайте send_test() напрямую в async def — используйте asyncio.to_thread() или фоновую задачу.

Переменные окружения не подхватываются

  • Вызовите load_dotenv() при старте приложения.
  • При запуске через systemd/Docker передайте переменные в окружение контейнера.

См. также