Жанр оговорю сразу: это порядок диагностики, а не рассказ о том, что причина была такая-то. Я не буду утверждать, из-за чего именно письмо не дошло, — у меня нет подтверждения, и подставлять правдоподобную версию вместо факта смысла нет.

Зато есть последовательность, которая приводит к ответу, и в ней важен порядок.

Коротко

  • Успешное подключение к 25-му порту доказывает связность и больше ничего.
  • Причина отказа почти всегда записана в ответе принимающей стороны — он лежит в логах Postfix.
  • SPF, DKIM и DMARC проверяются на фактически отправленном письме, а не в DNS.
  • В логах почты есть персональные данные. Это надо учитывать до того, как переслать кому-то кусок лога.

Шаг первый: связность

Самое дешёвое — убедиться, что имя резолвится и до сервера вообще можно дойти.

dig +short A mail.example.com
nc -vz 203.0.113.25 25

Здесь и дальше адреса демонстрационные.

И сразу главное про этот шаг. Успешное подключение к 25-му порту означает ровно одно: сетевая связность есть. Оно ничего не говорит о том, примет ли принимающая сторона ваше письмо.

Отказ в доставке и отказ в соединении — разные события. Соединение может устанавливаться идеально, а письмо отвергаться после команды RCPT TO или вообще после приёма — по репутации, по подписи, по содержимому. Крупные почтовые системы отвергают охотнее всего именно на этом этапе, когда с сетью всё хорошо.

Поэтому зелёный результат nc — это не «всё в порядке», а «переходим к следующему шагу». Я подчёркиваю это, потому что соблазн остановиться здесь велик: проверка простая, результат наглядный, и очень хочется признать её ответом.

Шаг второй: что ответила принимающая сторона

Вот здесь и находится настоящий диагноз.

Когда сервер получателя отвергает письмо, он объясняет причину — кодом и текстом. Postfix записывает этот ответ в лог дословно. То есть ответ на вопрос «почему не дошло» обычно уже написан, и его надо не вычислять, а прочитать.

docker compose logs --tail=2000 postfix-mailcow | grep -i 'status=bounced\|status=deferred'

Разница между двумя статусами существенная:

  • deferred — временный отказ, сервер получателя предлагает попробовать позже. Письмо ещё не потеряно, оно в очереди.
  • bounced — окончательный отказ. Письмо не будет доставлено, и отправитель получил уведомление.

Первое состояние даёт время. Второе означает, что разбираться надо было раньше.

Текст ответа в строке лога стоит читать целиком, а не по ключевым словам. Именно там принимающая сторона обычно называет причину и часто даёт ссылку на своё описание этой причины.

Шаг третий: SPF, DKIM и DMARC — на письме, а не в DNS

Проверить записи в DNS недостаточно. Записи могут быть верными, а письмо всё равно не пройдёт проверку: подпись может не проставляться, отправка может идти с адреса, которого нет в SPF, домен в подписи может не совпадать с доменом отправителя.

Правильная проверка — на фактически отправленном письме. Отправьте его на ящик в любой крупной системе и посмотрите заголовки полученного: там будет записано, прошли ли проверки и с каким результатом.

DNS при этом проверяется как вспомогательный шаг, чтобы понять, есть ли вообще что проверять:

dig +short TXT example.com
dig +short TXT dkim._domainkey.example.com
dig +short TXT _dmarc.example.com

Порядок здесь тот же, что и во всей статье: сначала факт о конкретном письме, потом объяснение из конфигурации. Наоборот — путь к правдоподобным версиям.

Шаг четвёртый: кто, кому и сколько

Когда вопрос шире одного письма — например, надо понять, что вообще происходит с отправкой, — смотреть надо поток целиком: входящие, исходящие, отправителей, получателей.

docker compose logs --tail=5000 postfix-mailcow | grep -oE 'from=<[^>]*>' | sort | uniq -c | sort -rn | head
docker compose logs --tail=5000 postfix-mailcow | grep -oE 'to=<[^>]*>' | sort | uniq -c | sort -rn | head

Это полезно не только при разборе инцидента. Всплеск исходящих с одного ящика — типичный признак того, что учётной записью пользуется не её владелец, и заметить его можно только глядя на распределение, а не на отдельные письма.

И отдельно: в логах почты есть персональные данные

Это то, о чём легко забыть в разгар разбора.

В строках лога — адреса отправителей и получателей, темы писем, размеры, время. Это персональные данные и деловая переписка компании, даже если самих писем в логе нет.

Практические следствия:

  • Не пересылайте куски логов в мессенджеры и на форумы «как есть». Перед тем как показать строку постороннему — в том числе в вопросе к поддержке или в статью вроде этой, — адреса надо заменить.
  • Выгрузки логов на рабочий стол имеют свойство оставаться там. Файл, сделанный «на пять минут для разбора», живёт годами и переживает несколько переустановок.
  • Доступ к логам — это доступ к метаданным переписки всей компании. Кто с кем и когда переписывался — иногда более чувствительная информация, чем содержание.

Я написал этот раздел последним, а думать о нём надо первым: разбор доставки почти всегда начинается с того, что кто-то копирует кусок лога и отправляет его коллеге.

Что из этого стоит запомнить

Порядок проверок важнее их количества. Связность, потом ответ принимающей стороны, потом проверки подписей на реальном письме. Каждый следующий шаг отвечает на вопрос, который предыдущий не закрывает.

И главное: успешная простая проверка — самый удобный способ обмануть себя. Открытый порт, правильная запись в DNS, работающий ping — всё это приятно видеть и всё это отвечает не на тот вопрос, который вы задали.