К основному содержанию
скилл CX Codex

assign-unowned-contacts — скилл для Claude Code

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

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add TomGranot/hubspot-admin-skills --skill assign-unowned-contacts -a claude-code
Открыть на GitHub

Что делает assign-unowned-contacts

Скилл закрывает дыру в маркетинговой базе HubSpot: контакты без владельца получают рассылки, но их ответы никто не видит, а в отчётах по владельцам они не появляются вовсе. Скилл считает такие контакты, показывает доступных владельцев и назначает одного из них.

Внутри три скрипта: before.py считает контакты без владельца и выводит список владельцев, execute.py пакетно проставляет HUBSPOT_TARGET_OWNER_ID из .env, after.py проверяет, что счётчик упал до нуля. Порог безопасности в execute.py — 50,000 контактов, высокий, потому что назначение владельца обратимо. Запускать нужно из каталога проекта, чтобы .env, data/ и reports/ нашлись на месте.

Для работы понадобятся ключ HubSpot (HUBSPOT_ACCESS_TOKEN в .env, подойдёт и старый токен приватного приложения) со скоупами на чтение и запись контактов и чтение владельцев, Python 3.10+ с uv. Массовое назначение — решение бизнеса, а не техники: без согласования стратегии с тимлидами запускать execute.py не стоит.

Какие задачи решает

  • найти маркетинговые контакты HubSpot без владельца
  • назначить одного владельца всей пачке контактов
  • распределить контакты по территориям или по кругу между менеджерами
  • проверить, что после массового назначения безвладельных контактов не осталось
  • починить отчёты, в которых контакты без владельца выпадают

Примеры запросов агенту

  • Посчитай маркетинговые контакты HubSpot без владельца и покажи доступных владельцев
  • Назначь все контакты без владельца на HUBSPOT_TARGET_OWNER_ID из .env
  • Проверь, что маркетинговых контактов без владельца больше не осталось

Как установить assign-unowned-contacts

1Через Skills CLI (Vercel)

npx skills add TomGranot/hubspot-admin-skills --skill assign-unowned-contacts -a claude-code

2Вручную: положить файлы

~/.claude/skills/assign-unowned-contacts/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/assign-unowned-contacts/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о assign-unowned-contacts

Как установить assign-unowned-contacts в Claude Code?

Выполните npx skills add TomGranot/hubspot-admin-skills --skill assign-unowned-contacts -a claude-code. Скилл окажется в ~/.claude/skills/assign-unowned-contacts/SKILL.md.

Что делает assign-unowned-contacts?

Ищет маркетинговые контакты HubSpot без владельца и назначает им владельца — через интерфейс или пакетным обновлением по API. После прогона проверяет, что счётчик таких контактов равен нулю.

Какие ключи и доступы нужны для работы скилла?

Ключ HubSpot в .env под именем HUBSPOT_ACCESS_TOKEN со скоупами на чтение и запись контактов и чтение владельцев, а также Python 3.10+ с uv. Перед массовым назначением нужно согласовать стратегию с тимлидами.

Исходный текст

SKILL.md · MIT
---
name: assign-unowned-contacts
description: "Assign an owner to marketing contacts that have no owner. Ensures every marketable contact has accountability for follow-up, proper lead routing, and accurate owner-based reporting."
license: MIT
metadata:
  author: tomgranot
  version: "1.1"
  category: data-enrichment
---

# Assign Unowned Marketing Contacts

Assign an owner to all marketing contacts that currently have no owner. Unowned contacts create gaps in reporting, prevent proper lead routing, and mean no one is accountable for follow-up on marketing-generated responses.

## Why This Matters

Marketing contacts without an owner are a blind spot. They receive campaigns but no one sees their responses. They appear in aggregate metrics but not in individual pipeline views. In owner-based dashboards and reports, they simply do not exist. For teams using round-robin or territory-based routing, unowned contacts bypass the entire system.

## Prerequisites

