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.13mail.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 без репутации. Отправьте несколько настоящих писем и подождите неделю.