uCheckeruChecker
14 мин чтения

AI-персонализация email в реальном времени: технологии и реализация

«Привет, {имя}!» - не персонализация. Это подстановка переменной. Настоящая персонализация - когда ML-модель за миллисекунды собирает для каждого получателя собственную версию письма: товарные рекомендации, текстовые блоки, CTA. Разберём архитектуру, алгоритмы и конкретные паттерны реализации.


Что значит «в реальном времени»

Классическая персонализация работает в момент сборки кампании. Маркетолог создаёт сегмент, выбирает контент, нажимает «отправить». Контент определён до отправки и одинаков для всего сегмента.

Real-time персонализация откладывает решение до последнего момента. Два варианта: send-time - модель выбирает контент в момент отправки, и open-time - контент рендерится, когда получатель открывает письмо. Разница принципиальная. Send-time дешевле и совместим с любыми почтовыми клиентами. Open-time позволяет учитывать события, произошедшие между отправкой и открытием, но требует серверного рендеринга через прокси-изображения.

На практике send-time покрывает 90% задач. Open-time оправдан для задач вроде таймеров обратного отсчёта или актуальных складских остатков.

Слой данных: что нужно модели

Любая рекомендательная система начинается с данных. Для email-персонализации нужны три категории сигналов:

Профильные данные. Регион, язык, устройство, дата регистрации, тарифный план. Меняются редко, хранятся в основной БД. Запрос - прямой SELECT или кэш.

Поведенческие данные. Открытия писем, клики, покупки, просмотры страниц, время на сайте. Поступают потоком через события (Kafka, RabbitMQ, webhook). Здесь объём на порядки больше: один подписчик генерирует десятки событий в день.

Контекстные данные. Время суток, день недели, погода, текущие акции, складские остатки. Не привязаны к конкретному пользователю, но влияют на релевантность контента.

Собирать всё в одну таблицу - путь к хаосу. Стандартный подход - feature store: промежуточный слой, который агрегирует сырые события в готовые фичи и отдаёт их модели за миллисекунды.

Поток данных: от событий до фичей


  Events (clicks, opens, purchases)
    |
    v
  Message Queue (Kafka / RabbitMQ)
    |
    +--> Stream Processor (Flink / Spark Streaming)
    |         |
    |         v
    |    Feature Store (Redis + Postgres)
    |         |
    |         +-- user_features:  avg_open_rate, last_purchase_days,
    |         |                   preferred_category, device_type
    |         |
    |         +-- item_features:  category, price_bucket,
    |         |                   popularity_score, margin
    |         |
    |         +-- context:        time_of_day, day_of_week,
    |                             active_promo_ids
    |
    +--> Batch Pipeline (daily retrain)
              |
              v
         Model Registry (MLflow / Vertex AI)

Feature store решает две проблемы: гарантирует, что модель в продакшене получает те же фичи, на которых обучалась, и обеспечивает скорость - Redis отдаёт вектор фичей за 1-3 мс.

Модели рекомендаций: три подхода

Collaborative filtering. Основа: пользователи со схожим поведением интересуются похожими вещами. Матрица «пользователь - товар» разлагается на два набора эмбеддингов (user embedding и item embedding), скалярное произведение даёт прогноз интереса. Работает, когда накоплено много взаимодействий. Плохо справляется с новыми подписчиками (cold start).

Content-based filtering. Модель смотрит на признаки контента и профиль пользователя. Если подписчик кликал на статьи про Python - предложим ещё Python-статьи. Не зависит от поведения других пользователей, легко объясним. Недостаток: замкнутость - модель не предложит ничего за пределами привычных категорий.

Гибрид (two-tower, wide&deep). Стандарт в продакшене. Одна «башня» обрабатывает пользовательские фичи, другая - фичи контента. Выходные эмбеддинги сравниваются через dot product или MLP. Wide-компонент ловит простые корреляции (категория + регион), deep-компонент - нелинейные паттерны. Обучение на исторических кликах и конверсиях.

Two-tower модель: архитектура


  User Features                    Item Features
  [open_rate, last_click_days,     [category, price, popularity,
   preferred_cat, device, ...]      margin, freshness, ...]
         |                                  |
         v                                  v
  +-------------+                  +-------------+
  | User Tower  |                  | Item Tower  |
  | FC -> ReLU  |                  | FC -> ReLU  |
  | FC -> ReLU  |                  | FC -> ReLU  |
  +------+------+                  +------+------+
         |                                  |
         v                                  v
   user_embedding (128d)           item_embedding (128d)
         |                                  |
         +------------- dot product --------+
                          |
                          v
                   relevance score (0..1)
                          |
                          v
                   top-K items for email

На базе от 50 000 подписчиков two-tower даёт ощутимый прирост CTR по сравнению с правилами. Ниже этого порога часто хватает content-based подхода с ручными правилами в качестве fallback.

Сборка письма: от скора к HTML

Модель выдала top-K рекомендаций. Дальше - рендеринг. Архитектура зависит от объёма рассылки, но общий паттерн один.