- Phase 1 hygiene and earlier Phase 3 enrichment processes completed
- A HubSpot service key (`HUBSPOT_ACCESS_TOKEN` in `.env`; an existing legacy private app token also works) with contact read/write and owners read scopes
- Python 3.10+ with [`uv`](https://github.com/astral-sh/uv)
- Access to Contacts with permission to bulk edit owner assignments
- **Approval from team leads before bulk assignment.** This is a business decision, not just a technical one. Get sign-off on the assignment strategy before proceeding.

## Scripts

Script paths are relative to this skill's directory. Run them from your project directory so `.env`, `data/`, and `reports/` resolve there: prefix each path with wherever this skill is installed (`skills/assign-unowned-contacts/` in a clone of the repo).

| Stage | Script | Run with |
|-------|--------|----------|
| Before | [`scripts/before.py`](./scripts/before.py) | `uv run scripts/before.py` |
| Execute | [`scripts/execute.py`](./scripts/execute.py) | `uv run scripts/execute.py` |
| After | [`scripts/after.py`](./scripts/after.py) | `uv run scripts/after.py` |

`before.py` counts unowned marketing contacts and lists available owners. `execute.py` batch-assigns them to `HUBSPOT_TARGET_OWNER_ID` (set in `.env`; safety threshold 50,000 — high because owner assignment is fully reversible). `after.py` verifies the count dropped to zero.

## Interview: Gather Requirements

Before executing, collect the following information from the user:

**Q1: Who should unowned contacts be assigned to?**
- Examples: "Assign all to our Integration User", "Distribute across the sales team", "Assign to the Marketing Team user", "Assign to specific reps by territory"
- Default: No default -- this is a business decision that requires team lead approval

**Q2: Should we use territory-based routing, round-robin, or a single catch-all owner?**
- Examples: Territory-based (assign by geography or industry), round-robin (distribute evenly across active reps), catch-all (assign all to one user)
- Default: Catch-all to a single integration/service user as a temporary measure, with plans to implement proper routing later

## Plan

1. Identify all marketing contacts with no owner (before state)
2. Decide on the assignment strategy (catch-all user vs. territory rules)
3. Execute the bulk assignment
4. Verify all marketing contacts have owners (after state)

## Before

### Create the Unowned Marketing Contacts List

1. Go to **Contacts > Lists > Create list**
2. Select **Active list**
3. Name: `CLEANUP: Unowned Marketing Contacts`
4. Add filters:
   - Marketing contact status > is any of > Marketing contact
   - AND Contact owner > is unknown
5. Save the list and note the count

### Script Approach

Run `uv run scripts/before.py`. The core query:

```python
resp = requests.post(f"{BASE}/crm/v3/objects/contacts/search", headers=HEADERS, json={
    "filterGroups": [{"filters": [
        {"propertyName": "hs_marketable_status", "operator": "EQ", "value": "true"},
        {"propertyName": "hubspot_owner_id", "operator": "NOT_HAS_PROPERTY"},
    ]}],
    "limit": 1,
})
print(f"Unowned marketing contacts: {resp.json()['total']}")
```

## Execute

### Assignment Strategy Decision

Choose one of these approaches (requires team lead approval):

**Option A: Catch-All User (Simplest)**
- Assign all unowned contacts to a single integration/service user
- Pro: Fast, ensures 100% coverage immediately
- Con: One "owner" accumulates a large number of contacts; not meaningful for routing
- Best when: You plan to implement proper routing later and just need coverage now

**Option B: Territory/Region Rules**
- Assign based on contact geography, industry, or company size
- Pro: More meaningful ownership, better for sales follow-up
- Con: Requires a defined routing matrix and more complex execution
- Best when: You have established sales territories

**Option C: Round-Robin**
- Distribute evenly across active sales reps
- Pro: Fair distribution, immediate accountability
- Con: May assign contacts to reps who do not cover that segment
- Best when: Small team, all reps handle all segments

### Bulk Assignment via UI

1. Open the unowned marketing contacts list
2. Click the checkbox in the table header to select all contacts on the page
3. Click **Select all X contacts** to select across all pages
4. Click **Edit** in the toolbar
5. In the property dropdown, select **Contact owner**
6. Search for and select the chosen owner
7. Click **Update**
8. Confirm the bulk edit
9. For large numbers (5,000+), HubSpot processes in batches. This may take several minutes.

### Bulk Assignment via API

For the catch-all strategy, this is fully scripted: set `HUBSPOT_TARGET_OWNER_ID` in `.env` (run `before.py` to see available owner IDs) and run `uv run scripts/execute.py`.

```python
# What execute.py does:
# 1. POST /crm/v3/objects/contacts/search — unowned marketing contacts (paginated)
# 2. Build batch payload: {"inputs": [{"id": ..., "properties": {"hubspot_owner_id": OWNER_ID}}]}
# 3. POST /crm/v3/objects/contacts/batch/update in batches of 100

# For territory-based routing (extend the script):
# 1. Search for unowned marketing contacts with country/state/industry properties
# 2. Map each contact to an owner via your territory matrix
# 3. Batch update with the appropriate owner per contact
```

**API notes:**
- Get owner IDs from the Owners API: `GET /crm/v3/owners?limit=100`
- To find a specific owner by email: iterate through owners and match on `email`
- Batch update accepts up to 100 records per call
- Rate limit: 100 requests per 10 seconds per app on Free/Starter, 190 on Professional/Enterprise. The Search API has its own cap of 5 requests per second.

## After

Wait 5-10 minutes for HubSpot to finish processing, then verify: `uv run scripts/after.py` (re-runs the before-state query and compares against the baseline; the count should be 0).

**Verification checklist:**

1. The unowned marketing contacts list shows 0 contacts
2. Re-run the before-state script — count should be 0
3. Spot-check 5-10 contacts that were previously unowned — confirm they show the assigned owner
4. Check owner-based dashboards/reports to confirm the previously invisible contacts now appear

## Rollback

- Owner assignment is fully reversible: the execute script's CSV audit trail records every contact it assigned. To undo, batch-update those contact IDs with the previous owner (empty for "no owner").
- Individual assignments can also be reverted from each contact's property history.

## Key Technical Learnings

- **This is a business decision, not just a technical one.** Always get approval from sales/marketing leadership on the assignment strategy before executing. Bulk-assigning contacts to the wrong people creates confusion and erodes trust.
- **A catch-all user is a temporary solution.** If you assign to a single integration user, plan a follow-up process to redistribute contacts to actual sales reps when proper routing is established.
- **Pair with lead owner cleanup.** Once proper routing is in place, revisit contacts under the catch-all user and reassign them to real owners.
- **HubSpot bulk edit limits.** For very large batches (10,000+), the UI bulk edit may time out. Use the API approach instead, which handles pagination and batching gracefully.
- **New contacts need routing too.** After this one-time cleanup, implement a workflow or lead rotation rule to automatically assign owners to new marketing contacts going forward.

© tomgranot · MIT · TomGranot/hubspot-admin-skills

Похожие скиллы

Всё по теме «Маркетинг и контент» →
seo-audit coreyhaines31 Скилл проводит технический и on-page аудит сайта: краулинг, индексация, Core Web Vitals, метатеги, hreflang. Пригодится, когда трафик упал или страницы застряли на второй выдаче. CX Codex +1 238 500установок programmatic-seo coreyhaines31 Скилл помогает планировать генерацию сотен посадочных страниц на базе шаблонов и структурированных данных. Позволяет избежать санкций поисковиков за пустой контент. CX Codex +1 150 800установок copy-editing coreyhaines31 Правит уже написанный маркетинговый текст: убирает речевые штампы, чистит формулировки и обновляет устаревшие страницы. Для нового текста с нуля не подходит. CX Codex +1 149 800установок marketing-ideas coreyhaines31 Скилл помогает подобрать маркетинговые идеи для SaaS или другого программного продукта с учётом этапа развития и доступных ресурсов. Пригодится, когда непонятно, с какого канала начать. CX Codex +1 147 400установок sales-enablement coreyhaines31 Скилл для ИИ-агента, который собирает презентации, one-pager'ы, скрипты демо и документы для отработки возражений. Пригодится, когда команда продаж просит материалы, а не очередную презентацию о продукте. CX Codex +1 119 400установок revops coreyhaines31 Скилл помогает проектировать RevOps: вести лидов по этапам, считать скоринг и выстраивать передачу из маркетинга в продажи. Нужен для CRM-процессов. CX Codex +1 114 000установок

Из того же набора11

review-bounced-contacts Еженедельный ручной разбор контактов с 3+ отскоками: удалить или попробовать восстановить. Помогает не переусердствовать с подавлением и убрать мусор. CX Codex +1 ★ 78звёзд репо new-contact-hygiene-workflow Создаёт в HubSpot воркфлоу, который при появлении контакта ставит стадию жизненного цикла и подтягивает компанию с отраслью. CX Codex +1 ★ 78звёзд репо enrich-company-name Копирует название компании из связанной записи в пустое поле контакта HubSpot. Пригодится, если сегментация и токены персонализации ломаются из-за пустых значений. CX Codex +1 ★ 78звёзд репо create-segment-lists Собирает в HubSpot библиотеку списков сегментов: клиенты, партнёры, конкуренты, сотрудники, уровни ICP и отрасли. Нужна для таргетинга, подавления рассылок и отчётности. CX Codex +1 ★ 78звёзд репо cleanup-workflows Находит в HubSpot воркфлоу, которые никогда не запускались, отключены больше 90 дней или остались от тестов, и помогает их удалить. CX Codex +1 ★ 78звёзд репо cleanup-lead-owners Убирает из HubSpot уволенных сотрудников и переназначает их контакты, компании и сделки. Нужен, когда человек уходит, а его записи остаются висеть. CX Codex +1 ★ 78звёзд репо cleanup-dashboards Наводит порядок в дашбордах HubSpot: находит заброшенные, дубли и тестовые, помогает свести отчёты к нескольким рабочим экранам. CX Codex +1 ★ 78звёзд репо bounce-monitoring-workflow Скилл собирает в HubSpot три воркфлоу для контроля отскоков: подавляет контакты после нескольких bounce, шлёт алерты и помечает адреса на ручную проверку. CX Codex +1 ★ 78звёзд репо

Спонсоры проекта

Компании, которые помогают держать аналитику открытой