Danil Makarov внедрение · автоматизацияimplementation · automation
Кейс · Внутренняя автоматизацияCase study · Internal automation

Lead Capture

Ответы на исходящие письма превращаются в размеченные лиды и назначенные встречи — без человека, читающего каждый ящик.Outbound email replies become labelled leads and booked meetings, without a person reading every inbox.

Leadhype — B2B-агентство лидогенерации в США, которое ведёт исходящие кампании с примерно 5 000 отправляющих ящиков. Ответы возвращались в эти же ящики, и часть положительных терялась. Я написал спецификацию, собрал сервис с AI-ассистированной разработкой и веду его в проде.Leadhype is a U.S. B2B lead-generation agency running outbound campaigns from about 5,000 sending mailboxes. Replies came back across those mailboxes, and some positive ones were lost. I wrote the spec, built the service with AI-assisted development, and run it in production.

Роль:Role: спецификация → сборка → эксплуатацияspec → build → run Год:Year: 2026 Срок до v0.1:Time to v0.1: 1 неделя1 week
Lead Capture architecture: Smartlead replies arrive by webhook, are deduplicated, labelled by an LLM, routed to Slack and reconciled with Calendly; a fallback poller and monitoring cover failures. Smartlead campaign replies Calendly bookings Webhook ingest trigger.dev Fallback poller every 30 min Dedup idempotent writes LLM labelling reply intent Router real leads only Slack lead channel Supabase (PostgreSQL) leads · labels · bookings · reconciliation Monitor heartbeat quota · bounces failed runs → alerts webhook reconcile
Архитектура · без данных клиентовArchitecture · no client data on this page
01

ПроблемаThe problem

Исходящие кампании отправляются с тысяч ящиков через Smartlead. Ответы приходят туда же: часть — автоответы и отказы, часть — «да, давайте созвонимся». Их нужно было найти, разметить, передать команде и убедиться, что встреча действительно назначена.Outbound campaigns go out from thousands of mailboxes through Smartlead. Replies come back the same way: some are auto-replies and opt-outs, some say “yes, let's talk”. Those had to be found, labelled, handed to the team, and checked against whether a meeting was actually booked.

Аудит пайплайна показал 36 положительных ответов, на которые никто не ответил с августа.An audit of the pipeline found 36 positive replies nobody had answered since August.

Единого источника истины не было: ответы жили в почтовых ящиках, бронирования — в Calendly, а статус лида — в головах людей. Любое изменение процесса требовало ручной сверки.There was no single source of truth: replies lived in inboxes, bookings in Calendly, lead status in people's heads. Any process change meant manual reconciliation.

1 wk
от спецификации
до v0.1 в проде
from spec
to v0.1 in production
36
неотвеченных лидов
найдено аудитом
unanswered leads
found by the audit
5k
отправляющих ящиков
за пайплайном
sending mailboxes
behind the pipeline
4
внешних интеграции
Smartlead · LLM · Slack · Calendly
external integrations
Smartlead · LLM · Slack · Calendly
02

Как это работаетHow it works

Шесть шагов от письма до встречи. Каждый шаг идемпотентен: событие можно безопасно обработать дважды.Six steps from an email to a meeting. Every step is idempotent: an event can safely be processed twice.

01
Приём по webhookWebhook ingestion

Smartlead шлёт событие ответа; задача в trigger.dev принимает его и записывает в Supabase.Smartlead sends a reply event; a trigger.dev task receives it and records it in Supabase.

02
ДедупликацияDeduplication

Повторные доставки и ретраи схлопываются: один ответ — одна запись лида.Repeat deliveries and retries are collapsed: one reply, one lead record.

03
LLM-разметка намеренияLLM intent labelling

Модель получает текст ответа и небольшой фиксированный набор меток намерения. Узкий набор держит точность и делает ошибки заметными.The model gets the reply text and a small, fixed set of intent labels. The narrow set keeps accuracy up and makes mistakes visible.

04
Маршрутизация в SlackRouting to Slack

Только реальные лиды уходят в рабочий канал команды вместе с текстом ответа и его контекстом.Only real leads go to the team's working channel, together with the reply and its context.

05
Сверка с CalendlyCalendly reconciliation

Бронирования сопоставляются с лидами, чтобы «интерес» превращался в «встреча назначена», а не оставался в Slack навсегда.Bookings are matched to leads so that “interested” becomes “meeting booked” instead of living in Slack forever.

06
Резервный поллинг и мониторингFallback polling and monitoring

