Перейти к содержимому
VC
Кейс № 21 · E-commerce / Внутренние системы

Внутренняя CRM для операционной команды: один экран вместо пяти вкладок

Общий почтовый ящик, живые данные Shopify, отгрузки, звонки, ответы опроса и возвраты по клиенту — в одном окне. Написан на стандартной библиотеке Python и работает на дроплете с 961 МБ памяти.

Индустрия
E-commerce, два магазина на Shopify
Стек
Python stdlib · Shopify Admin API · IMAP
Сроки
≈ 6 недель до боевого использования
Итог
Карточка клиента 10,3 с → ≈3 с
01 · Боль

Ответ на вопрос «где мой заказ» собирался из пяти систем

US-бренд товаров для активного отдыха, два магазина на Shopify. Двое операционистов вели клиентов, переключаясь между двумя админками Shopify, почтовым клиентом, облачной телефонией и складскими таблицами.

Самый частый вопрос покупателя — «где мой заказ». Чтобы на него ответить, нужно было найти письмо, вытащить из него номер заказа, вспомнить, в каком из двух магазинов этот заказ лежит, открыть нужную админку, найти отгрузку, вернуться в почту и написать ответ. Пять переключений контекста на одну строчку текста.

Чарджбеки одного из магазинов не были видны вообще: они приходили в интерфейс, в который операционная команда просто не заходила. Узнавали о них постфактум.

Первая версия карточки клиента, когда её собрали, грузилась 10 секунд — и операционист сказал об этом прямо. Это и стало главным техническим требованием: инструмент, который открывается дольше, чем ручное переключение вкладок, никто использовать не будет.

02 · Решение

Своя CRM на стандартной библиотеке Python

HTTP-сервер на http.server из стандартной библиотеки, без веб-фреймворка. Не из эстетики: так он ставится на сервер, где нет pip и нет права его туда принести. Ни одной внешней зависимости — значит, нечему сломаться при обновлении и нечего чинить через полгода.

01
Синк почты

IMAP каждые 15 минут: два корпоративных ящика, более 100 тыс. писем

02
Парсинг

Разбор в треды, классификация, потоковая запись на диск

03
Индексы

Адрес → смещения строк; отдельный индекс номеров заказов по телу писем

04
Карточка

Шесть источников параллельно: заказы обоих магазинов, отгрузки, звонки, опрос, возвраты

05
Ответ

Шаблоны, подписи, AI-черновик, отправка и пересылка с вложениями через очередь

Шесть источников параллельно — и честная деградация

Карточка клиента собирает шесть независимых источников одновременно: заказы первого магазина, заказы второго, отгрузки, историю звонков из облачной телефонии, ответы на пост-продажный опрос и возвраты с чарджбеками.

Ключевая деталь — thread-local счётчик сбоев, который переносится в главный поток. Без него отвалившийся источник просто не показывался бы, и неполная карточка молча считалась бы полной. Операционист видел бы «заказов нет» там, где на самом деле «API магазина сейчас не ответил». Это разные вещи, и клиенту про них говорят разное.

Индексы вместо стрима всего корпуса

Почта двух ящиков — более 100 тыс. писем, корпус в сотни мегабайт. Первая версия на каждый просмотр карточки читала его целиком: отсюда 5,8 секунды на поиск писем по клиенту.

Построил два индекса:

  • Адрес → смещения строк — открыть корпус, прыгнуть по offset, прочитать только нужные письма
  • Номер заказа → клиент — индекс номеров, вытащенных из тел писем: покупатель пишет с личного адреса, а заказ оформлен на рабочий — карточка всё равно находится

Потоковый парсер: иначе OOM-kill

Парсер почты изначально держал разобранный корпус в памяти — на дроплете с 961 МБ это заканчивалось OOM-kill. Переписал на потоковый: письма проливаются на диск по одному, в памяти остаётся только лёгкая проекция заголовков. Пик памяти упал с 985 МБ до 256 МБ (на самом сервере — 215 МБ), при этом результат побайтово идентичен: то же количество писем и тредов, sha256 содержимого совпал.

