Перейти до вмісту
CRM9 листопада 2026 р.7 хв читання

CRM на одному екрані: приймання лідів, фільтри та відправлення в n8n

Як зібрати просту CRM для одного користувача: SQLite, API приймання, дедуп, ШІ-синхронізація Telegram кожні 20 хвилин, розумний розподіл за 15 параметрами, задачі з плану та outbox у n8n.

Головне

CRM синхронізує Telegram через ШІ кожні 20 хвилин: ШІ читає відповіді, відрізняє клієнта від бота, оновлює CRM і розкладає лідів за 15 параметрами. Задачі народжуються з плану: перший контакт → follow-up → розбір відповіді → повтор.

ПоділитисяTelegram
Назад до блогу
Зміст

Проблема: ліди в трьох місцях

До цієї CRM заявки жили в трьох незв'язаних місцях: форма портфоліо писала в JSONL і Telegram, AI OPS зберігав демо-заявки в Supabase, а Telegram-ліди лежали в CSV. У кожного джерела був свій формат, але не було спільної відповіді на питання «що з цим лідом відбувається далі».

Потрібна була не копія великої CRM, а один робочий екран: лід прийшов, отримав статус, наступний крок і пішов у n8n. Без ролей, відділів і сорока вкладок налаштувань.

Що увійшло в MVP

  • один користувач і один вхід;
  • приймання заявок з портфоліо та AI OPS через спільний API;
  • ручне додавання й імпорт Telegram-лідів;
  • картка з етапом, температурою, score, бюджетом, тегами, нотатками та задачами;
  • фільтри за етапом, джерелом, температурою, тегами, бюджетом, датою й наступним кроком;
  • відправлення в n8n із журналом доставок і повторами;
  • SQLite, systemd, Caddy і щоденний бекап.

Один вхід замість трьох інтеграцій

Усі джерела надсилають однаковий формат lead.v1:

{
  "schema": "lead.v1",
  "external_id": "portfolio:8f3c...",
  "source": "portfolio",
  "name": "Іван",
  "email": "ivan@example.com",
  "message": "Потрібна CRM для відділу продажів",
  "consent": { "granted": true, "at": "2026-11-09T10:00:00Z" }
}

API приймає Bearer-ключ і Idempotency-Key. Повторний запит з тим самим ключем не створює другий лід. Якщо email, Telegram або телефон уже є в базі, CRM доповнює картку й записує подію «повторне звернення».

Це важливіше, ніж здається. Форми повторюють запити, люди надсилають заявку двічі, а власник не повинен вручну розбирати дублі.

SQLite та черга замість інфраструктури

Для одного користувача й десятків лідів на день SQLite достатньо. WAL, індекси та busy_timeout закривають читання, фільтри й фонові записи. База — один файл, а бекап — копія файла, а не окремий сервер Postgres.

Відправлення в n8n зроблено через outbox:

  1. лід створюється в SQLite;
  2. поруч з'являється запис deliveries зі статусом queued;
  3. worker надсилає payload на вебхук;
  4. при 2xx ставить sent, при 5xx — повторює з backoff;
  5. після восьми спроб — failed і кнопка «Повторити».

Прямий fetch із форми виглядає простіше, але якщо n8n недоступний, лід залишається без доставки. Черга переживає рестарт і не блокує відповідь користувачу.

Фільтри, які справді потрібні

Фільтри в CRM — це не «поле для галочки». Вони відповідають на щоденні питання: що нове, що горить, кому не відповіли, що готове до відправлення, де помилка n8n.

У поточній версії є:

  • етап, джерело й температура;
  • теги, бюджет і score;
  • дата створення й остання активність;
  • наступний крок: прострочено, сьогодні, цього тижня, не задано;
  • статус відправлення в n8n;
  • архів і спам;
  • збережені види та експорт поточного фільтра в CSV.

Стан фільтрів живе в URL. Посилання можна зберегти, надіслати собі в Telegram або відкрити на телефоні.

Дедуп за контактами

Ключі дедупу за пріоритетом:

  1. source + external_id;
  2. нормалізований email;
  3. Telegram без @;
  4. телефон у E.164;
  5. нечіткий збіг імені та компанії — лише прапорець, без автооб'єднання.

Нормалізовані поля зберігаються окремо. Це дозволяє знайти одну й ту саму людину, навіть якщо в одній формі вона написала @Username, а в іншій — посилання https://t.me/username.

Контракт із n8n

n8n отримує не «плоский JSON на око», а стабільний контракт:

{
  "schema": "lead.v1",
  "delivery_id": "dlv_...",
  "event": "lead.created",
  "lead": {
    "id": "ld_...",
    "name": "Іван",
    "source": "portfolio",
    "temperature": "hot",
    "score": 70
  },
  "consent": { "granted": true },
  "meta": { "attempt": 1 }
}

Запит підписаний HMAC-SHA256:

X-CRM-Signature: sha256=<hmac(secret, timestamp + "." + body)>

n8n перевіряє підпис першим вузлом. Далі workflow сам вирішує, куди вести ліда: у Telegram, таблицю, CRM клієнта або пошту.

