| name | hh-outreach |
| description | Массовая рассылка откликов на HH вакансии для лидгена. Триггеры: /hh-outreach, 'отклики hh', 'рассылка hh', 'откликнись на вакансии' |
/hh-outreach - Автоматические отклики на HH.ru
Полная спецификация: https://github.com/TimmyZinin/hh-outreach/blob/main/SPEC.md
Вызов
/hh-outreach <количество> [запрос] [--флаги]
Аргументы
| Аргумент | Тип | Дефолт | Описание |
|---|
| количество | число | обязательный | Сколько откликов отправить |
| запрос | строка | "маркетолог OR SMM OR контент" | Поисковый запрос HH |
| --query | строка | - | Альтернатива позиционному запросу |
| --period | число | 1 | Период поиска в днях |
| --min-salary | число | - | Минимальная зарплата |
| --dry-run | флаг | - | Поиск + генерация БЕЗ отправки |
| --preview | флаг | - | Показать текст перед каждой отправкой |
| --any-format | флаг | - | Не фильтровать по удалёнке |
| --no-filter | флаг | - | Отключить exclusion фильтр |
Процесс
Шаг 0: INIT
- Прочитать
~/hh-outreach-data/sent.json
- Если файл corrupted: восстановить из
.bak или создать пустой
- Валидация: JSON парсится, поле
vacancies - массив, каждый элемент имеет id и status
- Дедупликация: убрать записи с одинаковым
id
- Backup:
cp sent.json sent.json.bak
- Проверить лимит: если vacancies.length > 150 - показать предупреждение, если > 180 - запросить подтверждение
- Парсить аргументы (количество, запрос, флаги)
- CDP healthcheck:
CDP=~/.claude/skills/chrome-cdp/scripts/cdp.mjs
NODE=~/.nvm/versions/node/v22.22.1/bin/node
$NODE $CDP list
- Если ошибка "Daemon failed" - сообщить: "CDP недоступен. Открой chrome://inspect и включи remote debugging."
- Найти таб с hh.ru или открыть новый
Шаг 1: SEARCH
- Построить URL:
https://hh.ru/search/vacancy?text={QUERY}&order_by=publication_time&search_period={PERIOD}&items_on_page=20&page={PAGE}
- Добавить
&schedule=remote если НЕ --any-format
- Добавить
&salary={MIN_SALARY} если указан --min-salary
- Навигировать через
$NODE $CDP nav <tab> <url> (timeout 15s)
- Извлечь вакансии через
$NODE $CDP eval:
var cards = document.querySelectorAll('a[data-qa="serp-item__title"]');
- Фильтрация (если НЕ --no-filter):
- Исключить если title содержит: Wildberries, WB, Ozon, продаж, рекрут, HR, недвижим, ассистент, Junior, Младший, стажёр, дизайнер, developer, разработчик, motion, Python, Java, бухгалтер
- Дедупликация: исключить ID которые уже есть в sent.json
- Пагинация: если набрано меньше нужного - page++, повторить
- Если 0 результатов - показать "0 вакансий найдено", завершить
Шаг 2: READ (для каждой вакансии)
$NODE $CDP nav <tab> <vacancy_url> (timeout 15s)
- Извлечь через
$NODE $CDP eval:
- Название, компания, зарплата (raw), описание (первые 1000 символов)
- Проверить "Вы откликнулись" - если да: SKIP_ALREADY_APPLIED
- Парсинг зарплаты:
- "от 100 000" → 100000
- "до 150 000" → 150000
- "от 100 до 150" → 125000 (среднее)
- "$2 000" → 200000 (курс $1=100₽)
- "€2 000" → 220000 (курс €1=110₽)
- "gross" или "до вычета" → multiply 0.87
- Не парсится → null
Шаг 3: GENERATE
Сгенерировать кастомный текст по модульному шаблону:
OPENING (random из 5):
Коллеги из {КОМПАНИЯ}, привет!
Привет, команда {КОМПАНИЯ}!
{КОМПАНИЯ}, привет!
Привет! Пишу по вашей вакансии {ДОЛЖНОСТЬ}.
Коллеги, привет!
VACANCY_REACTION:
Насколько я понимаю, вы ищете {ПЕРЕСКАЗ - 1-2 предложения на основе описания}
OFFER (если зарплата есть):
У меня есть предложение получше. Вместо найма в штат предлагаю поработать на подряде - оплата на моё ИП, {SALARY*0.8} вместо {SALARY}.
OFFER (если зарплаты нет):
У меня есть предложение получше. Вместо найма в штат предлагаю поработать на подряде - оплата на моё ИП, на 20% дешевле чем штатный сотрудник с налогами.
WHAT_I_DO (match по ключевым словам):
| Ключевые слова | Текст модуля |
|---|
| SMM, соцсети, контент-план | AI-контент-завод генерит посты, визуалы, публикует на все площадки автоматически |
| лидогенерация, B2B, трафик | Лид-машина автоматически находит клиентов, обогащает контакты, генерит предложения |
| воронки, автоворонки, email | Вебинарные воронки с автоматическими прогревами, квалификация лидов |
| SEO, контент-маркетинг | AI генерит экспертные тексты под семантическое ядро, публикует |
| CRM, retention, рассылки | AI автоматизирует сегментацию, персональные письма, триггерные цепочки |
| видео, reels, YouTube | AI-видеопайплайн: HeyGen, ElevenLabs, автоматическая нарезка |
| блогеры, influencer | AI ищет блогеров, генерит ТЗ, отслеживает результаты |
SLOTS:
Я работаю как удалённый подрядчик. Беру до 5 клиентов одновременно, сейчас у меня есть 2 свободных слота.
CTA:
Кейсы: timzinin.com
Поговорить: calendly.com/timzinin
Anti-AI правила:
- Без тире (—), только дефис с пробелами ( - )
- Без слов: инновационный, комплексный, синергия, оптимизация
- Без hedge words: возможно, наверное, может быть
- Без passive voice
- Компактные абзацы
ШАБЛОН "BOLD 2026" (актуальный, утверждён 2026-04-10)
Используется по умолчанию для всех рассылок начиная с 2026-04-10. Задача: пробить
через рекрутера к фаундеру, продать себя вместо 10 сотрудников.
Выбор шаблона по категории вакансии
Вакансии делятся на 2 категории:
- MARKETING - CMO, Head of Marketing, Growth, маркетолог, SMM, контент, PR, brand
- AI_AUTOMATION - AI engineer, ML, data, Python, DevOps, CTO, аналитик,
product owner AI, prompt engineer
Заполнение плейсхолдеров
{COMPANY} - название компании из HH API
{POSITION_TITLE} - точное название вакансии
{SALARY} - если salary_from указан: "200 000 руб" или "200-450K руб".
Если salary отсутствует - использовать альтернативную формулировку (см. ниже).
Шаблон для MARKETING вакансий
Коллеги из {COMPANY}, слушайте.
Я даже календарь посмотрел на всякий случай — и да, на дворе 2026 год, а вы мне тут платите {SALARY} за работу человека на позиции {POSITION_TITLE}. Давайте я вам помогу этой ерундой прекратить заниматься?
Вместо того чтобы кормить 10 таких ребят, платите мне — я AI-инженер по процессам. Я вам соберу систему из Claude, GPT и автоматизаций, которая закроет вашу вакансию за меня. Я не пишу вам код руками — я вам строю армию агентов, которые работают за меня и за ваших будущих сотрудников сразу. В частности, {POSITION_TITLE} у вас закроется моими руками за пару дней.
Вы уже поняли, что вам пишет мой агент, да? Я так и работаю. Я плачу моим агентам копейки, а они мне делают работу целого отдела.
Вот мой сайт: timzinin.com
Вот мой календарь: https://calendly.com/timzinin
Хватит жечь свой бюджет на вакансии из прошлого — пора догонять прогресс.
С любовью,
Тим Зинин
Шаблон для AI_AUTOMATION вакансий
Коллеги из {COMPANY}, слушайте.
Я даже календарь посмотрел на всякий случай — и да, на дворе 2026 год, а вы мне тут платите {SALARY} за работу человека на позиции {POSITION_TITLE}. Давайте я вам помогу этой ерундой прекратить заниматься?
Вместо того чтобы кормить 10 таких ребят, платите мне — я AI-инженер по процессам. Я вам соберу систему из Claude, GPT и автоматизаций, которая закроет вашу вакансию за меня. Я не пишу вам код руками — я вам строю армию агентов, которые работают за меня и за ваших будущих сотрудников сразу. В частности, задачи с вашей вакансии {POSITION_TITLE} я заберу на свою машину и отдам вам результат.
Вы уже поняли, что вам пишет мой агент, да? Я так и работаю. Я плачу моим агентам копейки, а они мне делают работу целого отдела.
Вот мой сайт: timzinin.com
Вот мой календарь: https://calendly.com/timzinin
Хватит жечь свой бюджет на вакансии из прошлого — пора догонять прогресс.
С любовью,
Тим Зинин
Если зарплата НЕ указана
Заменить фрагмент
"вы мне тут платите {SALARY} за работу человека на позиции {POSITION_TITLE}"
на
"вы мне тут готовы платить живому человеку за работу на позиции {POSITION_TITLE}".
Правила форматирования (обязательные)
- Ровно 1 пустая строка между абзацами, никаких двойных пустых строк
- Никаких leading blank lines
- Никаких trailing blank lines больше одной
- Тире внутри предложений допускается (наглая разговорная интонация)
- Ссылки в чистом виде:
timzinin.com, https://calendly.com/timzinin
- Проверяется хуком
~/.claude/hooks/social-text-spacing.py
Шаг 4: SEND
- Если --dry-run: показать текст, записать SKIP_DRY_RUN, перейти к следующей
- Если --preview: показать текст, спросить OK/Skip
- Skip → записать SKIP_USER
- DOM-based клики (ВСЕ через eval по textContent, НЕ clickxy):
eval "var btns = document.querySelectorAll('a, button');
for (var i = 0; i < btns.length; i++) {
if (btns[i].textContent.trim() === 'Откликнуться') {
var rect = btns[i].getBoundingClientRect();
if (rect.y > 100 && rect.width > 100) { btns[i].click(); break; }
}
}"
- Обработать popup "вакансия в другой стране":
eval "var btns = document.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
if (btns[i].textContent.indexOf('Все равно') > -1) { btns[i].click(); break; }
}"
- Клик "Добавить сопроводительное" (eval по textContent, rect.y > 500)
- Polling textarea (каждые 500мс, timeout 5с):
eval "document.querySelector('textarea') ? 'ready' : 'no_ta'"
- Если textarea не найдена: SKIP_NO_TEXTAREA
- Ввод текста:
$NODE $CDP type <tab> "<text>"
- Клик "Откликнуться" в модалке (eval, rect.y > 500)
- Polling "Резюме доставлено" (каждые 500мс, timeout 5с)
- Если не найдено: screenshot + FAILED_NO_CONFIRMATION
- Anti-bot check: если на странице "капча", "подтвердите", "подозрительная активность" → FAILED_ANTI_BOT, STOP
- Throttle: пауза 5-10 сек (random) перед следующим
Шаг 5: LOG
Atomic write в sent.json:
cp sent.json sent.json.bak
Запись в sent.json:
{
"id": "131799467",
"company": "Арт-Матита",
"title": "Маркетолог автоворонок",
"salary": 100000,
"salary_raw": "от 100 000 ₽ за месяц, на руки",
"status": "SENT",
"reason": null,
"date": "2026-04-03",
"message_preview": "Коллеги из Арт-Матита..."
}
Reason codes: SENT, SKIP_NO_TEXTAREA, SKIP_ALREADY_APPLIED, SKIP_DRY_RUN, SKIP_USER, FAILED_NO_CONFIRMATION, FAILED_CDP_ERROR, FAILED_CDP_TIMEOUT, FAILED_ANTI_BOT
Дневной лог ~/hh-outreach-data/log-YYYY-MM-DD.md:
| # | ID | Компания | Вакансия | ЗП | Статус | Reason |
Шаг 6: REPEAT
Вернуться к Шаг 2. Счётчик: X/N.
Лимит: максимум 30 за сессию.
Шаг 7: REPORT
HH Outreach: завершено
Отправлено: X
Пропущено: Y (Nx SKIP_NO_TEXTAREA, Nx SKIP_ALREADY_APPLIED, ...)
Ошибок: Z (Nx FAILED_NO_CONFIRMATION, ...)
Топ-3 по зарплате: ...
Всего за всё время: N (из ~200 лимита HH)