Привет, Хабр. Меня зовут Денис.
У меня уже был опыт с 152-ФЗ на другом проекте — умный поиск вакансий, там перед эмбеддингами стоял PII Guard на natasha + регулярках. Казалось, я уже примерно понимаю, куда класть минимизацию. Потом я взялся за почту.
Продукт называется MailBro. Несколько ящиков, сводка «что сделать сегодня», сроки, черновик ответа. Человек подключает Яндекс / Mail.ru / Gmail / Exchange паролем приложения, мы тащим письма, гоняем через модель, отдаём приоритет и черновик. Отправка только после ручного подтверждения — про это ниже чуть-чуть, но не в этом главная боль.
Главная боль в том, что я почти повесил на главную фразу «мы соблюдаем 152-ФЗ», потому что перед моделью вырезаю паспорт и карту. Юрист потом смотрел на меня так, будто я предложил закрыть налоговую красивым CSS.
Ниже — что именно сломалось в голове, какой код получился и где я до сих пор не буду делать вид, что всё сшито.
С чего началась самоуверенность
Первая версия Privacy Gateway выглядела «достаточно». Берём subject + body_text, режем тело до бюджета (у меня сейчас AI_CLASSIFY_CHAR_BUDGET=3000 символов — иначе одно письмо с простынёй съедает разбор целиком), гоняем детектор, подменяем находки плейсхолдерами вида [PERSON_001], [PHONE_002], [EMAIL_001]. Маппинг оригинал↔плейсхолдер кладём в отдельную таблицу, шифруем через Laravel Crypt, TTL маппинга короче жизни тела: тело письма у нас живёт 48 часов (MAIL_BODY_TTL_HOURS), маппинг по умолчанию 24 (PRIVACY_MAPPING_TTL_HOURS).
Дальше модель видит уже дырявый текст. Ответ модели мы восстанавливаем обратно через restore(), чтобы в карточке у человека снова были нормальные имена, а не [PERSON_001]. В промпте жёстко: плейсхолдеры непрозрачные, угадывать ФИО запрещено.
На этом месте у меня в голове щёлкнуло: «ну всё, ПДн не уезжают». Хотелось бейдж на лендинг.
Потом я открыл приказ №140
После 01.09.2025 обезличивание — это не «мы замазали ФИО». Критерий: без дополнительной информации нельзя понять, чьи это данные.
Свободный текст письма этому почти никогда не удовлетворяет. Вычеркнули Ивана Ивановича — осталось «договор с ООО „Вектор“ на 12,4 млн, оплатить до пятницы, ждём на объекте на Варшавке». Человека всё равно вычислят. Плейсхолдер [CARD_001] не делает письмо анонимным набором слов.
Юридически то, что я делаю, ближе к минимизации / маскированию / псевдонимизации. Не к обезличиванию. В подвале сайта я специально оставил скучную формулировку про метки. Маркетолог внутри меня каждый раз хочет её «улучшить». Не даю.
И да: в статье про Smart Search я когда-то сам писал «обезличенный текст» про тот же приём. Сейчас бы так не написал. Там резюме, тут чужая переписка — цена ошибки другая.
Что детектим и что режем навсегда
Детектор у меня пока на регулярках и ключевых словах. Без иллюзий: это не NLP-лаборатория. Зато поведение предсказуемое и тестируемое.
Классы примерно такие:
Класс | Что ловим | Что делаем |
|---|---|---|
| ФИО кириллицей | → |
| +7 / 8… | → |
| обычный email | → |
| ИНН 10/12 | → плейсхолдер |
| СНИЛС | → плейсхолдер |
| ул./г./пр-т… | → плейсхолдер |
| серии/номера | письмо в модель не уходит |
| 13–19 цифр + Луна | не уходит |
| 20 цифр р/с | не уходит |
| пароль, otp, cvv, 2fa, «код подтверждения»… | не уходит |
| куски про здоровье/судимость/веру и т.п. по ключевым словам | не уходит |
| «коммерческая тайна», | не уходит |
Вот это важное. Не всё маскируется. Часть классов — fail-closed: PrivacyDecision.allowed = false, состояние письма уходит в AiSkipped, в аудит пишется privacy_blocked и счётчики сущностей. Денег не списываем. Модель вообще не вызывается.
Почему так жёстко? Потому что «замаскировать паспорт как [PASSPORT_001]» всё равно оставляет рядом контекст, из которого часто понятно, чей это паспорт. Я лучше потеряю один разбор, чем отправлю такое наружу.
Минусы честно: ложные срабатывания есть. ИНН из десяти цифр иногда путается с чем попало. ФИО-регексп ловит «Новый Год» и подобные конструкции. SPECIAL по словам — грубый. На этом месте хочется воткнуть NER покруче. Пока живу с регулярками и правлю кейсы по мере боли.
Пайплайн одного письма, без слайдов
Грубо:
IMAP fetch → Message (state …) → PrivacyGateway::prepare() allowed=false → AiSkipped, audit privacy_blocked, план без LLM allowed=true → AiQueued → RoutingAiProvider (yandex/* или зарубежный шлюз) → schema-valid JSON → restore плейсхолдеров → карточка / черновик / лейблы
Деньги: кошелёк трогаем только если провайдер вернул валидный по схеме результат. Упал шлюз, кривой JSON, privacy block — пользователю ноль. Иначе боты и ретраи жрут бюджет в пустоту; на вакансиях я уже наступал.
Роутинг моделей отдельный слой. Префикс yandex/ идёт в Yandex AI Studio (обработка в РФ, в запросах x-data-logging-enabled: false). Остальное — через зарубежный OpenAI-совместимый шлюз, и только если пользователь сам так выбрал: на ящик, на сводку или по умолчанию.
Самое вкусное, что я себе запретил: fallback «Яндекс лёг → давайте пока GPT, пользователь не заметит». В RoutingAiProvider это просто AiGatewayFailed::notConfigured(...). Точка. Сводка пустая, поддержка орёт, зато в момент инцидента я не объясняю, почему рабочий ящик уехал за рубеж без выбора человека.
GigaChat в этом продукте live не включал. Со стандартной офертой под чужие ПДн там своя история, я её уже разбирал с юристом на прошлом заходе. Отечественный лейбл сам по себе ничего не закрывает.
Ещё четыре грабля, уже не про код
«Шлюз — российское ООО, базы в РФ → трансгранички нет».
Нет. Если модель за рубежом, ст. 12 никуда не делась от того, что посередине стоит российская обёртка.
«Провайдер пишет, что соблюдает принципы 152-ФЗ».
Его заявление ≠ ваше поручение на обработку. Без основания передача шлюзу — передача ПДн третьему лицу.
«Пользователь нажал „подключить ящик“ — согласие на всю переписку».
На корреспондентов согласие пользователя не работает. Иван не согласился за Петра. У меня основание для их данных — законный интерес (п. 7 ч. 1 ст. 6), и его ещё надо уметь объяснить, а не повесить в подвал сайта.
Админка «ну глянь письмо, там баг».
Тела писем в staff-админке нет. Чиним на синтетике и на логах без контента: latency, model id, class ошибки, entity counts. Иначе админка быстро становится ещё одним почтовым клиентом со всеми ящиками сразу.
Мелочь, которую тоже чуть не сломали «для удобства»: календарная подписка (человек сам добавляет URL в Яндекс/Google/Apple) отдаёт дату и строку «MailBro: срок по письму» + ссылку на задачу. Тему и отправителя туда не кладу. Один тикет «хотя бы тему» закрыл. Тема письма в чужом календаре — часто уже ПДн корреспондента.
Что из этого торчит наружу в продукте
Российские модели по умолчанию для новых. Зарубежные дешевле и остаются выбором.
Тихого переключения маршрута нет.
Тела короткоживущие.
Отправка письма — только после человека. Черновик можно, автоответчик я себе не разрешил: один «уверенный» автоответ на письмо из суда стоит дороже года разговоров про магию. Для IMAP это ещё и пароль приложения, не «полный OAuth на весь аккаунт» — основной пароль я не прошу и не храню.
В пушах по умолчанию нет отправителя и темы. В Telegram подробностей нет принципиально.
Цены и лимиты лучше смотреть на экране тарифа: я их тут высекать не буду, правят без переписывания Хабра.
Сам сервис: https://mailbro.ru. Справка с деталями по ящикам и моделям лежит в /help.
Честный хвост
Уведомление РКН, поручения, трансграничка при выборе зарубежной модели — это не абзац «мы используем современных провайдеров». Часть бумаги у меня ещё в работе. Минимизация снижает объём и вероятность утечки. Она не превращает продукт в «152-ФЗ compliant» бейдж.
Если делаете ИИ поверх чужих документов в РФ: не пишите на лендинге то, чего нет в договорах. И не делайте fallback в другую страну «на время аварии». Авария — не согласие пользователя.
Буду рад в комментариях, как у вас устроен fail-closed на границе с LLM. У меня пока регулярки, шифрованный маппинг и много запретов. Запреты, как ни странно, держат продукт лучше слоганов.

