Письма с 2FA кодами находятся на пересечении нескольких проблемных зон доставки. Они короткие, срочные, содержат цифры и отправляются повторно. Каждый из этих признаков срабатывает на спам-эвристики, которые фильтры совершенствовали годами.
Когда код попадает в спам, пользователь не входит вообще. Нет никакой graceful degradation. Нет возможности «повторить попытку после перезагрузки». А учитывая, что код живёт 5–10 минут, даже малейшая задержка доставки — это практически гарантированный отказ в доступе.
Коды 2FA — критичный путь. Код в спаме останавливает ваше приложение полностью. Отправляйте их с отдельного, максимально защищённого поддомена, с собственным DKIM и жёсткой инфраструктурой. Это не часть маркетинговой рассылки.
Какие эвристики спама срабатывают против 2FA
- Короткое тело письма. «Ваш код подтверждения: 382910. Срок действия 10 минут.» Две строки, одна цифра, слово «срочно». Выглядит как учебный пример фишингового письма, на котором учили байесовский классификатор.
- Код в строке темы. «Ваш код: 382910» запускает anti-phishing эвристику в Gmail, которая специально ловит строки похожие на учетные данные в теме письма.
- Быстрые повторные отправки. Пользователь три раза кликнул «выслать код заново» — вы отправили три почти идентичных короткихписьма с одного адреса одному получателю за 60 секунд. Это срабатывает на частотные эвристики.
- Язык срочности. «подтвердить», «защита», «истекает», «немедленно» — все эти слова есть в фишинговых базах знаний фильтров.
Когда 2FA не работает, пользователь не думает «спам-фильтр съел письмо». Он думает «приложение говно». Он удаляет приложение, оставляет отрицательный отзыв или инициирует chargeback. Цена одного упущенного кода 2FA измеряется потеряными пользователями и проблемами с репутацией, а не тикетами в поддержку.
Как измерять размещение 2FA на практике
Запустите боевую цепочку 2FA на seed-ящики, зафиксируйте размещение по каждому провайдеру и время доставки. Код, который пришёл через 3 минуты после истечения 5-минутного срока действия, бесполезен, даже если технически он в инбоксе.
# Триггерим 2FA на seed-адреса
for seed in seeds.list(group='2fa-test'):
start = time.now()
trigger_2fa(seed.address)
delivery = poll_placement(seed.address, timeout=120)
print({
"provider": seed.provider,
"folder": delivery.folder,
"seconds": (delivery.arrived_at - start).total_seconds()
})
# Падаем при:
# - любой seed в "спаме"
# - доставка > 30 секундПриемлемые метрики
- Размещение. 99%+ в инбоксе на Gmail, Outlook, Yahoo, iCloud.
- Латенция. Менее 15 секунд на p95, менее 30 секунд на p99.
- Доставка при переотправке. То же размещение и та же скорость при второй и третьей отправке одному и тому же получателю.
Исправления в порядке убывания влияния
- Отдельный поддомен и отправитель.
auth.yourapp.comс собственным DKIM-селектором. Никогда не делитесь с маркетингом и уведомлениями. - Переделайте шаблон письма. Добавьте контекст, которого нет в фишинговых письмах: имя пользователя, название браузера или устройства, из которого идёт попытка входа, город по IP. Это отличает легитимную 2FA от фишинга уже в теле письма.
- Не кладите код в тему письма. Тема: «Запрос входа в ваш аккаунт». Код в теле. Gmail'ская anti-phishing эвристика, которая ловит коды в темах, будет срабатывать, если вы положите код там.
- Добавьте text/plain версию. HTML-only письма выглядят тощими. Добавьте простотекстовую часть с кодом, одной строкой объяснения и контактом поддержки.
- Используйте выделенный IP если позволяет объём. Шареные IP у транзакционных ESP портят репутацию через других клиентов — один плохо настроенный аккаунт на том же IP может полностью уронить ваше размещение 2FA на час.
Email 2FA не должна быть единственным вторым фактором. Предложите TOTP (Google Authenticator, Authy) как основную опцию, а email как fallback. TOTP имеет нулевой риск доставки. Для критичных аккаунтов добавьте WebAuthn.
Шаблон, который попадает в инбокс
Тема: Запрос входа из Chrome на macOS (Москва, Россия)
Привет {{first_name}},
Мы получили запрос входа в ваш аккаунт из:
Браузер: Chrome 121 на macOS
Место: Москва, Россия (примерно, по IP)
Время: {{timestamp_iso}}
Если это были вы, введите этот код на странице входа:
{{code}}
Код действует до {{expiry_iso}} ({{expiry_human}}).
Если вы НЕ пытались входить, срочно смените пароль:
https://yourapp.com/account/security
--
Служба безопасности {{app_name}}
ул. Примерная, 123, Москва
support@yourapp.comОбратите внимание: полнотелое письмо, контекст запроса входа, имя команды, физический адрес, обычные URL. По структуре полная противоположность фишингу, несмотря на наличие в письме одноразового кода.
Проверка инфраструктуры
- SPF
~all, а не-allизначально. Если вы пропустите один include, hard fail (-all) будет отклонять легитимную 2FA. Начните с soft fail, мониторьте DMARC отчёты, потом переходите на-allкогда всё чисто. - DKIM ключ 2048 бит. 1024-битные ключи всё ещё работают, но всё больше штрафуются Gmail'ом и Outlook'ом.
- DMARC
p=quarantineилиp=reject. Отсутствие DMARC политики — это handicap доставки для критичной аутентификационной почты. - MTA-STS и TLS reporting. Опубликуйте
_mta-sts.yourapp.com. Провайдеры всё больше требуют TLS-protected доставку для транзакционной почты.
Шаблон 2FA переделывается чаще, чем кажется — i18n, ребрендинг, юридический footer. Каждое из этих изменений может привести к регрессии размещения. Подключите автоматический тест в CI auth-сервиса.