
В большом мобильном продукте коммуникации запускаются разными командами. Подписки, апселлы, промоакции и A/B-эксперименты развиваются параллельно и часто независимо друг от друга.
У каждой инициативы свои условия показа, сегменты, частотные ограничения и метрики. В коде это превращается во множество точек входа и локальных проверок. Со временем сценариев становится десятки: эксперименты пересекаются, приоритеты конфликтуют, команды конкурируют за один и тот же экран. Проблема оказывается не в доставке интерфейса, а в отсутствии единого механизма принятия решений — кто и по каким правилам определяет, что увидит пользователь.
Меня зовут Михаил Христокьян. Я работаю над мобильными продуктами Почты и Облака Mail и занимаюсь архитектурой и развитием системы продуктовых коммуникаций внутри приложения. Сегодня я расскажу о том, как мы решили эту проблему.
Но прежде, чем перейти к статье, несколько слов о том, как она родилась. Чтобы рассказать коллегам, как правильно использовать новый инструмент, мы провели «Разборки». Это внутренний формат обучения внутри VK, в ходе которого собирается разовая встреча посвященная прикладному разбору конкретного инструмента. В рамках таких встреч мы не только рассказываем про идею, возможности и планы на будущее, а также показываем коллегам как решать их конкретные задачи на реальных кейсах.
После успешно проведённых «Разборок» я решил, что этот материал может принести пользу гораздо большему количеству людей и написал эту статью.
Когда скорость запуска коммуникаций — это не маркетинговая прихоть
Давайте представим, что вы открываете мобильное приложение. Вас встречает предложение подключить подписку со скидкой. Через пару дней — напоминание о новой функции. Параллельно идёт A/B-эксперимент с альтернативным экраном. А маркетинг уже готовит новую акцию, которая должна стартовать «вчера».
Теперь представим, что каждая из этих коммуникаций запускается разными командами. У каждой свои условия показа, своя аудитория, свои ограничения по частоте и свои метрики. И все они претендуют на один и тот же экран.
В Mail мы ежедневно работаем с продуктами, где коммуникации — это не разовые баннеры, а множество гипотез, экспериментов и других сценариев. Скорость их запуска напрямую влияет на удержание внимания пользователя, продуктовые метрики и, конечно, выручку.
Мы умеем доставлять интерфейс динамически. Но у нас не было системы, которая централизованно решала бы, что именно показать пользователю в конкретный момент. И именно с этого началась история фермы коммуникаций.
Что такое ферма коммуникаций
Ферма коммуникаций — это слой внутри мобильного приложения, который позволяет запускать продуктовые коммуникации и мини-фичи без участия релизного цикла. Запуск такого сценария в классической модели требует синхронизации нескольких ролей:
заказчик или маркетолог;
продукт-менеджер;
дизайнер;
аналитик;
тестировщик;
разработчик (iOS/Android).
Коммуникация превращается в маленький продуктовый проект, оттягивающий на себя ресурсы команды. Получается дорого, так как нужно не только сделать, но и синхронизировать всех участников.

Ферма убирает эту зависимость. Идея описывается конфигурацией, публикуется в A/B-сервисе и может быть запущена без участия мобильных разработчиков и без релиза приложения.
При этом ферма — не конструктор полноценных разделов приложения и не универсальная платформа для любой бизнес-логики. Она решает узкий, но масштабный класс задач: продуктовые коммуникации в точках показа внутри существующего интерфейса.
Важно, что ферма:
работает рядом с нативными решениями и не заменяет их;
учитывает приоритеты и ограничения других сценариев;
централизует правила показа;
позволяет запускать A/B-эксперименты и гипотезы за часы, а не недели.
Фермой могут пользоваться не только разработчики. Запуск коммуникации — это уже не инженерный процесс, а управляемый продуктовый инструмент, доступный маркетологам, продакт-менеджерам и аналитикам в рамках заданного контракта.
Фактически, ферма отделяет две вещи:
разработку инфраструктуры;
управление коммуникациями.
Именно это разделение позволяет ускорять запуск, не увеличивая нагрузку на мобильные команды.
Как ферма принимает решение о показе
В основе фермы лежит простой, но важный принцип: коммуникации не инициируют показ сами. Они становятся кандидатами. Процесс выглядит так:

