Несколько DKIM-селекторов на одном домене: параллельная подпись от разных ESP без конфликтов
Amazon SES, Postmark, ваш Postfix и рассылочный ESP навязывают каждый свой DKIM-селектор — и инженеры зря пытаются «выбрать один главный ключ». Конфликта селекторов не существует. Разбираем, что ломается на самом деле: DKIM alignment в DMARC, лимит в 10 SPF-запросов и перезатёртые CNAME — и как развести N отправителей по селекторам и поддоменам так, чтобы все прошли alignment на p=reject.
EvilMail Team19 июля 2026 г.12 мин чтения
Онбордите новый ESP — Amazon SES для транзакционки, Postmark для чеков, Mailchimp для рассылки — и на каждом шаге мастер настройки просит добавить свой DKIM-селектор в зону. К третьему провайдеру возникает соблазн «навести порядок» и оставить один главный ключ. Не делайте этого. Вы решаете несуществующую проблему и по дороге ломаете доставку.
Конфликта DKIM-селекторов не существует в принципе. Селектор — это пространство имён, а не эксклюзивный слот на домене. На одном From-домене может параллельно жить сколько угодно ключей, и каждый отправитель подписывает своим. А вот то, что реально ломает мультипровайдерную отправку, находится в трёх других местах: DKIM alignment внутри DMARC, лимит 10 DNS-запросов в SPF и вопрос владения ротацией при CNAME-делегировании. Разберём по порядку — от модели данных в DNS до рабочей раскладки на p=reject.
Почему «конфликта селекторов» не существует
Заголовок DKIM-Signature несёт два тега, которые полностью определяют, откуда верификатор возьмёт ключ: d= (домен) и s=
Несколько DKIM-селекторов на домене: параллельная подпись разных ESP | evilmail — EvilMail Blog
(селектор). Публичный ключ лежит в TXT-записи по адресу
<selector>._domainkey.<domain>
. Приёмная сторона читает
s=
и
d=
прямо из заголовка конкретного письма и идёт ровно за одной записью. Ключ от SES под селектором
ses-euw1
и ключ от Postmark под
pm
живут в разных именах DNS и никогда не пересекаются.
То есть два, пять, десять селекторов сосуществуют без каких-либо взаимных помех. «Выбрать один главный» — это не консолидация, а отключение подписи у всех отправителей, кроме одного, после чего их письма теряют DKIM целиком.
Что ломается на практике, когда провайдеров становится несколько:
Alignment-фейлы — подпись криптографически валидна, но d= не совпадает с From-доменом, и DMARC падает.
SPF PermError — суммарное число include: от нескольких ESP превышает лимит в 10 DNS-запросов.
Перезатёртые CNAME — при онбординге нового ESP кто-то правит существующую делегирующую запись вместо добавления новой, и старый провайдер теряет ключ.
Цель, таким образом, не «один ключ», а N изолированных селекторов плюс гарантированный DMARC alignment для каждого источника.
2.Запрашивает TXT ses-euw1._domainkey.example.com и достаёт публичный ключ p=.
3.Применяет каноникализацию relaxed/relaxed к телу и к перечисленным в h= заголовкам.
4.Сверяет хеш тела с bh= и проверяет подпись b= публичным ключом.
Сама TXT-запись выглядит так:
text
ses-euw1._domainkey.example.com. 3600 IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...IDAQAB"
Поскольку у каждого провайдера свой s=, они физически не могут пересечься. Единственное, о чём нужно договориться заранее, — соглашение об именовании селекторов, чтобы два разных ESP случайно не запросили одно имя. Рабочая схема: ses-euw1 (провайдер + регион), pm (Postmark), mc1 (Mailchimp, с номером на случай второго аккаунта), s202607 — собственный ключ с датой ротации в имени. Дата в селекторе бесплатно даёт вам журнал: сразу видно, когда ключ создан и не пора ли его менять.
Alignment — то, ради чего всё затевается
Здесь живёт главная ловушка мультипровайдерной отправки. Authentication и alignment — разные вещи. DKIM «pass» не равно DMARC «pass».
DKIM-подпись валидна, если криптография сошлась — точка. DMARC добавляет второе требование: домен из d= должен быть выровнен с доменом из видимого заголовка From:. В режиме relaxed (по умолчанию, adkim=r) достаточно совпадения на уровне организационного домена — mail.example.com в d= выровнен с example.com во From. В strict (adkim=s) требуется точное совпадение.
Классический провал: на старте многие ESP подписывают письмо своим доменом. SES без настроенного домена ставит d=amazonses.com, Mandrill — d=mandrillapp.com. Подпись валидна, dkim=pass. Но amazonses.com не выровнен с вашим example.com, DMARC не засчитывает этот DKIM, и если у вас p=reject — письма отбиваются. Причём в спам они уходят «внезапно»: технически подпись зелёная, а причина не видна без чтения alignment-полей.
Чинится это одним действием: у провайдера настраивается verified / branded domain, после чего он начинает подписывать вашим d=. Ниже — механизм выравнивания по провайдерам:
Провайдер
Как достигается alignment
Что кладём в DNS
Amazon SES
Easy DKIM с верифицированным доменом → d= становится вашим
3 CNAME на *.dkim.amazonses.com
Postmark
DKIM для домена отправки
1 CNAME (или TXT) на их ключ
Mailchimp
Authenticated domain
CNAME на их запись
Свой Postfix + OpenDKIM
Подписываете сами вашим d=
Собственный TXT с публичным ключом
Поддоменная сегментация: чистый способ развести N отправителей
Зрелый подход — не сваливать всех отправителей в корневой домен, а разложить их по поддоменам:
mail.example.com — транзакционка через SES;
news.example.com — маркетинговые рассылки через ESP;
example.com — живая человеческая почта из вашего Postfix.
Три причины, почему это правильно:
Alignment всё равно проходит. При relaxed (adkim=r) mail.example.com выровнен с организационным доменом example.com, так что From для живой почты можно держать строгим по бренду ([email protected]), а транзакционку слать с [email protected].
Изоляция репутации. Сожжённая маркетинговая рассылка бьёт по репутации news.example.com, а не топит транзакционные письма и не роняет доставку писем сброса пароля.
SPF не суммируется. Лимит в 10 DNS-запросов считается на каждый домен отдельно. Разведя отправителей по поддоменам, вы не складываете все include: в одну запись.
На каждом поддомене — своя тройка: собственный DKIM-селектор, свой SPF и свой Return-Path.
SPF и Return-Path: где мультипровайдер реально конфликтует
Вот здесь настоящий конфликт, в отличие от DKIM. SPF проверяется по домену из MAIL FROM (Return-Path), а не из From:. И у SPF жёсткий потолок: суммарно не больше 10 DNS-запросов на разворачивание всех include, a, mx, ptr, exists. Уже include:amazonses.com + include:spf.mtasv.net (Postmark) + include:servers.mcsv.net (Mailchimp) плюс ваш собственный a/mx легко перебирают лимит. Результат — PermError, который большинство приёмников трактует как SPF fail.
Два рабочих выхода:
Кастомный Return-Path на поддомене провайдера. Настраиваете bounce-домен вида bounces.mail.example.com, куда провайдер кладёт свой SPF изолированно. Каждый ESP тогда «живёт» в своей SPF-записи, и ничего не суммируется.
SPF flattening — с осторожностью: разворачиваете include в конкретные IP-диапазоны, но берёте на себя обязанность следить за их изменениями у провайдера, иначе тихо отвалится часть отправки.
Важный нюанс: DMARC проходит, если сошёлся хотя бы один из двух механизмов — выровненный DKIM или выровненный SPF. При корректном DKIM alignment можно не выравнивать SPF вовсе. Но полагаться на один механизм не стоит: пересылка (forwarding) ломает SPF, а вот DKIM переживает её, поэтому именно DKIM — ваш несущий механизм, а SPF — страховка.
CNAME-делегирование против собственного TXT: кто владеет ротацией
Две модели публикации ключа, и выбор между ними — это выбор, кто владеет ротацией.
ESP-managed (CNAME). Провайдер даёт вам делегирующую запись:
text
abc123._domainkey.example.com. IN CNAME abc123.dkim.amazonses.com.
Провайдер ротирует приватный ключ у себя, вы не трогаете DNS. Удобно, но вы теряете видимость и контроль над графиком ротации. Для SES и Postmark это нормальный дефолт — им и положено управлять своими ключами.
Self-managed (TXT). Генерируете пару сами, публичную часть кладёте в TXT, приватную — на свой сервер. Полный контроль и ротация по вашему расписанию, но ручная работа. Для собственного Postfix + OpenDKIM это единственно верный путь:
bash
opendkim-genkey -b 2048 -s s202607 -d example.com
# создаёт s202607.private (приватный ключ) и s202607.txt (публичная TXT-запись)
Приватный ключ ложится в /etc/opendkim/keys/example.com/s202607.private, а маппинг домен → селектор → ключ прописывается в таблицах KeyTable и SigningTable, на которые ссылается /etc/opendkim.conf.
Железное правило: никогда не переиспользуйте один приватный ключ на двух системах. Каждая подписывающая система — свой селектор и своя пара ключей. Иначе компрометация одного сервера означает отзыв ключа сразу у всех отправителей.
Ротация ключей без даунтайма
Многоселекторность делает ротацию тривиальной за счёт overlap-стратегии:
1.Поднимаете новый селектор (s202607) параллельно старому (s202601) — обе TXT-записи в зоне одновременно.
2.Переключаете подписывающую систему на новый селектор.
3.Ждёте максимальный возраст очередных писем плюс TTL записи — на практике сутки при TTL 3600.
4.Удаляете старую TXT.
Почему это работает без потерь: письма, которые уже ушли и лежат в чужих очередях или в архивах с последующей перепроверкой, всё ещё верифицируются по старому селектору, пока вы его не сняли. В 2026 стандарт — 2048-битный RSA; 1024 бита считается слабым и его пора выводить. Ed25519 (k=ed25519) поддержан, но публикуйте его двойной подписью вместе с RSA, чтобы старые верификаторы не остались без валидного ключа.
В Authentication-Results смотрите не на голый dkim=pass, а на связку dkim=pass header.d=example.comиdmarc=pass. Если dkim=pass, а header.d= показывает домен провайдера — это ровно тот misaligned-случай из матрицы выше.
Дальше — DMARC aggregate-отчёты (RUA). В XML по каждому источнику есть <policy_evaluated> с полями dkim и spf, а в <auth_results> — фактический header.d. Источник с валидным DKIM, но failed alignment виден мгновенно: dkim в policy_evaluated стоит fail, хотя в auth_results подпись pass. Это и есть типичная причина «почему письма от нового ESP уходят в спам при p=reject». Включите fo=1 в DMARC-записи, чтобы получать failure-отчёты по каждому невыровненному механизму:
Каждый ESP получил собственный уникальный селектор; имена не пересекаются (ses-euw1, pm, mc1, s202607).
У каждого провайдера настроен verified / branded domain — d= в подписи ваш, а не amazonses.com / mandrillapp.com.
Проверено dkim=pass header.d=<ваш домен> в реальных заголовках от каждого источника, а не только от одного.
Отправители разведены по поддоменам; репутация и SPF-лимиты изолированы.
SPF на каждом домене укладывается в 10 DNS-запросов; bounce-домены на поддоменах провайдеров, где нужно.
Собственные ключи — 2048-бит, self-managed TXT; ESP-ключи — их CNAME. Ни один приватный ключ не переиспользован.
TTL на _domainkey-записях = 3600; готова overlap-процедура ротации.
DMARC стартовал с p=none + rua/fo=1, отчёты прочитаны, все источники выровнены — только после этого p=reject.
Сначала прогоните неделю на p=none и прочитайте aggregate-отчёты по каждому источнику. Если все N выравниваются — переключайте на p=reject. Селекторов может быть сколько угодно; значение имеет только то, что каждый из них проходит alignment.