Імпорт Telegram-лідів

Сценарій — 120+ прямих контактів із Telegram-вивантаження. Імпорт ідемпотентний: external_id = telegram-tools:<id>, дедуп за @username.

У картці зберігаються група, категорія, офер, score, посилання на пост і дата поста. Оригінальна дата залишається в метаданих, а created_at виставляється на момент імпорту — щоб дашборд показував, що ліди справді додані.

Розумна синхронізація: лід не живе у двох місцях

Кожні 20 хвилин ШІ-шар читає нові повідомлення й оновлює CRM: перше повідомлення, відповіді, етап, температуру, теги, останню активність і помилки доставки. Модель відрізняє живого клієнта від бота чи агрегатора, а детермінований синк переносить дані без дублів. Не потрібно вручну копіювати листування та переносити статуси.

Синк ідемпотентний. Повторний запуск оновлює ту саму картку, а не створює дубль: дедуп за Telegram, email і телефоном. Якщо відповідь надійшла о 16:32, вона з'явиться в CRM на найближчому тику, а задача «Розібрати відповідь» закриється після позначення повідомлення прочитаним.

CRM: ШІ-синхронізація, ліди й задачі на демо-даних

Розподіл: 15 параметрів

Лід не просто падає в загальний список. Він розкладається за чергами та пріоритетами за параметрами:

  • джерело й група, звідки надійшов запит;
  • етап: новий, зв'язалися, кваліфікований, пропозиція, переговори;
  • температура: гарячий, теплий, холодний;
  • score 0–100;
  • бюджет і валюта;
  • мова й часовий пояс;
  • канал зв'язку: Telegram, email, телефон;
  • остання активність;
  • вік ліда;
  • SLA без відповіді;
  • є відповідь чи ні;
  • дата follow-up;
  • теги: tier, day, sent, replied, flood, bot-reply;
  • статус доставки в n8n;
  • наявність відкритої задачі та наступного кроку.

На практиці це дає зрозумілі зрізи: «гарячі без відповіді», «прострочений follow-up», «відповіли, але задача не розібрана», «flood — повторити після паузи». Один і той самий лід не губиться між чатом, таблицею та нотатками.

Задачі: спочатку планування, потім дія

Автоматичні задачі з'являються не з хаосу, а з плану:

  1. План за датами. Перший контакт — за датою й часом із плану, follow-up — через три робочі дні, повтор після блокування — після паузи.
  2. Черга задач. З плану народжуються конкретні задачі: перше повідомлення, follow-up, розбір відповіді, повтор після PEER_FLOOD.
  3. Next action. У ліда завжди один найближчий крок, а не десять рівнозначних.
  4. Автозакриття. Відповідь розібрана або надіслана — задача закривається, next action перераховується.

Так задачі допомагають рухати ліда, а не створюють видимість роботи.

Робоче місце, а не таблиця

Після перших тижнів стало зрозуміло: мало просто зберігати ліди. Потрібно, щоб список був робочим місцем.

Що додав:

  • 21 колонка — від компанії й Telegram до тегів і останнього контакту; набір зберігається в URL.
  • Inline-редагування — етап, температура, score, бюджет і дата наступного кроку змінюються прямо в рядку.
  • Збережені види — фільтр + сортування + колонки під назвою.
  • Розширені фільтри — score, канал зв'язку, давність активності та вік нових.
  • SLA-мітка — «N днів без відповіді» у нових лідів.
  • Глобальний пошук — ⌘K / Alt+K за іменем, компанією, email, Telegram і текстом.
  • Масові дії та експорт вибраних — етап, температура, тег, «Зв'язався», архів, n8n.
  • Закріплення та j/k — важливі ліди зверху, навігація рядками з клавіатури.
  • Задачі та наступні кроки — окремий екран; закриття задачі оновлює next action.
  • Збереження стану — повернення з картки відкриває той самий відфільтрований список.

Це не «ще десять кнопок», а сценарії, які прибирають переходи в картку та ручну рутину.

Що вийшло

У сценарії — 120+ Telegram-контактів і 30 активних діалогів. 10+ функцій, один користувач, одна база, один екран.

Що виявилося важливим:

  • не робити ролі та команди, яких немає;
  • не тягнути Postgres заради одного користувача;
  • зберігати нормалізовані контакти, а не сподіватися на «чисті» дані;
  • відокремити приймання ліда від відправлення в n8n;
  • зробити фільтри посиланнями, а не станом у голові.

Що далі

Наступні кроки — не «більше полів», а менше ручної роботи: CSV-імпорт в інтерфейсі, склеювання дублів, автоматичні правила відправлення та звіт за джерелами. Але поточна версія вже закриває головний біль: лід не губиться між формою, Telegram і n8n.

Повний кейс з архітектурою та деталями: CRM для лідів.

Валера · webdev07

Senior Systems Engineer. Будую системи, які працюють.

@webdevs07

Потрібна схожа автоматизація?

Напиши в Telegram — обговоримо задачу та формат роботи.

Обговорити проєкт