← Все статьи Как я собрал AI-руководителя отдела продаж в Instagram Direct - и почему самое сложное оказалось не AI
AI-команда

Как я собрал AI-руководителя отдела продаж в Instagram Direct - и почему самое сложное оказалось не AI

Илья Черняк · 5 июня 2026 г.

Построил не чат-бота, а AI-руководителя отдела продаж в Instagram Direct. Самое сложное оказалось не AI - а просто прочитать входящее сообщение через код.

Неделю назад я сидел перед терминалом и понимал, что застрял. Не на нейросетях - они заработали за день. Не на архитектуре - SQLite и пара API, ничего космического. Я застрял на том, чтобы просто прочитать входящее сообщение в Instagram Direct через код. Просто прочитать. Это стоило мне больше нервов, чем вся остальная система вместе взятая.

У меня есть клиент с активным Instagram-аккаунтом. Каждый день - десятки входящих в Direct: “сколько стоит?”, ответы на сторис, фотки товаров, голосовые. К вечеру горячий покупатель, который написал утром, тонет под слоем мусора. Владелец не успевает. Деньги утекают. Классика.

Я решил посмотреть на Direct не как на мессенджер, а как на отдел продаж. А если это отдел - ему нужен руководитель. Только не живой (дорого, не масштабируется), а AI-система, которая сортирует входящих, подсказывает кому писать прямо сейчас и готовит черновик ответа в голосе владельца.

Пишу с пылу с жару, пока помню каждую команду. Это не “как правильно по документации” - это как реально собрал, с граблями.

Четыре такта, а не “сели и закодили”

Такт 1. Разведка. Прежде чем писать код, я прогнал рынок IG-DM/CRM-инструментов. Нашел позиционный разрыв. Существующие решения - это либо чат-боты с ветвлениями, либо CRM без понимания контента. Никто не совмещает ось намерения покупателя + детекторы ситуаций + чтение медиа (фото, голосовые, видео) + смысловой поиск по истории + AI-черновики в голосе владельца. Отсюда родился месседж: это не чат-бот, а AI-руководитель отдела продаж.

Такт 2. Дизайн. Через Claude Design (claude.ai) я создал полный гайд - знак, палитру, типографику, - лендинг и CSS-скин дашборда. Скин забрал как сырье (CSS-токены + классы) и портировал в серверный движок. Это дало единый визуальный язык до того, как написана первая строчка бизнес-логики.

Такт 3. Ядро. Построил архитектуру на тесном по RAM VPS Hetzner - без единой тяжелой ML-зависимости. Все “дорогое” (LLM, эмбеддинги, мультимодальность) вынесено в облачные API.

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

Самое сложное: как вообще принимать и отправлять Instagram DM

Прежде чем обсуждать AI - надо разобраться с тем, без чего вся система бессмысленна: программный доступ к личным сообщениям Instagram. Это оказалось самой тяжелой и самой неочевидной частью проекта. Не нейросети, не архитектура, а буквально “как читать и писать DM через код”.

По-настоящему сложное в интеграции с Instagram DM - это не AI, а получить авторизованный доступ на чтение и запись личных сообщений. AI - просто API-вызовы. Вся кровь - в транспортном слое.

В прошлой статье я описывал официальный путь: свое приложение в Meta for Developers, System User Token, прохождение App Review. App Review я прошел. Но прав именно на личные сообщения он мне так и не дал. Постить, читать и отвечать на комментарии - пожалуйста. А вот залезть в Direct через официальный API - это отдельная стена, выше предыдущей. Для этого проекта я нашел путь короче - в обход этой стены вообще. Расскажу.

Официальный путь - стена. Чтобы получить программный доступ именно к личным сообщениям, нужен отдельный раунд App Review под разрешение instagram_manage_messages: бизнес-верификация компании, скринкасты с демонстрацией каждого сценария, обоснования, недели переписки с ревьюерами и частые отказы без внятных причин. Для одного аккаунта и быстрых итераций это оверкилл, который съедает недели до первого прочитанного сообщения.

