Синтаксис SPF-записи: механизмы, квалификаторы, модификаторы и макросы
Синтаксис SPF-записи (SPF record syntax) подчиняется единому правилу: это одна DNS TXT-запись, начинающаяся с v=spf1, за которой через пробелы следуют термины — механизмы с необязательными квалификаторами, а затем модификаторы — разбираемые слева направо до первого совпадения. Вот пример полной записи:
v=spf1 ip4:192.0.2.0/24 include:_spf.example.com -all
Одна строка авторизует подсеть /24 и серверы стороннего провайдера, а всё остальное отклоняет. Все правила, которым она подчиняется, закреплены в RFC 7208 — стандарте SPF, опубликованном в апреле 2014 года.
Эта страница — полный справочник: все механизмы, все квалификаторы, оба модификатора, полная таблица макросов, порядок вычисления, ограничения DNS-запросов и правила размещения записи — каждый пункт со ссылкой на соответствующий раздел RFC 7208. Если вам сначала нужны основы протокола — зачем существует SPF и как он соотносится с DKIM и DMARC — начните с нашего /learn/spf/[руководства по SPF]. Если вы создаёте и поддерживаете записи — добавьте эту страницу в закладки.
Структура SPF-записи
SPF-запись — это одна строка текста в поле RDATA одной TXT-записи. Её грамматика содержит ровно три вида элементов: тег версии, механизмы (каждый с необязательным квалификатором) и модификаторы. Тег версии должен быть в точности v=spf1 — запись, начинающаяся с v=spf10, отбрасывается целиком, без частичного разбора (§4.5).
Краткая грамматика по §3 и §4.6.1:
| Элемент | Форма | Роль |
|---|---|---|
Версия |
|
Идентифицирует запись (§4.5) |
Механизм |
|
Сверяется с IP-адресом клиента; может совпасть или не совпасть (§4.6.2) |
Квалификатор |
|
Результат, возвращаемый при совпадении механизма (§4.6.2) |
Модификатор |
|
Дополнительная информация; в сопоставлении не участвует (§6) |
Термины разделяются пробелами. Имена механизмов нечувствительны к регистру; термины, не содержащие ни =, ни :, ни /, считаются механизмами (§4.6.1). Единственная синтаксическая ошибка в любом месте делает всю запись недействительной: функция check_host() — процедура вычисления SPF на стороне получателя, как её называет RFC — сначала проверяет синтаксис и при любой ошибке немедленно возвращает PermError, не обрабатывая ни одного термина (§4.6). Именно поэтому один лишний символ способен нарушить аутентификацию для всех сообщений домена.
Механизмы
Существует восемь механизмов, каждый из которых либо совпадает с подключающимся IP-адресом, либо нет. RFC 7208 §5 делит их на базовые механизмы фреймворка (all, include) и механизмы явного указания отправителей (designated-sender mechanisms): a, mx, ptr, ip4, ip6, exists. Прежде чем перейти к полной таблице: большинство реальных записей используют только include, ip4, ip6 и all — остальная часть этого справочника нужна для того, чтобы вы могли читать чужие записи, а не потому что ваши собственные обязательно в них нуждаются.
| Механизм | Синтаксис | Совпадает, когда… | Учитывается в лимите 10 запросов? | RFC § |
|---|---|---|---|---|
|
|
Всегда |
Нет |
§5.1 |
|
|
Указанная запись возвращает Pass |
Да |
§5.2 |
|
|
IP клиента входит в A/AAAA-адреса домена |
Да |
§5.3 |
|
|
IP клиента является адресом одного из MX-хостов домена |
Да (плюс отдельные ограничения на адресные запросы MX) |
§5.4 |
|
|
Обратный DNS указывает на целевой домен |
Да |
§5.5 |
|
|
IP клиента входит в указанную IPv4-сеть |
Нет |
§5.6 |
|
|
IP клиента входит в указанную IPv6-сеть |
Нет |
§5.6 |
|
|
Составленный домен имеет хотя бы одну A-запись |
Да |
§5.7 |
Разобрать любую опубликованную запись по механизмам — с указанием DNS-стоимости каждого и ссылкой на RFC — можно с помощью /tools/spf-syntax-inspector/[инспектора синтаксиса SPF].
all
all совпадает всегда, поэтому его место — в конце записи как явное правило по умолчанию (§5.1). Всё, что стоит после него, — мёртвый текст: «Механизмы, перечисленные после all, ДОЛЖНЫ игнорироваться», а модификатор redirect= игнорируется всякий раз, когда all встречается в записи хоть где-нибудь (§5.1). Запись без завершающего all или redirect= молча даёт результат Neutral (§4.7).
include
include:domain рекурсивно вычисляет SPF-запись указанного домена и совпадает только тогда, когда это вычисление возвращает Pass (§5.2). Это не склейка записей. Сам RFC признаёт, что название «было выбрано неудачно»: -all внутри включённой записи не проваливает вашу внешнюю запись — Fail, Softfail или Neutral внутри просто означает «совпадения здесь нет, продолжаем» (§5.2). Лучшая аналогия: if-match. Важное следствие: если включаемый домен не публикует SPF-запись вообще, include возвращает PermError (§5.2).
a и mx
a совпадает, когда IP клиента является одним из A- или AAAA-адресов целевого домена; mx — когда он является адресом одного из MX-хостов домена (§5.3, §5.4). Оба по умолчанию применяются к текущему домену, если аргумент не указан, и оба принимают двойные CIDR-суффиксы: a/24 сравнивает только первые 24 бита, а a:example.com/24//64 задаёт IPv4- и IPv6-префиксы отдельно (§5.3). Обратите внимание на асимметрию стоимости: mx — это один термин в счёт лимита 10 запросов, однако его вычисление порождает один MX-запрос плюс адресный запрос для каждого MX-хоста, при этом допускается не более 10 адресных записей — иначе возвращается PermError (§4.6.4).
ip4 и ip6
ip4: и ip6: проверяют, входит ли IP клиента в указанную сеть; синтаксис использует двоеточие — ip4:192.0.2.0/24 — но никак не знак равенства (§5.6). Если длина CIDR не указана, по умолчанию применяются /32 и /128 соответственно, то есть точное совпадение адреса; сокращённые адреса вида 192.0.2 не допускаются (§5.6). Это единственные механизмы явного указания отправителей с нулевой DNS-стоимостью — самые дешёвые термины в любой записи.
exists
exists:domain формирует доменное имя, запрашивает для него A-запись и совпадает, если любая A-запись возвращается — вне зависимости от её значения, и всегда именно A-запрос, даже при IPv6-подключениях (§5.7). Один запрос, произвольная логика. В сочетании с макросами (§7) это позволяет реализовать динамическую авторизацию: публикуйте в подконтрольной зоне имена хостов для каждого IP, и запись авторизует именно эти IP, не перечисляя их явно. Именно этот подход используется в действующей записи Salesforce — пример приведён ниже.
ptr (не использовать)
Само название раздела §5.5 в RFC 7208 гласит буквально "ptr" (do not use): механизм «НЕ ДОЛЖЕН публиковаться», поскольку он медленный, ненадёжен при DNS-ошибках и создаёт нагрузку на серверы имён .arpa. Получатели обязаны поддерживать его, но вы никогда не должны его публиковать.
41 728 доменов — 1,4% SPF-совместимых доменов — по-прежнему публикуют устаревший механизм ptr.
Следующий шаг: вставьте свою запись в /tools/spf-syntax-inspector/[инспектор синтаксиса SPF] и убедитесь, что каждый её механизм оправдывает свою DNS-стоимость.
Квалификаторы
Квалификатор (qualifier) — это один символ-префикс, определяющий результат, который возвращается при совпадении механизма. Если квалификатор не указан, по умолчанию используется + (§4.6.2). mx и +mx — один и тот же термин.
| Квалификатор | Результат | Значение (§2.6) |
|---|---|---|
|
Pass |
Клиент авторизован (по умолчанию, когда квалификатор опущен) |
|
Fail |
Клиент явно не авторизован |
|
Softfail |
Вероятно, не авторизован; домен не готов заявить жёсткий Fail |
|
Neutral |
Домен ничего не утверждает об этом клиенте |
На практике выбор сводится к финальному термину: -all или ~all. Получатели обрабатывают их по-разному, а пересылка почты (форвардинг) дополнительно усложняет картину — подробно мы разбираем этот компромисс в статье /blog/spf-softfail-vs-hardfail/[softfail vs hardfail].
Если вы также применяете DKIM и DMARC — публикуйте -all; если вы ещё выясняете, какие отправители используют ваш домен, — начните с ~all и ужесточьте политику, когда /blog/spf-softfail-vs-hardfail/[статья о softfail vs hardfail] поможет определиться.
Модификаторы
Модификаторы — это пары имя=значение, всегда через знак равенства, никогда через двоеточие; они предоставляют информацию, а не участвуют в сопоставлении (§6). RFC 7208 определяет два модификатора, каждый допускается не более одного раза: дублирование redirect= или exp= является ошибкой PermError, тогда как нераспознанные модификаторы игнорируются, сколько бы раз они ни встречались (§6).
redirect=
redirect=domain передаёт всё вычисление записи другого домена — но только после того, как ни один механизм не совпал (§6.1). Результат той записи становится вашим результатом, с одним уточнением: там, где отсутствие записи обычно даёт None, redirect= на домен без SPF-записи даёт PermError (§6.1). Два правила, которые часто упускают: redirect= засчитывается как один DNS-запрос (§4.6.4), и он «ДОЛЖЕН игнорироваться», когда где-либо в записи присутствует механизм all (§6.1).
Отличия от include:
| Поведение | include: (§5.2) |
redirect= (§6.1) |
|---|---|---|
Совпадает при |
Только Pass; иначе вычисление продолжается |
Не механизм; применяется после всех промахов |
Эффект результата указанной записи |
Только Pass передаётся как совпадение |
Результат полностью заменяет ваш |
При наличии |
Не затрагивается |
Игнорируется |
Указанная запись отсутствует |
PermError |
PermError |
Предназначение |
Пересечение административных границ |
Совместная политика для собственных доменов |
exp=
exp=domain указывает TXT-запись, макро-развёрнутая строка которой возвращается как пояснение при результате Fail (§6.2). Это единственный термин в языке, который никогда не засчитывается в счёт лимита запросов — §4.6.4 явно освобождает exp от этого ограничения, поскольку его запрос происходит после вычисления и только при Fail.
Макросы
Макросы — это последовательности %{…}, раскрываемые во время вычисления из свойств сообщения. Несмотря на то что они полностью определены в RFC 7208 §7, это наименее изученная часть синтаксиса SPF-записей. ABNF определяет ровно одиннадцать символов макросов: s, l, o, d, i, p, h, c, r, t, v (§7.1).
| Макрос | Раскрывается в (§7.2) | Примечания |
|---|---|---|
|
Полный адрес отправителя, например |
|
|
Локальная часть адреса отправителя ( |
Ограничивает кэширование (§7.3) |
|
Домен отправителя |
|
|
Вычисляемый домен |
|
|
IP-адрес подключающегося клиента |
Четыре октета для IPv4 / побайтовые nibble-значения для IPv6 (§7.3) |
|
Подтверждённое обратным DNS имя для данного IP |
«не использовать» (§7.2, §5.5) |
|
|
|
|
Домен из команды HELO/EHLO |
|
|
IP-адрес клиента в удобочитаемом виде |
Только в тексте |
|
Домен хоста, выполняющего проверку |
Только в тексте |
|
Текущая метка времени (секунды с начала эпохи) |
Только в тексте |
Три литеральных escape-последовательности дополняют набор: %% — знак процента, %_ — пробел, %- — URL-кодированный пробел (§7.1).
Трансформаторы
Трансформаторы изменяют раскрытие внутри фигурных скобок (§7.3). Цифра оставляет только указанное количество правых частей: %{d2} для email.example.com даёт example.com. Буква r переворачивает части по точкам: при IP клиента 192.0.2.1 %{i} даёт 192.0.2.1, а %{ir} — 1.2.0.192. Пользовательские разделители позволяют разбивать по другим символам: %{l-} разбивает локальную часть по дефисам, а части всегда воссоединяются через точки.
Развёрнутый пример из самого RFC в §7.4 связывает всё воедино. При IP клиента 192.0.2.3 и домене email.example.com:
%{ir}.%{v}._spf.%{d2}
→ 3.2.0.192.in-addr._spf.example.com
Сочетая это с exists:, вы получаете поимённую авторизацию в одном запросе — пример из §5.7 v=spf1 exists:%{ir}.%{l1r+-}._spf.%{d} -all принимает решения «на уровне пользователя и IP-адреса клиента». Итак, что означает %{i} в SPF-записи? Это IP-адрес подключающегося клиента — в формате с точками для IPv4 и в побайтовом nibble-формате для IPv6 — и именно этот макрос обеспечивает работу динамических записей шлюзов (§7.2). Раскрыть любой макрос для реального IP можно с помощью /tools/spf-macro-debugger/[отладчика SPF-макросов].
Предупреждение из самого RFC: записи, использующие макросы s, l, o или h, исключают кэширование результатов, поэтому §7.3 не рекомендует отправляющим доменам использовать их в директивах механизмов. Прежде чем публиковать любой макрос, раскройте его для реального IP-адреса клиента в /tools/spf-macro-debugger/[отладчике SPF-макросов] и убедитесь, что сформированное имя разрешается так, как ожидается.
Порядок вычисления и правило первого совпадения
Вычисление идёт по принципу первого совпадения (first-match-wins): механизмы проверяются слева направо, первое совпадение завершает обработку, а его квалификатор становится результатом (§4.6.2). Порядок — это не стиль, это семантика.
Проследим путь IP-адреса 198.51.100.7 через следующую запись:
v=spf1 ip4:192.0.2.0/24 -a mx ~all
1. ip4:192.0.2.0/24 → 198.51.100.7 не входит в диапазон → нет совпадения, продолжаем
2. -a → IP не входит в A-записи домена → нет совпадения, продолжаем
3. mx → IP совпадает с адресом MX-хоста → СОВПАДЕНИЕ — останавливаемся
Результат: Pass (неявный "+" совпавшего механизма)
~all так и не достигается. Теперь обратный случай: если бы IP совпал на шаге 2, результатом был бы Fail — и ни один более поздний механизм не смог бы это исправить. Квалификатор - в начале записи окончателен для любого IP, который он затрагивает, независимо от того, что мог бы сказать include правее. Это также напрямую отвечает на вопрос о порядке: да, порядок механизмов имеет значение, потому что вычисление останавливается при первом совпадении (§4.6.2).
Если ничего не совпало и нет redirect=, результатом по умолчанию является Neutral — «как если бы ?all было указано последней директивой» (§4.7). RFC всё равно рекомендует явно завершать запись через all или redirect= — неявные значения по умолчанию усложняют отладку.
Лимит DNS-запросов (10 запросов и пустые ответы)
Правило, дословно из §4.6.4: «Реализации SPF ДОЛЖНЫ ограничивать общее количество таких термов до 10 в ходе вычисления SPF… Если этот лимит превышен, реализация ДОЛЖНА вернуть 'permerror'.» Перечень учитываемых термов полностью определён:
| Термин | Учитывается в лимите 10? | Дополнительные ограничения (§4.6.4) |
|---|---|---|
|
Да |
Вложенные запросы внутри него тоже учитываются |
|
Да |
— |
|
Да |
≤10 адресных запросов (A/AAAA) на одно вычисление MX, иначе PermError |
|
Да |
≤10 адресных запросов на одно вычисление PTR; избыточные записи игнорируются |
|
Да |
— |
|
Да |
— |
|
Нет |
DNS-запросов не выполняют |
|
Нет |
Запрашивается позже, только при Fail — явно освобождён |
Существует второй, отдельный лимит, который большинство справочников обходит стороной: пустые запросы (void lookups). Запросы, не возвращающие ответа (RCODE 0 с нулевым количеством записей или NXDOMAIN), ДОЛЖНЫ быть ограничены двумя, при этом два — рекомендуемое значение по умолчанию; «превышение лимита даёт результат 'permerror'» (§4.6.4). Запись, полная устаревших include, может получить PermError из-за пустых запросов задолго до того, как будут исчерпаны все десять.
148 655 из 3 077 219 SPF-совместимых доменов — 4,8% — превышают лимит в 10 запросов и рискуют получать PermError для каждого отправляемого сообщения.
Если ваша запись превышает лимит, пошаговый способ его сократить описан в статье /blog/spf-too-many-dns-lookups/[устранение ошибки «слишком много DNS-запросов в SPF»].
Одна запись, только TXT
Доменное имя «НЕ ДОЛЖНО иметь несколько записей, которые привели бы к выбору более одной записи при проверке авторизации» (§3.2) — и последствие сформулировано явно, без размытых формулировок: когда при выборе записи обнаруживается более одной v=spf1-записи, check_host() «выдаёт результат 'permerror'» (§4.5). Две SPF-записи не объединяются, не конкурируют и не работают частично. Они просто ломают аутентификацию. Если у вас две записи — объедините механизмы из них в одну.
«SPF-записи ДОЛЖНЫ публиковаться ТОЛЬКО как DNS TXT (тип 16) Resource Record (RR)» (§3.1). Специальный тип записи SPF (тип 99) из экспериментального периода SPF был упразднён рабочей группой SPFbis, которая пришла к выводу, что модель с двумя типами записей «была принципиально ошибочной» (§3.1).
107 646 доменов по-прежнему публикуют устаревшую SPF-запись типа 99.
Записи длиннее 255 символов
Это правило чаще всего понимают неправильно — и поисковые результаты здесь особенно ненадёжны. Ограничение в 255 октетов относится к одной строке символов (character-string) внутри TXT-записи, но не ко всей записи. TXT-запись может содержать несколько строк в кавычках, и §3.3 недвусмысленен: «Если опубликованная запись содержит несколько строк символов, запись ДОЛЖНА обрабатываться так, как если бы эти строки были конкатенированы без добавления пробелов.» Таким образом, запись:
example.com. IN TXT "v=spf1 ip4:192.0.2.0/24 include:_spf.exam" "ple.com -all"
вычисляется как v=spf1 ip4:192.0.2.0/24 include:_spf.example.com -all — обратите внимание: разрыв приходится на середину токена, но конкатенация его устраняет, поскольку пробел не вставляется. Именно так правильно публиковать запись длиннее 255 символов. Реальный практический предел находится в другом месте: §3.4 рекомендует держать весь DNS-ответ достаточно маленьким, чтобы он умещался в UDP-пакет в 512 октетов (примерно 450 октетов для имени и записей), поскольку слишком большие ответы могут молча отбрасываться промежуточными устройствами, некорректно обрабатывающими DNS по TCP.
Примеры реальных записей
Четыре аннотированные записи — от минимальной до основанной на макросах. Количество запросов было подсчитано путём последовательного обхода каждой цепочки с помощью dig +short TXT в живом DNS — записи провайдеров меняются (Google упростил _spf.google.com с 4 запросов до 1 в декабре 2025 года), поэтому всегда проверяйте самостоятельно, а не полагайтесь на устаревшие данные из блогов.
1. Один провайдер (собственная опубликованная строка Google — Set up SPF):
v=spf1 include:_spf.google.com ~all
Читается как: авторизовать серверы Google; всё остальное — Softfail. Стоимость: 1 запрос — _spf.google.com теперь разрешается в плоский список ip4/ip6. /blog/google-workspace-spf-record/[Руководство по SPF-записи для Google Workspace] охватывает полное раскрытие этого include, объединение записей для сторонних отправителей и вопрос ~all против -all.
2. Провайдер плюс собственный почтовый сервер:
v=spf1 ip4:192.0.2.10 mx include:_spf.google.com -all
Читается как: авторизовать один фиксированный IP, MX-хосты домена и Google; всё остальное — Fail. Стоимость: 2 запроса (mx, include — ip4 бесплатен).
3. Несколько SaaS-провайдеров — Google плюс Microsoft 365 (комбинация, которую Google дословно публикует в своей документации):
v=spf1 include:_spf.google.com include:spf.protection.outlook.com ~all
Читается как: авторизовать обоих провайдеров; всё остальное — Softfail. Стоимость: 2 запроса — оба include сейчас разрешаются в плоские списки адресов.
4. Динамическая авторизация на основе макросов (действующая запись Salesforce для _spf.salesforce.com):
v=spf1 exists:%{i}._spf.mta.salesforce.com -all
Читается как: для каждого подключения проверять, есть ли A-запись у <ip-клиента>._spf.mta.salesforce.com; если да — Pass, иначе — Fail. Один запрос exists авторизует целый парк серверов, не перечисляя ни одного адреса явно — шаблон §5.7 в действующем производственном использовании.
Хотите собрать свою запись? /tools/spf-generator/[Генератор SPF] создаёт запись на основе ваших провайдеров, а /tools/spf-checker/[проверщик SPF] валидирует результат по всем правилам этой страницы.
Распространённые синтаксические ошибки
Реальные синтаксические ошибки встречаются реже, чем предполагает маркетинг поставщиков — и честные цифры заслуживают внимания. Крупнейшее рецензируемое исследование конфигурации SPF показало, что только 2,9% из 7 251 736 SPF-совместимых доменов имели вообще какие-либо ошибки, а истинные синтаксические ошибки составляли 18,15% из них — примерно 0,5% всех SPF-совместимых доменов (Czybik et al., ACM IMC 2023, arxiv.org/abs/2502.08240). Важно разделять категории: синтаксические ошибки, ошибки PermError из-за превышения лимита запросов и слишком мягкая политика — это три разные проблемы.
Встречающиеся ошибки при этом вполне предсказуемы. Каждая строка таблицы ниже — это наблюдаемый класс ошибок с долей среди синтаксических ошибок по данным исследования IMC 2023:
| Ошибка | Неправильно | Правильно | Правило |
|---|---|---|---|
|
|
|
§5.6 определяет |
Пробел после двоеточия (16,6%, IMC 2023) |
|
|
ABNF §4.6.1 пробел не допускает |
|
|
|
Механизмы принимают |
Более одной |
Две TXT-записи |
Одна объединённая запись |
§3.2 / §4.5 → PermError |
Термины после |
|
|
§5.1 — молча игнорируются |
SPF объединён со строкой верификации (7,0%, IMC 2023) |
|
Отдельные TXT-записи |
Механизм выбора §4.5 ломается |
Путаница = вместо : задокументирована Microsoft как одна из наиболее распространённых ошибок, с которыми сталкиваются её службы поддержки, наряду с лишними пробелами и завершающими точками (документация Microsoft по SPF, актуально на 2026-07-28).
Если в ваших DMARC-отчётах уже появляется PermError и вам нужен путь к диагностике, а не справочный материал, этот процесс описан в статье /blog/spf-permerror-fix/[устранение SPF PermError].
Часто задаваемые вопросы
Можно ли иметь две SPF-записи для одного домена?
Нет. RFC 7208 допускает ровно одну SPF-запись для одного доменного имени (§3.2), и когда при выборе записи обнаруживается более одной TXT-записи v=spf1, результатом является PermError (§4.5) — аутентификация не проходит для каждого сообщения. Вместо этого объедините механизмы из обеих записей в одну запись v=spf1.
В чём разница между include: и redirect=?
include: совпадает только тогда, когда указанная запись возвращает Pass, и вычисление продолжается если нет (§5.2). redirect= применяется только после того, как все механизмы не совпали, полностью заменяет результат вашей записи результатом указанного домена и игнорируется, если в записи присутствует механизм all (§6.1).
Какие механизмы учитываются в лимите 10 DNS-запросов?
Согласно RFC 7208 §4.6.4, учитываются include, a, mx, ptr и exists, а также модификатор redirect. Механизмы ip4, ip6 и all и модификатор exp не выполняют DNS-запросов при вычислении и освобождены от лимита. Дополнительно действует отдельный лимит на два пустых запроса.
Что означает %{i} в SPF-записи?
%{i} — это макрос, раскрывающийся в IP-адрес подключающегося клиента: в формате с четырьмя октетами для IPv4 и в побайтовом nibble-формате для IPv6 (§7.2, §7.3). Чаще всего он используется совместно с exists: для формирования имён хостов по IP-адресу, что позволяет одним запросом авторизовать динамическое множество отправителей.
Имеет ли порядок механизмов значение?
Да. Механизмы вычисляются слева направо, первое совпадение завершает обработку, а квалификатор этого механизма становится результатом (§4.6.2). Ранний квалификатор - окончателен для любого IP, который он затрагивает — ни один последующий include не может его переопределить, — а термины после all никогда не вычисляются (§5.1).
Как публиковать SPF-запись длиннее 255 символов?
Разбейте её на несколько строк в кавычках не более 255 байт каждая внутри одной TXT-записи. Получатели обязаны конкатенировать строки без добавления пробелов (§3.3), так что разрыв может приходиться даже на середину токена. Держите весь DNS-ответ в пределах примерно 450 октетов, чтобы он умещался в один UDP-пакет (§3.4).
Итог
Синтаксис SPF-записи требует точности, и весь стандарт сводится к короткому списку правил, которые стоит запомнить:
Одна запись, только TXT. Ровно одна TXT-запись v=spf1 для каждого имени; вторая — это PermError, а не объединение (§3.1, §3.2).
Первое совпадение — финальное. Слева направо, остановка на первом совпавшем механизме, возврат его квалификатора (§4.6.2). Порядок — это политика.
Следите за бюджетом запросов. Десять DNS-запрашивающих термов и два пустых запроса — превышение любого из лимитов приводит к отказу для каждого сообщения (§4.6.4).
Макросы — стандарт, а не фольклор. Одиннадцать символов из §7 обеспечивают работу динамических записей, которые Salesforce использует в продакшне прямо сейчас.
Когда вы будете готовы увидеть, как ваша запись работает в реальных потоках почты, агрегированные отчёты DMARCguard покажут, какие источники проходят проверку, какие нет и что именно нужно изменить.