Я долго ходил по конференциям, у меня хороший нетворк, и все это длительное время я наполнял CRM контактами, результатами звонков, переписками, договорённостями и тд.
В какой‑то момент в ней оказалось почти 10К человек и вся моя история отношений с ними.
Но накопить данные конечно легче чем применять их так, чтобы была максимальная польза. Чтобы CRM приносила эту пользу, я должен помнить, кого и как там искать, какие фильтры включить, что делать дальше и как масштабировать это.
Я хотел автоматизировать почти всё.

Что было в первой версии моей CRM 

Источники данных существовали независимо друг от друга:

  • Контакты, которые у меня появились после личных знакомств на конференциях и телефонных митингов 

  • Отдельно информация о встречах и звонках за 2022–2026 годы;

  • архивы Telegram с десятками тысяч диалогов;

  • карточки людей, заметки, статусы и теги;

  • отдельная старая CRM, которая умела отправлять сообщения.

После объединения и дедупликации получалось 9 718 сущностей‑контактов.

Но один человек мог присутствовать сразу в нескольких источниках: например, сначала написать в Telegram, потом прийти на звонок, а через год появиться в заметках под каким‑то другим именем.

В общем, определить, какие записи относятся к одному человеку, сохранить происхождение каждого факта и не потерять историю — не всегда просто

Почему я не стал переписывать CRM с нуля

Сначала я хотел взять эту ранее написанную старую CRM, составить список функций и написать вместо нее новую версию, которую смогу сделать идеальнее.

Я от этого отказался.

У старой CRM уже были рабочие механизмы авторизации, отправки сообщений, управления аккаунтами и соблюдения ограничений Telegram. Переписывание всего сразу означало бы повторить несколько лет накопленных исключений и ошибок, часть которых никто уже не помнил.

Я разделил систему на две части.

Старая CRM выполняет механическую работу:

  • читает и записывает данные;

  • получает события;

  • отправляет сообщения;

  • соблюдает лимиты;

  • повторяет запрос после временной ошибки;

  • сохраняет доказательство отправки.

Все решения переходят к агенту. Он решает:

  • кого сейчас стоит поднять из базы;

  • почему этот человек важен;

  • что изменилось после последнего общения;

  • какое действие будет следующим;

  • нужен ли вообще контакт сейчас.

Получилась не новая CRM в привычном смысле. Скорее, старая CRM стала руками, а агент — как бы слоем принятия решений.

Как система устроена сейчас

В ней пять основных слоёв:

Немного про каждый слой отдельно.

1. Архив событий

У меня 51 000 Telegram‑диалогов по четырём аккаунтам. Выгружать полтора миллиона сообщений через LLM дорого и медленно. С этой работой лучше справляются обычные скрипты и SQLite:

SELECT
    dialog_id,
    MAX(message_date) AS last_contact,
    COUNT(*) AS message_count
FROM messages
GROUP BY dialog_id;

Скрипт просто и бесплатно определяет дату последнего общения, количество сообщений и наличие ответа. Модель нужна, когда потребуется понять смысл разговора.

2. Единая карточка

Следующий слой объединяет события вокруг человека.

Контакт может быть записан как:

  • имя в заметках после звонка;

  • Telegram username;

  • числовой Telegram ID;

  • телефон из WhatsApp;

  • имя и компания из визитки;

  • участник группового чата.

Надёжного универсального идентификатора нет. Username меняется, имена совпадают, телефон присутствует не везде.

Поэтому объединение идёт в несколько ступеней:

  1. Точное совпадение идентификатора.

  2. Совпадение по username.

  3. Имя плюс компания или другой дополнительный признак.

  4. Совпадения проверяются, не объединяются автоматически.

В карточке сохраняется происхождение каждого факта. Если два источника противоречат друг другу, система должна показать мне это.

3. Высчитываю все параметры

Я проставил критерий по тому, какие у меня отношения с этим лидом:
— Hot;
— Warm;
— Lukewarm;
— Cold;
— Archived.

Дополнительно каждый контакт получил приоритет реактивации от 0 до 100.

Упрощённо формула такая:

priority =
    0.35 × recency
  + 0.20 × frequency
  + 0.25 × depth
  + 0.20 × resurgence
  − penalty

Recency ‑давность последнего содержательного касания; frequency — количество звонков и разговоров; depth — глубина отношений; resurgence — новый входящий сигнал; penalty — оставшиеся без ответа сообщения.

score = 2 ** (-days_since_event / half_life)

Для разных типов событий используются разные сроки пересмотра.
Например я сейчас делаю обновления такого перерасчета и задачи по моим контактам (моим лидам) такие:
16 — написать сейчас;
77 — надо обработать на этой неделе;
601 — рассмотреть в течение месяца;
остальные — оставить в покое до появления новых данных.

4. Автоматизация

Передавать агенту всю историю CRM при каждом запросе нельзя. Даже если забыть о цене, данных для обработки слишком много.

Сначала работает детерминированный поиск:

SELECT *
FROM people
WHERE reactivation_priority >= 75
ORDER BY reactivation_priority DESC
LIMIT 20;

Для каждого лида собирается информация:

  • кто это;

  • откуда мы знакомы;

  • дата последнего контакта;

  • последние содержательные сообщения;

  • результаты звонков;

  • незакрытые договорённости;

  • подтверждённые общие интересы;

  • причина, по которой контакт попал в очередь.

Только после этого подключается языковая модель.