Диаграмма выше намеренно упрощена. Она показывает только основной поток принятия решения. В реальной системе есть дополнительные шаги — кеширование конфигураций, обработка ошибок, аналитика и обновление состояния показов, — но для понимания архитектуры они не критичны.
Важный элемент схемы — Kotlett. Это инфраструктура динамической доставки фич, поверх которой работает ферма коммуникаций. Если кратко, Kotlett позволяет загружать и исполнять функциональность приложения без релиза клиента, а также использовать единый формат вёрстки и логики для iOS и Android.
Подробно про устройство Kotlett уже рассказывали в отдельной статье, поэтому здесь не будем углубляться в детали. В контексте фермы важно лишь то, что Kotlett отвечает за доставку и исполнение коммуникаций: через него в приложение попадает не только описание интерфейса, но и логика работы самой фермы: правила показа, обработка действий пользователя и взаимодействие с хостовым приложением — и всё это без релиза приложения!
Как выглядит конфигурация фермы
После того как ферма получает список доступных коммуникаций из A/B-сервиса, она работает не с кодом, а с конфигурациями. Каждая коммуникация описывается JSON-контрактом: в нём содержатся параметры показа, ограничения, приоритет и ссылка на вёрстку. Такой JSON может выглядеть следующим образом:
{ "id": "<название>", "config_json": { ... }, "config_url": "" "mount_point": "", "plateRule": { "period": -1, "priority": 0, "end_condition" : { "shows_count" : 0, "actions_count" : 0 }, "triggers": { "isPaidUser" : false, "isBizUser" : false, "isCorpUser" : false, "skipFirstLaunch": false, <...> } }, "screens" : [...], "presentationStyle": { "" : {} } }
Конфигурация не привязана к конкретному типу интерфейса. Один и тот же JSON используется для разных форматов коммуникаций и других сценариев показа. Меняются только тип и ссылка на вёрстку, а правила показа остаются одинаковыми.
При просмотре конфигурации может возникнуть вопрос: зачем в контракте сразу несколько полей, связанных с вёрсткой — config_json, config_url и mount_point?
config_json
Содержит саму вёрстку коммуникации в формате JSON. Используется для небольших конфигураций или при тестировании.
config_url
Ссылка на удалённую вёрстку. На практике используется чаще, так как позволяет хранить большие конфигурации вне A/B-сервиса и обновлять их независимо.
mount_point
Формат, который подойдёт в том случае, когда заказчик не в силах самостоятельно создать вёрстку, или сценарий слишком сложный (здесь нужно залезать в репозиторий Kotlett и создавать вёрстку с помощью DSL).
Автоматизация процессов
Для ещё большего сокращения TTM вокруг фермы были построены инструменты, которые упрощают работу с типовыми сценариями. Админ-панель:

В ней можно настроить:
название и идентификатор коммуникации;
период активности;
сегменты аудитории;
ограничения показов;
ссылки на изображения;
тексты и кнопки;
аналитические события;
ссылку на вёрстку.
После сохранения админка формирует JSON-конфигурацию, которая публикуется в A/B-сервисе и становится доступной для фермы.
События для отправки аналитики были унифицированы. Это привело к тому, что все события по коммуникациям можно отследить в сервисе без постоянного обновления дашбордов.
Результаты внедрения
С помощью фермы стало возможна работа с единым типом конфигурации. Это активно используется в Android/iOS-клиентах, причём не только в одном приложении.
Решён конфликт с параллельными запусками А/В-экспериментов.
Ресурс тестирования с каждой задачи по запуску сокращён на 2-3 часа.
Спринты мобильных разработчиков стали гибкими.
По сути, ферма стала единым механизмом, через который в приложении появляются продуктовые коммуникации — от простых подсказок до полноценных промосценариев.