Песочнице SMTP место в том же compose-файле, что и приложение: она поднимается вместе с docker compose up, не требует аккаунта, работает без интернета и — самое полезное — та же конфигурация работает в CI, где никто не будет кликать по дашборду. Контейнер, который туда ставят, обычно Mailpit: один образ, два порта, активная поддержка, и он заменил заброшенный MailHog.

Сервис

Документация Mailpit рекомендует такую форму, и именно к ней приходят почти все compose-файлы:

services:
  mailpit:
    image: axllent/mailpit
    container_name: mailpit
    restart: unless-stopped
    volumes:
      - ./data:/data
    ports:
      - 8025:8025   # веб-интерфейс
      - 1025:1025   # SMTP
    environment:
      MP_MAX_MESSAGES: 5000
      MP_DATABASE: /data/mailpit.db
      MP_SMTP_AUTH_ACCEPT_ANY: 1
      MP_SMTP_AUTH_ALLOW_INSECURE: 1

Что даёт каждая строка:

  • MP_DATABASE + том сохраняют перехваченные письма между перезапусками; без них Mailpit живёт в памяти и перезапуск контейнера очищает инбокс.
  • MP_MAX_MESSAGES ограничивает хранилище, чтобы оно не росло вечно (5000 — с запасом для разработки; в CI можно снизить до нескольких сотен).
  • MP_SMTP_AUTH_ACCEPT_ANY / MP_SMTP_AUTH_ALLOW_INSECURE принимают любые SMTP-доступы: многие конфигурации требуют логин и пароль даже в разработке.

Если SMTP нужен только контейнерам внутри сети, уберите маппинг 1025:1025: опубликованные порты — ровно то, из-за чего тестовый SMTP однажды становится открытым релеем в интернете.

Направьте приложение на него

Внутри compose-сети хост — это имя сервиса, а не localhost. Это самая частая ошибка с контейнерными песочницами:

# Laravel (.env)
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=anything
MAIL_PASSWORD=anything
MAIL_ENCRYPTION=null
# Django settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'mailpit'
EMAIL_PORT = 1025
EMAIL_HOST_USER = 'anything'
EMAIL_HOST_PASSWORD = 'anything'
EMAIL_USE_TLS = False

С ноутбука (вне сети) тот же сервис доступен на 127.0.0.1:1025, а веб-интерфейс — на http://127.0.0.1:8025.

Кто ещё встаёт на это место

  • smtp4dev — аналог на .NET: та же идея, те же два порта, свой образ. Берите, если команда живёт в Visual Studio.
  • MailHog встречается в старых compose-файлах. Он больше не поддерживается — путь миграции за пять минут описан в Альтернативе MailHog (порты совпадают, меняется только имя образа).
  • Хостируемая песочница (этот инстанс) — вариант, когда контейнера не хочется вообще: compose-сервиса нет, но и «поднимать» нечего.

Как дождаться его в CI

docker compose up -d возвращает управление раньше, чем SMTP начнёт принимать соединения, — отсюда «мигающие» тесты. Либо ждите порт, либо задайте healthcheck:

    healthcheck:
      test: ["CMD", "/mailpit", "readyz"]
      interval: 2s
      timeout: 2s
      retries: 15

Тогда docker compose up -d --wait (Compose v2.17+) заблокируется до готовности — без sleep в пайплайне.

Три детали, которые стоит знать

  1. Размер письма. Песочница, принимающая что угодно, всё равно имеет лимиты: если тест с вложением падает, сверьте лимит приложения и песочницы, прежде чем винить код.
  2. Таймауты. В разработке таймаут мейлера лучше держать коротким (5–10 секунд): сломанная песочница должна падать быстро, а не подвешивать запрос на минуту.
  3. Доступ с другого хоста. Если ловушка живёт на сервере (например, общий staging), не публикуйте 1025 наружу — привяжите к локальному интерфейсу или оставьте внутри сети: публичная ловушка — это сервис с чужой почтой внутри.

Что дальше