Self-hosted почта: Postfix, Dovecot, CoreDNS, DKIM, SPF и DMARC
Published: 2026-06-10
Почта @antonnovikov.com хостится на том же VPS, что и сайт. Стек: Postfix (SMTP), Dovecot (IMAP), OpenDKIM (подписание), CoreDNS (авторитативный DNS), SnappyMail (вебмейл). Привести всё в рабочее состояние — особенно с точки зрения доставляемости — потребовало нескольких итераций.
Зачем хостить почту самостоятельно
Потому что хотелось. Почта — самое сложное, что можно хостить самому, этим она и была интересна. Практический результат: я контролирую MX-записи, ничего не идёт через Google или Microsoft, и я наконец понял, что на самом деле делают SPF, DKIM и DMARC.
DNS: CoreDNS как авторитативный сервер
Вместо DNS-хостинга у регистратора или Cloudflare 11-setup-dns.sh разворачивает CoreDNS внутри k0s-кластера как авторитативный nameserver для antonnovikov.com. Zone-файл хранится в ConfigMap:
zone@ IN SOA ns1.antonnovikov.com. me.antonnovikov.com. (
2026010101 ; Serial
3600 600 604800 3600
)
@ IN NS ns1.antonnovikov.com.
@ IN A 91.184.248.13
* IN A 91.184.248.13
mail IN A 91.184.248.13
@ IN MX 10 mail.antonnovikov.com.
Wildcard * IN A означает, что любой поддомен резолвится в IP сервера без добавления отдельных записей — удобно при начальной настройке.
Серийный номер SOA важен: downstream-резолверы используют его для определения изменений зоны. Увеличивайте его (формат YYYYMMDDNN) при каждом изменении, иначе изменения не распространятся.
SPF-запись
@ IN TXT "v=spf1 ip4:91.184.248.13 a mx -all"
| Механизм | Значение |
|---|---|
ip4:91.184.248.13 |
разрешить почту с этого IP |
a |
разрешить почту с IP из A-записи |
mx |
разрешить почту с того, на что указывает MX |
-all |
жёстко отклонить всё остальное |
-all — hardfail: любой сервер, не соответствующий политике, отклоняется сразу (не просто помечается как спам).
DKIM с OpenDKIM
13-setup-dkim.sh устанавливает OpenDKIM, генерирует пару RSA-ключей длиной 2048 бит и настраивает Postfix пропускать исходящую почту через milter OpenDKIM на порту 8891.
Публичный ключ добавляется в DNS как TXT-запись:
mail._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
При смене ключей: используйте новое имя селектора (например, mail2), чтобы старые подписи проходили проверку в период перехода. Убирайте старый селектор только когда уверены, что письма со старыми подписями уже не в пути.
Режим канонизации — relaxed/relaxed: обрабатывает небольшие изменения пробелов, которые вносят некоторые почтовые релеи, иначе подписи ломались бы на перенаправленных письмах.
Конфиг Postfix:
milter_protocol = 6
milter_default_action = accept
smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891
DMARC
_dmarc IN TXT "v=DMARC1; p=reject; rua=mailto:postmaster@antonnovikov.com;
pct=100; adkim=s; aspf=s; sp=reject; fo=1"
| Тег | Значение | Смысл |
|---|---|---|
p |
reject |
отклонять письма с ошибкой DKIM+SPF alignment |
rua |
mailto:... |
куда слать агрегированные отчёты |
pct |
100 |
применять политику к 100% писем |
adkim |
s |
строгое выравнивание DKIM |
aspf |
s |
строгое выравнивание SPF |
sp |
reject |
та же политика для поддоменов |
fo |
1 |
forensic-отчёты о любых сбоях |
Начинайте с p=none при тестировании, затем p=quarantine, затем p=reject — когда убедитесь, что SPF и DKIM проходят.
Вебмейл SnappyMail
SnappyMail — PHP-вебмейл, работающий в поде в namespace webmail. Подключается к Dovecot по IMAP на localhost (hostNetwork: true).
yamlspec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: snappymail
image: djmaze/snappymail:latest
volumeMounts:
- name: snappymail-data
mountPath: /var/lib/snappymail
volumes:
- name: snappymail-data
hostPath:
path: /opt/snappymail
type: DirectoryOrCreate
hostNetwork: true необходим, потому что Dovecot работает на хосте, а не внутри кластера. Без этого контейнер вебмейла не может достучаться до 127.0.0.1:143.
Postfix и Dovecot на хосте
Работают прямо на хосте (не в Kubernetes), потому что им нужен прямой доступ к порту 25 и mail spool:
bash# Тест отправки
echo "Test" | sendmail -v test@gmail.com
# Очередь писем
postqueue -p
# Принудительный flush очереди
postqueue -f
# Проверка аутентификации Dovecot
doveadm auth test user@antonnovikov.com password
Чеклист доставляемости
| Проверка | Команда |
|---|---|
| Публичный ключ DKIM в DNS | dig TXT mail._domainkey.antonnovikov.com |
| DMARC-политика в DNS | dig TXT _dmarc.antonnovikov.com |
| SPF-запись в DNS | dig TXT antonnovikov.com |
| PTR совпадает | dig -x 91.184.248.13 → mail.antonnovikov.com |
| Порт 25 открыт | telnet smtp.gmail.com 25 с VPS |
| TLS работает | openssl s_client -connect mail.antonnovikov.com:465 |
Без совпадающей PTR-записи Gmail и другие провайдеры будут отклонять исходящую почту или жёстко штрафовать её репутацию независимо от SPF/DKIM/DMARC.
Тестирование доставляемости
bash# Отправить в Gmail и проверить заголовки
echo "DKIM test" | sendmail -v test-account@gmail.com
# В Gmail: показать оригинал → ищите DKIM=pass, SPF=pass, DMARC=pass
Типичные причины отказов:
- "Message rejected" — PTR-запись отсутствует или неверна
- "SPF check failed" — отправка с IP, не включённого в SPF
- "DKIM signature invalid" — OpenDKIM не запущен или milter не подключён к Postfix
- "Попадает в спам" — новый IP без репутации. Отправьте несколько настоящих писем и подождите неделю.