Всё, что нужно, чтобы не выходить из окна

  • Отправка и пересылка писем с вложениями через очередь — интерфейс не блокируется на SMTP
  • Персональные шаблоны и подписи у каждого сотрудника
  • AI-черновик ответа — по контексту треда и данным карточки; редактируется перед отправкой
  • Персональные аккаунты с памятью устройства — не логиниться заново каждое утро
  • Аудит-лог «кто / что / чем кончилось» — по любому действию видно, кто его сделал и чем оно завершилось
  • Блок складских остатков из Lark — прямо в карточке, без отдельной таблицы
  • Полноценная мобильная вёрстка — проверено на 390px, отвечать можно с телефона

Переезд на выделенный сервер — за один день

Сначала CRM жила на общем сервере вместе с трекером — соседство, при котором любой всплеск нагрузки бьёт по обоим. Вынес на отдельный сервер за nginx + Let’s Encrypt, с 15-минутным IMAP-синком, ежечасным монитором и ночными бэкапами. Переезд сверен паритетом: письма, треды и заказы на дашборде сошлись один в один со старой инсталляцией по каждому счётчику.

03 · Стек

Ноль внешних зависимостей — осознанно

Python stdlib

http.server, imaplib, sqlite3, urllib — ставится на сервер, где нет pip

Shopify Admin API

Живые заказы и отгрузки двух магазинов, без промежуточной базы

IMAP / SMTP

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

LLM для черновиков

Черновик ответа по контексту треда — сотрудник правит и отправляет

systemd · nginx · Let's Encrypt

Выделенный сервер, TLS через certbot, синк и мониторы по таймерам

pytest

454 собираемых теста, 28 тестовых файлов при 15 модулях кода

Python stdlibhttp.serverimaplibsqlite3Shopify Admin APIIMAP/SMTPLLMsystemdnginxLet's Encryptpytest
04 · Результат

Замеры до и после

Карточка клиента
10,3 с ≈3 с

шесть источников, собранные параллельно

Поиск писем по клиенту
5,8 с 0,03–0,45 с

индекс смещений вместо чтения всего корпуса

Пик памяти парсера
985 МБ 256 МБ

на самом сервере 215 МБ; результат побайтово идентичен

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

Оптимизация памяти проверялась не «на глаз»: после переписывания парсера сверялись количество писем, количество тредов и sha256 содержимого — совпали. Это тот случай, когда ускорение не имеет права поменять данные, и это надо доказать, а не заявить.

Переезд на выделенный сервер занял один день и был принят по паритету со старой инсталляцией. На проекте — 120 коммитов в модуле CRM и 454 собираемых теста (28 тестовых файлов при 15 модулях кода): внутренний инструмент, но не «скрипт на коленке».

05 · Применимость

Где ещё ложится та же методология

Это не «CRM для интернет-магазина». Это типовая задача «склеить N систем в один экран вокруг одной сущности» — там, где сотрудник каждый день руками делает join между вкладками:

  • Поддержка на общем ящике — почта + учётная система + доставка в одном треде вместо трёх вкладок
  • Мультиканальные продажи — несколько витрин и маркетплейсов, у каждого своя админка, а клиент один
  • Сервис и выездные работы — заявка, история звонков, склад запчастей и статус наряда по одному клиенту
  • Архивы переписки как источник данных — индексация корпуса писем и поиск по номерам документов внутри тел
  • Замена коробочного helpdesk — когда абонплата за seat растёт, а нужны две-три специфичные вкладки, которых в коробке нет
Что переиспользуется на следующих проектах
  • Параллельный сбор карточки с честным учётом сбоев — неполные данные никогда не выдаются за полные
  • Индексатор почтового корпуса: адрес → смещения и номер документа → клиент
  • Потоковая обработка вместо «загрузить всё в память» — работает на самых дешёвых VPS
  • Проверка миграции паритетом счётчиков и хешей, а не «вроде открылось»
  • Аудит-лог действий сотрудников по схеме «кто / что / чем кончилось»
Похожая задача?

Если сотрудник делает join между вкладками руками — это собирается в один экран

Начинаем с самого частого вопроса, на который команда отвечает каждый день, и собираем ровно тот экран, который на него отвечает. Первая рабочая версия — недели, а не кварталы, и её сразу проверяет тот, кто в ней будет сидеть.

Готовы начать?

Аудит за 5 000 ₽ — с конкретным отчётом и сметой

Расскажу что внедрить в вашем бизнесе в первую очередь, какая будет окупаемость, и нужен ли вообще AI для вашей задачи (иногда — нет).

Или просто напишите свой вопрос — отвечу в течение 2 часов