Если webhook не пришёл, поллер каждые 30 минут подхватывает пропущенное. Heartbeat и алерты: квоты API, падения задач и отказавшие bounce-webhooks дают сигнал, а не тишину.If a webhook never arrives, a poller running every 30 minutes picks up what was missed. A heartbeat plus alerts: API quotas, failed runs and broken bounce webhooks raise a signal instead of silence.

03

Решения, которые имели значениеDecisions that mattered

01

Спецификация до кодаSpec before code

Одна страница: источники событий, модель данных, набор меток, критерии приёмки по существующему ручному процессу. Утверждена CEO до начала сборки — спорили о тексте, а не о готовом коде.One page: event sources, data model, label set, acceptance criteria against the existing manual process. Approved by the CEO before the build started: we argued about the text, not about finished code.

02

Webhook плюс поллинг, а не «или»Webhook plus polling, not either

Webhooks быстрые, но теряются; поллинг надёжный, но медленный и ест квоту. Вместе они дают скорость и полноту, а дедупликация делает их безопасными.Webhooks are fast but get lost; polling is reliable but slow and burns quota. Together they give speed and completeness, and deduplication makes them safe.

03

Узкий набор метокA narrow label set

Фиксированный набор меток вместо свободного текста. Меньше свободы для модели — меньше ошибок и проще проверить выборку руками.A fixed set of labels instead of free text. Less freedom for the model means fewer mistakes and an easy manual spot-check.

04

Исторический backfillHistorical backfill

Сервис прогнали по накопленным ответам до запуска. Так нашлись 36 неотвеченных лидов и появилась проверка качества разметки на реальных данных.The service was run over the accumulated replies before go-live. That surfaced the 36 unanswered leads and gave a quality check of the labelling on real data.

05

Приёмка по старому процессуAcceptance against the old process

Результаты сервиса сверялись с тем, что давал ручной процесс; расхождения разбирались по одному. Только после этого ручной шаг убрали.The service's output was checked against what the manual process had produced; differences were reviewed one by one. Only then was the manual step removed.

04

СтекStack

trigger.dev
Фоновые задачи: приём webhook, поллинг, ретраи, расписанияBackground jobs: webhook handlers, polling, retries, schedules
Supabase · PostgreSQL
Лиды, метки, бронирования, журнал событийLeads, labels, bookings, event log
Vercel
Хостинг эндпоинтов и деплойEndpoint hosting and deployment
LLM API
Классификация намерения ответа по фиксированному набору метокReply-intent classification against a fixed label set
Smartlead API
Webhooks ответов и поллинг кампанийReply webhooks and campaign polling
Slack API · Calendly API
Доставка лидов и сверка бронированийLead delivery and booking reconciliation
Claude Code
AI-ассистированная разработка по спецификацииAI-assisted development from the spec
05

Что ломалось в продеWhat broke in production

Та часть, о которой спрашивают на интервью.The part people ask about in interviews.

→

Конфликт маршрутизации webhooksWebhook routing conflict

События приходили не тому обработчику. Развели маршруты и добавили проверку источника события.Events were reaching the wrong handler. Separated the routes and added validation of the event source.

→

Квота API кончалась молчаAPI quota ran out silently

Поллинг останавливался без единого алерта. Добавили оповещение задолго до лимита.Polling stopped without a single alert. Added an alert well before the limit is reached.

→

Отказавшие bounce-webhooksFailing bounce webhooks

Отскоки не доходили, и всё, что от них зависит, молчало. Починили доставку и поставили её под тот же мониторинг.Bounces were not arriving, so everything downstream of them stayed silent. Fixed delivery and put it under the same monitoring.

06

Моя роль и как это собраноMy role, and how it was built

Я отвечал за сервис целиком: проблема, спецификация, модель данных, интеграции, запуск и эксплуатация. Код в основном написан Claude Code по моей спецификации; я проверял, тестировал на реальных ответах, разворачивал и держу сервис в проде.I owned the service end to end: problem, spec, data model, integrations, go-live and operations. Most of the code was written by Claude Code from my spec; I reviewed it, tested it on real replies, deployed it, and keep the service running in production.

Это сознательный выбор, а не компромисс: за неделю в одиночку такой сервис иначе не собрать. Моя ценность здесь — в том, что система правильно спроектирована, честно проверена и переживает отказы, а не в том, сколько строк я набрал сам.This is a deliberate choice, not a compromise: one person cannot build this in a week any other way. My value here is that the system is designed right, tested honestly and survives failures, not how many lines I typed myself.

На этой странице нет названий клиентов и данных кампаний.No client names or campaign data appear on this page.