Skip to content
Agentmart
Submit a tool

assign-unowned-contacts

Assign an owner to marketing contacts that have no owner.

Skill by tomgranot

IndexedMIT★ 77
Write a review

Chalk Labs

Need help deploying assign-unowned-contacts?

Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.

Book a call

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/tomgranot-assign-unowned-contacts

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

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.

Use cases: marketing, sales

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Python package

Network hosts it talks to 2

  • api.hubapi.com · found in code
  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/assign-unowned-contacts

What's inside

Skill 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
Instructions
157 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts
  • Instructions call the network

Files (4)

  • SKILL.md
  • after.py · scripts/
  • before.py · scripts/
  • execute.py · scripts/

SKILL.md (first lines)

# 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

From TomGranot/hubspot-admin-skills, MIT.

More from TomGranot/hubspot-admin-skills

Signals

Stars
77
Forks
18
Open issues
0
Last commit (this folder)
Oct 12, 2026 (21 days ago)

From GitHub (fetched Oct 24, 2026).

Details

Version
1.1
License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from tomgranot

Done for you

Rather not set it up yourself?

Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.

See how we build AI agents →Read the case studies

Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.

launches shipped
50+
launches shipped
media placements
500+
media placements
founders on every account
2
founders on every account

Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block

Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.

Get assign-unowned-contacts running in your business

Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.

Goes to the Chalk Labs founders, not the newsletter. Privacy

Need help deploying assign-unowned-contacts?

CallGet help
Cite this page

assign-unowned-contacts skill. Agent Mart. https://agentmart.dev/skills/tomgranot-assign-unowned-contacts (last updated 2026-10-24)