Pipeline рендеринга


  Campaign trigger (scheduler / event)
         |
         v
  Recipient Queue (batch of user_ids)
         |
         v
  +------------------+
  | Personalization  |
  | Service          |
  |                  |
  | 1. Fetch user    |        +---------------+
  |    features  ----+------> | Feature Store |
  |                  |        +---------------+
  | 2. Score items   |        +---------------+
  |    via model ----+------> | Model Service |
  |                  |        | (gRPC / REST) |
  | 3. Apply rules   |        +---------------+
  |    (freq cap,    |
  |     blocklist)   |
  |                  |
  | 4. Render HTML   |        +---------------+
  |    template  ----+------> | Template Eng. |
  |                  |        | (MJML / Jinja)|
  +--------+---------+        +---------------+
           |
           v
  ESP / SMTP relay (Postfix, SES, Mailgun)

Шаблон письма содержит плейсхолдеры для динамических блоков: товарная карусель, подборка статей, персональный оффер. Каждый блок - отдельный компонент в шаблонизаторе. Personalization Service заполняет их данными из модели и передаёт готовый HTML в ESP.

Ключевое требование - скорость. На рассылке в 100 000 писем при лимите в один час каждое письмо нужно собрать за 36 мс. Это достижимо, если feature store в Redis, модель обслуживается через gRPC с батчингом, а шаблонизатор предкомпилирован.

Пример: динамические блоки в шаблоне

Jinja2 / MJML template
<mj-section>
  <mj-column>
    <!-- Static header -->
    <mj-text>Weekly digest for {{ user.first_name }}</mj-text>

    <!-- Dynamic: top-3 product recommendations -->
    {% for item in recommendations[:3] %}
    <mj-image src="{{ item.image_url }}" alt="{{ item.title }}" />
    <mj-text>{{ item.title }} - {{ item.price }} RUB</mj-text>
    <mj-button href="{{ item.url }}?utm_content=reco">
      View
    </mj-button>
    {% endfor %}

    <!-- Dynamic: content block chosen by model -->
    {% if user.segment == 'educator' %}
      {% include 'blocks/latest_article.mjml' %}
    {% elif user.segment == 'buyer' %}
      {% include 'blocks/discount_offer.mjml' %}
    {% else %}
      {% include 'blocks/popular_items.mjml' %}
    {% endif %}
  </mj-column>
</mj-section>

Шаблон один для всей рассылки. Контент внутри - уникальный для каждого получателя. ESP получает уже отрендеренный HTML.

Паттерны реализации

Precompute vs. on-the-fly

Два подхода. Precompute: ночной батч прогоняет модель по всей базе и сохраняет top-K рекомендаций в Redis. В момент отправки - только подстановка. Быстро, предсказуемо, но рекомендации устаревают за часы.

On-the-fly: модель вызывается для каждого письма в момент рендеринга. Рекомендации актуальны, но нагрузка на инфраструктуру кратно выше.

Компромисс: precompute с TTL 4-6 часов и on-the-fly fallback, если кэш протух. Для большинства рассылок четырёхчасовой давности рекомендации - допустимый trade-off.

Frequency capping

Модель оптимизирует релевантность, но не знает про усталость. Если один товар попадает в пять писем подряд, подписчик начнёт игнорировать рассылку. Frequency cap - обязательное правило поверх модели: не показывать один item чаще N раз за K дней. Реализуется через Redis-счётчики с TTL.

Fallback-стратегия

Модель не всегда может выдать рекомендации. Новый подписчик без истории, сбой сервиса, таймаут. Нужен fallback: глобально популярные товары, редакционная подборка, случайная выборка из каталога. Письмо должно уйти в любом случае - пустой блок рекомендаций хуже, чем неперсонализированный.

Метрики: что измерять

Персонализация - не цель, а инструмент. Измеряем то, на что она влияет.

CTR по рекомендательному блоку. Главная метрика. Сравниваем с контрольной группой, которая получает статичный контент. A/B-тест обязателен: без него невозможно отличить эффект модели от сезонности.

Revenue per email (RPE). Для e-commerce - основной бизнес-показатель. Персонализированные рекомендации увеличивают RPE на 15-35% по сравнению с ручными подборками. Но только если base rate конверсии уже здоровый.

Recommendation coverage. Какой процент каталога попадает в рекомендации. Если модель гоняет одни и те же 50 товаров - она переоптимизирована на популярность и не выполняет свою задачу.

Latency p95. 95-й перцентиль времени сборки одного письма. Если p95 уходит за 200 мс - рассылка на 100k будет идти часами. Мониторим и алертим.

Cold start: первые письма без данных

Новый подписчик - нулевая история. Collaborative filtering бесполезен. Что делать:

Источник подписки. Форма на странице «Кроссовки для бега» - подписчик с высокой вероятностью интересуется беговой обувью. UTM-метки, landing page, реферер - это уже сигнал.

Preference center. Спросить напрямую. Первое письмо после подписки - опрос: «Что вам интересно?» Два-три варианта, один клик. Конверсия таких опросов - 30-50%, если они короткие и вшиты в тело письма.