Прорыв - Zernio. Вместо того чтобы становиться собственным приложением Meta, я взял сервис, который уже является одобренным Meta Tech Provider. Это Zernio, и у него есть бесплатный тариф для личных сообщений. Подключаешь аккаунт через его OAuth-flow (одобрение Meta лежит на Zernio, не на тебе) - и сразу получаешь программный доступ к DM без собственного app review. Самая тяжелая часть всего проекта схлопнулась до 15 минут настройки.

Прием сообщений: вебхук

Zernio шлет вебхук на мой эндпоинт при каждом событии: message.received, message.sent, message.failed. Первичная настройка - handshake верификации в стиле Meta: GET-запрос с параметром hub.challenge, я возвращаю challenge обратно (доказываю владение эндпоинтом). Каждый POST подписан заголовком с signature - проверяю, что запрос действительно от Zernio, а не подделка.

Жесткое ограничение: вебхук работает по принципу fire-and-forget с ретраями. Если не ответить 200 за несколько секунд - Zernio начинает ретраить и заваливает дубликатами. Поэтому хендлер устроен так:

# Сразу 200, тяжёлая работа — в фоновом потоке
def handle_webhook(request):
    verify_signature(request)
    payload = parse(request)
    send_response(200)
    threading.Thread(target=process, args=(payload,)).start()

В пейлоаде message.received приходят sender: {id, username}, текст и attachments (медиа-вложения).

Отправка сообщений

POST /inbox/conversations/{id}/messages
Content-Type: application/json
Authorization: Bearer <token>

{"accountId": "...", "message": "текст ответа"}

Длинные ответы режу под лимит длины IG DM (около 1000 символов) и шлю кусками с паузами - иначе Instagram вернет HTTP 400 и сообщение молча потеряется.

Чтение истории и дедуп

GET /inbox/conversations/{id}/messages?accountId=<id>

Возвращает всю переписку, включая ручные ответы самого владельца (приходят как исходящие от “You”). Это критично: бот видит, что владелец уже ответил клиенту вручную, и не переотвечает.

Ограничение Meta - окно 24 часа. Meta разрешает писать пользователю только в течение 24 часов после его последнего входящего сообщения. Вне окна нужны спецтеги или human-agent-флаг. Поэтому система заточена отвечать быстро - и эскалировать владельцу, если не уверена в ответе.

Архитектура: что внутри и почему так

Ключевое ограничение - VPS Hetzner с тесной оперативкой. Никакого PyTorch, никаких локальных векторных баз данных, минимум зависимостей. Все тяжелое считает облако, сервер только оркестрирует.

“Мозг” - Claude (Anthropic). Формирует ответ в голосе владельца - не шаблонный “Здравствуйте, ваш запрос принят”, а живой текст, как если бы владелец писал сам. Исходящие по умолчанию не улетают клиенту напрямую - уходят на подтверждение через Telegram-бот. Владелец в Telegram видит черновик, может отредактировать текст прямо в реплае или нажать “отправить как есть”. Это human-in-the-loop: AI готовит, человек решает. Ни одно сообщение не уходит в Direct без ведома владельца.

Память и CRM на SQLite. Выбрал SQLite именно из-за ограничений RAM - ни ORM, ни внешний процесс не нужен. Внутри:

  • полная история диалога (зеркалит тред из Zernio)
  • дедуп “уже отвечено” (включая ручные ответы владельца)
  • карточки лидов: сегмент, телефон, этап воронки
  • полнотекстовый поиск через FTS

Ось намерения. На каждого лида Gemini 2.5 Flash ставит метку: “готов делегировать покупку” / “выбирает сам” / “просто смотрит”. К метке прилагается цитата-обоснование из переписки и уровень уверенности. Классификация запускается автоматически по таймеру и при каждом новом сообщении. Gemini стоит копейки и хорошо работает с короткими контекстами - идеальный классификатор.

