260 lines
14 KiB
Markdown
260 lines
14 KiB
Markdown
# Почта MoonWell
|
||
|
||
Для `support@moon-well.online` и `no-reply@moon-well.online` используется отдельный контейнер
|
||
[Stalwart Mail Server](https://stalw.art/). Он предоставляет SMTP, IMAP,
|
||
антиспам, DKIM и административный интерфейс. Данные и конфигурация хранятся
|
||
в именованных Docker volumes и переживают пересоздание контейнера.
|
||
|
||
## Что требуется от сервера
|
||
|
||
- постоянный публичный IPv4-адрес;
|
||
- возможность принимать TCP-соединения на порты `25`, `465`, `587`, `993`;
|
||
- разрешённый исходящий TCP-порт `25`;
|
||
- PTR/rDNS для IP со значением `mail.moon-well.online`;
|
||
- доступ к управлению DNS-зоной `moon-well.online`.
|
||
|
||
Без корректного PTR исходящие сообщения часто попадают в спам. Некоторые
|
||
хостинги блокируют исходящий порт 25 — это нужно проверить до настройки.
|
||
|
||
## Первый запуск
|
||
|
||
```bash
|
||
./scripts/mail-server.sh init
|
||
```
|
||
|
||
Проверь значения в `.env.mail`. Затем создай DNS-запись:
|
||
|
||
```text
|
||
mail.moon-well.online. A <публичный IPv4 сервера>
|
||
```
|
||
|
||
Запусти сервер:
|
||
|
||
```bash
|
||
./scripts/mail-server.sh up
|
||
./scripts/mail-server.sh credentials
|
||
```
|
||
|
||
Административный HTTP-порт намеренно доступен только на loopback-интерфейсе.
|
||
Открой туннель со своего компьютера:
|
||
|
||
```bash
|
||
ssh -L 8081:127.0.0.1:8081 <пользователь>@<сервер>
|
||
```
|
||
|
||
После этого открой `http://127.0.0.1:8081/admin` и войди с временными
|
||
реквизитами из команды `credentials`.
|
||
|
||
## Мастер Stalwart
|
||
|
||
Используй следующие значения:
|
||
|
||
- Server hostname: `mail.moon-well.online`
|
||
- Default email domain: `moon-well.online`
|
||
- Automatically obtain TLS certificate: включено
|
||
- Generate email signing keys: включено
|
||
- Storage: локальный RocksDB
|
||
- Directory: Internal Directory
|
||
- Logging: Console
|
||
|
||
Если DNS размещён у поддерживаемого Stalwart провайдера, подключи его в
|
||
последнем шаге. Тогда сертификат выпускается через DNS-01 без занятия
|
||
публичного порта 443. Иначе понадобится отдельно передать серверу TLS-сертификат
|
||
или временно предоставить ему порт 443 для ACME-проверки.
|
||
|
||
Сохрани постоянные реквизиты администратора, показанные мастером, и
|
||
перезапусти контейнер:
|
||
|
||
```bash
|
||
./scripts/mail-server.sh restart
|
||
```
|
||
|
||
Создай обычный пользовательский аккаунт `support@moon-well.online`.
|
||
Административный аккаунт нельзя использовать в почтовом клиенте.
|
||
|
||
Для приложения создай отдельный SMTP-аккаунт `no-reply@moon-well.online`
|
||
с правом отправки от этого адреса. Laravel использует его для подтверждения
|
||
email и восстановления пароля. Пароль хранится только в `.env` приложения;
|
||
в репозиторий его добавлять не нужно. Ящик поддержки остаётся отдельным.
|
||
|
||
В панели Stalwart открой `Management → Directory → Accounts` и создай
|
||
обычный пользовательский аккаунт (роль User):
|
||
|
||
- имя: `no-reply`;
|
||
- домен: `moon-well.online`;
|
||
- основной email: `no-reply@moon-well.online`;
|
||
- отображаемое имя: `MoonWell`;
|
||
- пароль: отдельный случайный пароль для SMTP.
|
||
|
||
В версиях панели с полем полного логина укажи `no-reply@moon-well.online`.
|
||
Названия полей могут отличаться между версиями; итоговый адрес и SMTP-логин
|
||
должны быть `no-reply@moon-well.online`. Не выдавай этому аккаунту роль
|
||
администратора. Отдельные MX/SPF/DKIM-записи для нового ящика не нужны —
|
||
используется уже настроенный домен.
|
||
|
||
[Создание аккаунтов в документации Stalwart](https://www.stalw.art/docs/faq/accounts/).
|
||
|
||
## DNS после мастера
|
||
|
||
В панели Stalwart открой:
|
||
|
||
`Management → Domains → Domains → moon-well.online → View DNS Zone file`
|
||
|
||
Перенеси предложенные записи в DNS-панель. В обязательный минимум входят:
|
||
|
||
- MX;
|
||
- SPF;
|
||
- DKIM;
|
||
- DMARC;
|
||
- записи автоконфигурации, которые предложит Stalwart.
|
||
|
||
Значение DKIM нужно брать только из созданной установки: ключ генерируется
|
||
индивидуально. Не копируй DKIM из примеров.
|
||
|
||
## Настройка почтового клиента
|
||
|
||
```text
|
||
Адрес: support@moon-well.online
|
||
IMAP-сервер: mail.moon-well.online
|
||
IMAP-порт: 993
|
||
Шифрование IMAP: SSL/TLS
|
||
SMTP-сервер: mail.moon-well.online
|
||
SMTP-порт: 465
|
||
Шифрование SMTP: SSL/TLS
|
||
SMTP-аутентификация: включена
|
||
Логин: support@moon-well.online
|
||
```
|
||
|
||
## Подключение Laravel
|
||
|
||
После создания `no-reply@moon-well.online` укажи в production `.env`:
|
||
|
||
```dotenv
|
||
APP_URL=https://moon-well.online
|
||
MAIL_MAILER=smtp
|
||
MAIL_SCHEME=smtps
|
||
MAIL_HOST=mail.moon-well.online
|
||
MAIL_PORT=465
|
||
MAIL_USERNAME=no-reply@moon-well.online
|
||
MAIL_PASSWORD=<пароль ящика>
|
||
MAIL_FROM_ADDRESS=no-reply@moon-well.online
|
||
MAIL_FROM_NAME="${APP_NAME}"
|
||
```
|
||
|
||
Затем сбрось и пересобери кэш конфигурации:
|
||
|
||
```bash
|
||
docker compose -f compose.prod.yaml exec -T --user app app php artisan optimize:clear
|
||
docker compose -f compose.prod.yaml exec -T --user app app php artisan config:cache
|
||
```
|
||
|
||
Если используется SSH-туннель к БД ядра, добавь
|
||
`-f compose.prod.ssh-tunnel.yaml` к обеим командам. Для Sail используй
|
||
`docker compose exec -T laravel.test php artisan …`.
|
||
|
||
`MAIL_HOST` — публичное имя с действительным TLS-сертификатом. Приложение
|
||
может отправлять письма на опубликованный Stalwart порт 465, даже если его
|
||
контейнер находится в другой Docker-сети. Не отключай проверку сертификата
|
||
и не подменяй имя на `stalwart` при использовании публичного сертификата.
|
||
|
||
При доставке нового кода выполни миграции (`php artisan migrate --force`).
|
||
Новые регистрации на сайте и через API лаунчера требуют подтверждения
|
||
почты; существующие и импортированные аккаунты сохраняют доступ.
|
||
Подтверждение не требует предварительного входа. Повторное письмо доступно
|
||
на `/email/verify`, восстановление — на `/forgot-password`. Обе ссылки
|
||
действуют 60 минут; ссылка восстановления одноразовая. Пароль меняется
|
||
в `web.users`, как при смене пароля в кабинете; игровые SRP-данные остаются
|
||
под управлением механизма билетов лаунчера.
|
||
|
||
Локально можно оставить `MAIL_MAILER=log`: тогда письма записываются в лог.
|
||
Для проверки реальной доставки используй свой аккаунт: зарегистрируй его,
|
||
подтверди почту, затем запроси восстановление. В обоих письмах отправитель
|
||
должен быть `MoonWell <no-reply@moon-well.online>`, а ссылки вести на `APP_URL`.
|
||
|
||
## Логотип сайта, писем и отправителя
|
||
|
||
Иконки сайта находятся в `public/branding/`, многоразмерный favicon —
|
||
в `public/favicon.ico`. Шаблон уведомлений использует
|
||
`public/branding/moonwell-mail-logo.png` как встроенное CID-изображение:
|
||
логотип прикрепляется к письму, а не загружается с чужого сервера.
|
||
|
||
Для значка рядом с отправителем подготовлен векторный логотип SVG Tiny P/S:
|
||
`https://moon-well.online/branding/moonwell-bimi.svg`.
|
||
В DNS-панели REG.RU добавляется запись:
|
||
|
||
```text
|
||
Тип: TXT
|
||
Имя: default._bimi
|
||
Значение: v=BIMI1; l=https://moon-well.online/branding/moonwell-bimi.svg;
|
||
```
|
||
|
||
Полное имя записи — `default._bimi.moon-well.online`. Нужен только один
|
||
BIMI TXT для этого имени. Запись применяется ко всем отправителям домена,
|
||
включая `support` и `no-reply`. Публиковать её следует после загрузки SVG
|
||
на production. Текущая политика DMARC `p=reject` соответствует требованию
|
||
BIMI; ослаблять её не требуется.
|
||
|
||
Это самостоятельная BIMI-запись без сертификата: наличие записи не
|
||
гарантирует отображение логотипа у получателя. Gmail требует дополнительно
|
||
VMC или CMC от удостоверяющего центра. После выпуска сертификата его цепочка
|
||
PEM размещается по HTTPS, а в TXT добавляется `a=https://…/certificate.pem`.
|
||
Обычный TLS-сертификат сайта или SMTP не заменяет VMC/CMC.
|
||
|
||
Требования: [Google Workspace](https://knowledge.workspace.google.com/admin/security/set-up-bimi),
|
||
[формат SVG BIMI Group](https://bimigroup.org/creating-bimi-svg-logo-files/).
|
||
|
||
Проверка после публикации DNS:
|
||
|
||
```bash
|
||
dig +short TXT default._bimi.moon-well.online
|
||
curl --fail --head https://moon-well.online/branding/moonwell-bimi.svg
|
||
```
|
||
|
||
## Резервное копирование
|
||
|
||
В бэкап должны попадать оба именованных volume:
|
||
|
||
- `stalwart-config`;
|
||
- `stalwart-data`.
|
||
|
||
Перед восстановлением используй ту же minor-версию образа Stalwart, что указана
|
||
в `compose.mail.yaml`.
|
||
|
||
## TLS на production-сервере
|
||
|
||
На production сертификат выпускает системный Certbot через Nginx. Контейнер
|
||
получает read-only каталог `MAIL_TLS_DIR` и использует:
|
||
|
||
```text
|
||
/run/stalwart-tls/fullchain.pem
|
||
/run/stalwart-tls/privkey.pem
|
||
```
|
||
|
||
Deploy-hook Certbot копирует обновлённые файлы в этот каталог и перезапускает
|
||
только контейнер Stalwart, чтобы он перечитал сертификат.
|
||
|
||
Hook устанавливается из `scripts/deploy-mail-certificate.sh`.
|
||
|
||
Certbot запускает deploy-hook для каждого обновлённого сертификата. Скрипт
|
||
проверяет, что сертификат покрывает `mail.moon-well.online`
|
||
(`MAIL_TLS_HOSTNAME`), и пропускает сертификаты других сайтов. Иначе
|
||
обновление сертификата основного сайта подменит сертификат SMTP/IMAP.
|
||
|
||
Внешний HTTPS-доступ к панели и JMAP проксируется через Nginx. Готовая
|
||
конфигурация находится в `ops/nginx-mail.conf`; внутренний HTTP-порт Stalwart
|
||
остаётся доступен только на `127.0.0.1`.
|
||
|
||
После установки сертификата его нужно добавить в Stalwart и выбрать как
|
||
сертификат по умолчанию. Для HTTP-настроек за Nginx также включается
|
||
`useXForwarded`, чтобы автоматические блокировки применялись к реальному
|
||
адресу клиента, а не к Docker gateway.
|
||
|
||
Если Nginx возвращает 502, а Stalwart работает и пишет
|
||
`security.ip-blocked ... remoteIp = <Docker gateway>`, проверь блокировку
|
||
адреса шлюза Docker в `Settings → Security → Blocked IPs`. Сними блокировку
|
||
только с этого адреса и добавь именно его в `Allowed IPs`, затем перезагрузи
|
||
настройки. Адрес шлюза можно узнать через `docker inspect`. HTTP-порт
|
||
Stalwart должен оставаться доступным только на loopback-интерфейсе хоста;
|
||
Nginx передаёт реальный адрес клиента в HTTP-заголовках. Не добавляй весь
|
||
интернет в разрешённые адреса и не отключай auto-ban.
|