Lookalike-сегменты. Модель находит похожих пользователей по профильным признакам (регион, устройство, источник) и подставляет их усреднённые предпочтения. Грубо, но лучше случайного контента.

После 3-5 взаимодействий (открытия, клики) данных достаточно, чтобы модель перешла на персональные рекомендации. Первая неделя - зона компромиссов.

Фундамент: качество базы

Модель рекомендаций бесполезна, если 20% базы - мёртвые адреса. Вы тратите GPU-циклы на персонализацию писем, которые никто не получит.

Персонализация работает на здоровой базе. Невалидные адреса искажают метрики: CTR занижается (мёртвые адреса не кликают, но попадают в знаменатель), модель обучается на шумных данных, bounce rate растёт и убивает репутацию домена.

Три точки, где валидация критична:

При подписке. Real-time проверка через API. Не пропускаем одноразовые адреса и очевидный мусор. Модель начинает работу с чистыми данными.

Перед рассылкой. Bulk-валидация за 12-24 часа до отправки. Адреса деградируют: люди меняют работу, почтовые ящики удаляются, домены перестают принимать почту.

В обучающем датасете. Перед ретрейном модели фильтруем события, связанные с невалидными адресами. Иначе модель будет учиться предсказывать интересы несуществующих получателей.

Валидация в ML-пайплайне


  Signup Form                     Bulk Campaign
      |                                |
      v                                v
  +-------------------+     +---------------------+
  | Real-time API     |     | Batch validation    |
  | (uChecker single) |     | (uChecker bulk API) |
  +--------+----------+     +----------+----------+
           |                            |
           v                            v
      Valid? ----No----> Reject    Valid? ---No---> Exclude
           |                            |
           v                            v
      Feature Store              Send pipeline
           |                            |
           v                            v
      Training data              Personalized email
      (clean labels)             (reaches inbox)

Чек-лист внедрения

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

  1. Очистить базу. Удалить невалидные адреса, одноразовые ящики, спам-ловушки. Без этого шага всё остальное - построение на песке.
  2. Настроить сбор событий. Открытия, клики, покупки должны приходить в единый event bus с user_id и timestamp.
  3. Собрать feature store. Агрегировать сырые события в фичи. Начать с простых: last_click_days, top_category, purchase_count_30d.
  4. Запустить content-based модель. Для старта хватит. Collaborative filtering добавить после накопления данных.
  5. Подготовить шаблон с динамическими блоками. Один-два блока для начала. Товарные рекомендации - самый понятный первый шаг.
  6. A/B-тест. Контрольная группа со статичным контентом. Минимум две недели, чтобы исключить сезонный шум.
  7. Мониторинг. CTR, RPE, latency p95, recommendation coverage. Дашборд, алерты.

Инструменты

Не обязательно строить всё с нуля.

Задача
Self-hosted
Managed
Feature store
Feast + Redis
Vertex AI Feature Store, Tecton
Модель рекомендаций
PyTorch / TensorFlow + MLflow
Amazon Personalize, GCP Recommendations AI
Model serving
Triton, TorchServe, BentoML
SageMaker Endpoints, Vertex AI Prediction
Шаблонизация email
MJML + Jinja2
Braze, Iterable, Sailthru
Валидация базы
Собственный SMTP-чекер
uChecker API
A/B-тестирование
Свой фреймворк + статистика
LaunchDarkly, Optimizely

Managed-решения дороже, но снимают инфраструктурную нагрузку. Для команды из 2-3 инженеров managed feature store и model serving - рациональный выбор. Модель рекомендаций часто имеет смысл держать свою: бизнес-логика слишком специфична для коробочного решения.

Типичные ошибки

Обучение на грязных данных. Модель впитывает шум из невалидных адресов. Bounce и отсутствие открытий воспринимается как негативный сигнал, хотя подписчик просто не существует. Результат - заниженные скоры для целых категорий контента.

Отсутствие fallback. Сервис модели упал - рассылка не ушла. Или ушла с пустыми блоками. Fallback не опционален.

Переоптимизация на CTR. Модель научилась подсовывать кликбейт. CTR вырос, RPE упал, отписки увеличились. Оптимизируйте на downstream-метрику: покупку, активацию.

Игнорирование privacy. GDPR, ФЗ-152. Персонализация на основе поведенческих данных требует согласия. Данные для обучения модели - персональные данные. Правовой аудит перед запуском, а не после.

Итого

AI-персонализация email - не магия и не маркетинговая опция. Это инженерная система: feature store, ML-модель, рендеринг-пайплайн, мониторинг. Она даёт 15-35% прироста к кликам и конверсии, но только при чистых данных, здоровой базе и корректной инфраструктуре.

Начните с фундамента: чистая база, надёжный сбор событий, простая content-based модель. Усложняйте по мере роста. Two-tower и real-time инференс - это следующий шаг, не первый.

Перед тем как строить персонализацию - проверьте базу. uChecker покажет, сколько адресов в вашем списке реально живые, а сколько - шум, на котором модель будет учиться впустую.

AI персонализация emailдинамический контент AIreal-time email персонализациярекомендательная система emailML email контентfeature storecollaborative filtering