Детекторы - готовые рабочие списки. Вместо бесконечного скролла диалогов - конкретные очереди с приоритетами:

  • “горячие без ответа” - кто написал и ждет
  • “хотят купить, но нет телефона” - надо дожать
  • “молчат три дня” - пора реактивировать

Открываешь дашборд утром - и сразу видишь, кому писать прямо сейчас. Не “все 47 диалогов”, а три конкретных имени с контекстом.

Чтение медиа. Фото, голосовые, видео, ответы на сторис - все прогоняется через Gemini 2.5 Flash и превращается в текст. Результат кешируется по идентификатору ассета. Бот “понимает” не только текст, но и фотографии товаров, голосовые вопросы, видео-демонстрации.

Смысловой поиск. gemini-embedding-001 (768 измерений) дает поиск по смыслу: “кто спрашивал про доставку”, “кто упоминал оптовый заказ”. Без локальной векторной БД - косинусное расстояние считается прямо в Python. При сотнях лидов хватает с запасом.

Goal-config. Акцент воронки - что сейчас важнее: собрать телефоны, закрыть на визит, предложить акцию - задается живым конфигом:

{
  "goal": "collect_phone",
  "priority_label": "нет телефона",
  "nudge": "Спросить номер для связи"
}

Меняешь JSON-файл - мозг перестраивается без перезапуска.

Дашборд на stdlib. Написан на чистом http.server из стандартной библиотеки Python. Ни Flask, ни Django, ни Streamlit - ради экономии RAM. Показывает лиды, детекторы, drill-down в полный тред, композер ответа с гейтом подтверждения, кнопку AI-черновика, переключение этапов воронки. Обогащение профилей (число подписчиков, верификация) подтягивается из Zernio.

Продуктизация: один движок, три режима

Чтобы превратить внутренний инструмент в продукт, я сделал движок context-driven через переменные окружения: бренд, имя владельца, лейблы оси намерения, режим read-only, базовый путь. Один и тот же код рендерит и реальную рабочую панель, и публичное демо.

Демо-база. Выдуманная ниша, фейковые лиды, 16 диалогов. Режим read-only: отправка отключена, баннер “это демо” виден сразу. Реальных данных нет.

Логин-гейт. Раньше доступ был по секретной ссылке с токеном в URL. Быстро, но для продукта не годится - ссылка утекает в историю браузера, в Slack-переписку, в скриншоты. Сделал нормальную форму логин/пароль. Сессия хранится в куке: значение - хэш от связки логин:пароль, пароль в открытом виде нигде не лежит, кука переживает рестарт сервера. В итоге три режима доступа уживаются в одном движке: открытое демо / старый токен для обратной совместимости / форма входа.

Развертывание

Два systemd-юнита: один на демо (read-only, демо-база, открытый доступ), второй на реальную панель (за логином). Существующий прод не трогал - выкатил рядом.

# /etc/systemd/system/dm-dashboard-demo.service (simplified)
[Service]
Environment="DASH_MODE=demo"
Environment="DASH_DB=/opt/dm-dashboard/demo.db"
ExecStart=/opt/dm-dashboard/venv/bin/python3 dashboard.py

Лендинг отдается статикой через Caddy.

DNS. Домен продукта висел на “магазинных” NS-серверах регистратора, где API управления зоной недоступен. Через API reg.ru переключил на стандартные NS, добавил A-записи, вычистил паразитные “парковочные” записи.

Автономный вотчер DNS. Маленький скрипт-демон поллил DNS и, как только домен зарезолвился на нужный IP, сам активировал прокси-запись и спровоцировал выпуск TLS-сертификата через Let’s Encrypt. Важный инсайт: Let’s Encrypt проверяет домен через авторитетные серверы, поэтому сертификат можно выпустить раньше, чем обновятся кэши публичных резолверов.

Грабли