Её задача — не искать человека среди тысяч карточек. Она должна решить локальную задачу: что разумно сделать с конкретным контактом, имея подтверждённые факты.

Если данных недостаточно, правильным результатом считается решение needs_review. 

5. Автоматизация коммуникации с лидами

Агент не отправляет сразу сам сообщения.

Сначала он подготавливает действие. Затем это действие получает отдельный транспортный слой, который ничего не знает о стратегии, зато знает ограничения канала.

Он проверяет:

  • с какого аккаунта можно писать этому человеку;

  • не превышен ли лимит;

  • не было ли это сообщение уже отправлено;

  • не пересекаются ли параллельные кампании;

  • есть ли идентификатор доставленного сообщения;

  • нужно ли остановиться после ответа.

Между отправками добавляется случайная задержка. При FloodWait транспорт ждёт и повторяет операцию.
Все аккаунты используют общий журнал бюджета, поэтому два параллельных процесса не могут независимо решить, что лимит ещё свободен.

if not budget.can_send(account):
    return "LIMIT_REACHED"

if already_contacted(person, campaign):
    return "DUPLICATE"

result = send_with_jitter(message)

if result.message_id:
    record_delivery(result.message_id)
else:
    record_attempt()

ATTEMPT означает, что система попыталась отправить сообщение. SENT означает, что получено доказательство доставки. Смешивать эти два состояния конечно нельзя.

Пример: 

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

В старой CRM у него до сих пор висит статус new: его поставили при заведении карточки и с тех пор никто не менял, потому что статус ни с чем не связан.

Ночной сборщик получает свежие сообщения и видит новый входящий сигнал. Детерминированный расчёт обновляет дату последнего контакта и поднимает приоритет реактивации.
Агент получает только карточку лида с последними сообщениями и с информацией об обещаниях, которые надо выполнять.

После подтверждения сообщение уходит через транспорт. CRM сохраняет идентификатор доставки, дату и причину касания. Если человек отвечает, запланированная цепочка автоматически останавливается.

В этом месте CRM становится моей системой продолжения отношений с людьми

Не получилось

Не удалась попытка заставить LLM делать всё

Очень хотелось давать агенту открытые запросы по типу «найди лучшего лида» для определенной цели. На практике это дорогой и плохо проверяемый путь. Модель тратит контекст на сортировку, которую SQL выполняет быстрее и точнее.

Я делаю разделение: код считает, база фильтрует, модель оценивает смысл, транспорт отправляет.

Доверие к статусам

Статусы быстро устаревают, сразу после последнего обновления. Вычисляемая температура тоже может ошибаться, но она хотя бы содержит формулу, дату расчёта и исходные сигналы.

Автоматизация только исходящих

Изначально почти все функции отвечали на вопрос «кому написать»: кампании, напоминания, холодные сообщения, анализ участников групп. Я делал большое количество исходящих рассылок

Но я поменял акценты, мне важнее сейчас тот лид, кто пришёл сам. Поэтому входящие обращения получили отдельную очередь, владельца и срок реакции.

Что в итоге автоматизировано

Сейчас без ручного просмотра тысяч карточек выполняются:

  • загрузка диалогов и сообщений;

  • объединение данных из разных источников;

  • обновление карточек;

  • определение давности и глубины отношений;

  • вычисление температуры;

  • построение очереди контактов;

  • обнаружение новых входящих сигналов;

  • подготовка контекста для агента;

  • предложение следующего действия;

  • контроль лимитов и защита от повторной отправки;

  • доставка и сохранение подтверждения;

  • остановка цепочки после ответа;

  • написание ответа лиду с продолжением диалога;

  • сигнал человеку в спорных случаях.

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

Сколько токенов это экономит

Без участия LLM выполняются обработка примерно 1,7 млн сообщений, агрегация десятков тысяч диалогов, дедупликация, расчёт давности, фильтрация, проверка лимитов и журналирование отправок.

Модель видит только те несколько карточек, которые уже отобраны обычным кодом. Поэтому стоимость работы агента зависит не от размера всей CRM, а от размера текущей очереди решений.

Большая память агента не должна целиком помещаться в контекст. Она должна уметь доставать маленький, проверяемый фрагмент в нужный момент.

Что бы я сделал иначе

Если бы пришлось начинать заново, я бы не начинал с интерфейса CRM.

Я бы построил систему в таком порядке:

  1. Неизменяемый архив событий.

  2. Устойчивые идентификаторы людей.

  3. Провенанс каждого факта.

  4. Вычисляемый скоринг с датой пересчёта.

  5. Очередь следующих действий.

  6. Отдельный безопасный транспорт.

  7. И только потом интерфейс.

Большая часть ценности — в правильном переходе от события к следующему действию.

Вывод

Я наполнил CRM данными, а автоматизация началась позже

Теперь:

  • архив хранит факты;

  • база связывает их с людьми;

  • обычный код считает;

  • агент принимает содержательные решения;

  • транспорт выполняет действия;

  • журнал доказывает, что они действительно выполнены.

Что я хочу сделать еще? Проанализировать результаты автоматизации и результаты по конверсиям по всей моей базе лидов. Мои агенты будут считать не только все, что и кому они написали, а будут делать мне живые графики по тому, какие категории лидов приносят больше денег, какие этапы коммуникаций стоит доработать, какие формулировки и варианты ответов лидам стоит совершенствовать, чтобы в результате росло количество продаж моих сервисов.

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