Семь конкретных мест, где я споткнулся. Каждое стоило от получаса до целого вечера.

1. systemd и пробелы в значениях. Environment=KEY=value with spaces молча берет только первое слово. Часами дебажил “почему переменная обрезана”. Решение:

Environment="KEY=value with spaces"

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

2. Модуль БД смотрел не на ту env-переменную. Один из модулей хранилища был привязан к другой переменной для пути к базе, чем остальные. Демо чуть не стало читать реальную базу с данными клиента. Урок: проверяй, к какому env реально привязан путь к БД в каждом модуле, а не только в главном.

3. CSS-скин грузится относительно скрипта. Скин подключается по пути dirname(__file__). Деплой на сервер обязан копировать CSS рядом с Python-файлом, иначе дашборд падает на старте с невнятной ошибкой.

4. API reg.ru с IP-whitelist. Я пытался рулить DNS с макбука - Access Denied. API reg.ru работает только с IP из белого списка. У меня в списке был только IP сервера. Все DNS-операции - с сервера через SSH.

5. Дев-артефакты на проде. На лендинге остался переключатель вариантов заголовка (A/B/C) от Claude Design и скрипт обфускации email. Посетитель видит три кнопки, которых быть не должно. Я заметил это только когда показывал демо живому человеку. Урок: перед продом - полный аудит лендинга на дев-мусор. Открой в инкогнито и пройди глазами каждый элемент, как будто видишь сайт впервые.

6. Лимит длины IG DM - около 1000 символов. Сообщения длиннее возвращают HTTP 400 и молча теряются. Я узнал об этом, когда клиент не получил ответ. Решение: нарезать длинные ответы на куски и отправлять с паузами.

7. Вебхук обязан отвечать мгновенно. Zernio ретраит при задержке - получаешь дубликаты входящих. Единственный надежный паттерн: сразу 200, вся логика в фоновом потоке.

Стек, чтобы повторить

  • Python stdlib (http.server) - дашборд и API
  • SQLite + FTS - память диалогов и CRM
  • Claude (Anthropic) - “мозг”: генерация ответов в голосе владельца
  • Gemini 2.5 Flash - классификация намерения и чтение медиа (фото, голосовые, видео)
  • gemini-embedding-001 (768 измерений) - смысловой поиск по истории
  • Zernio (бесплатный тариф) - транспорт Instagram DM: вебхуки, отправка, история треда; Meta Tech Provider, пропуск app review
  • Telegram-бот - human-in-the-loop (подтверждение исходящих)
  • Hetzner + Caddy + systemd + Let’s Encrypt - хостинг, реверс-прокси, TLS
  • API reg.ru - управление DNS-зоной
  • Claude Design (claude.ai) - бренд, лендинг, CSS-скин

Что в итоге

Я не разработчик. Claude Code открыл впервые в феврале - четыре месяца назад. Но этот проект показал мне простую вещь: сложное - не AI. Claude, Gemini, эмбеддинги заработали за день, это просто API-вызовы. Всю кровь стоил транспортный слой - авторизованный доступ на чтение и запись Instagram Direct. Как обошел стену Meta App Review - разобрано выше.

Вся тяжелая магия живет в облачных API. Сервер делает только оркестрацию: принял вебхук, дернул API, положил результат в SQLite, отдал HTML. На практике это значит, что такую систему можно поднять на самом дешевом VPS - мой укладывается в пару гигабайт RAM.

Главный сдвиг не технический, а концептуальный: Direct - это не мессенджер, а отдел продаж. Если это отдел - ему нужен руководитель. AI справляется, если правильно разделить, что считает облако, а что делает сервер.

Вопросы и показать свой первый рабочий прототип - в личку @magic4e. Подписывайся на @mdkguru - там я каждый день показываю, как собираю команду AI-агентов и во что это превращается. Спор на 300 000 в самом разгаре.

Понравилась статья?

Подписывайся на канал - там больше кейсов и практики

@mdkguru в Telegram →