Flag HubSpot deals with missing fields and Slack the rep using an agent skill
low complexityCost: Usage-based
Prerequisites
Prerequisites
- Claude Code or compatible agent
- Environment variables:
HUBSPOT_TOKEN,HUBSPOT_PORTAL_ID,SLACK_BOT_TOKEN - A JSON file or environment variable mapping HubSpot owner IDs to Slack user IDs
Step 1: Create the skill
Create .claude/skills/missing-deal-fields/SKILL.md:
---
name: missing-deal-fields
description: Find HubSpot deals missing close date or amount and DM the deal owner in Slack
disable-model-invocation: true
allowed-tools: Bash(python *)
---
Audit active deal data quality. Searches for deals missing required fields
(close date, amount) and sends each deal owner a Slack DM listing their
incomplete deals with links to fix them.
Run: `python $SKILL_DIR/scripts/audit.py`Step 2: Write the audit script
Create .claude/skills/missing-deal-fields/scripts/audit.py:
import os, json, requests
from slack_sdk import WebClient
HUBSPOT_TOKEN = os.environ["HUBSPOT_TOKEN"]
PORTAL_ID = os.environ.get("HUBSPOT_PORTAL_ID", "YOUR_PORTAL_ID")
HEADERS = {"Authorization": f"Bearer {HUBSPOT_TOKEN}", "Content-Type": "application/json"}
SEARCH_URL = "https://api.hubapi.com/crm/v3/objects/deals/search"
slack = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
# Load owner-to-Slack mapping from env or file
OWNER_MAP_PATH = os.path.join(os.path.dirname(__file__), "owner_map.json")
if os.path.exists(OWNER_MAP_PATH):
with open(OWNER_MAP_PATH) as f:
OWNER_TO_SLACK = json.load(f)
else:
OWNER_TO_SLACK = json.loads(os.environ.get("OWNER_TO_SLACK", "{}"))
REQUIRED_FIELDS = ["closedate", "amount"]
PROPERTIES = ["dealname", "amount", "closedate", "dealstage", "hubspot_owner_id"]
def search_missing(field_name):
resp = requests.post(SEARCH_URL, headers=HEADERS, json={
"filterGroups": [{"filters": [
{"propertyName": field_name, "operator": "NOT_HAS_PROPERTY"},
{"propertyName": "dealstage", "operator": "NOT_IN",
"values": ["closedwon", "closedlost"]},
]}],
"properties": PROPERTIES,
"limit": 100,
})
resp.raise_for_status()
return resp.json().get("results", [])
# Fetch and deduplicate
seen = set()
all_deals = []
for field in REQUIRED_FIELDS:
for deal in search_missing(field):
if deal["id"] not in seen:
seen.add(deal["id"])
props = deal["properties"]
missing = [f for f in REQUIRED_FIELDS if not props.get(f)]
all_deals.append({
"id": deal["id"],
"name": props["dealname"],
"owner_id": props.get("hubspot_owner_id") or "unassigned",
"missing": missing,
})
if not all_deals:
print("All active deals have complete fields.")
exit(0)
# Group by owner
by_owner = {}
for deal in all_deals:
by_owner.setdefault(deal["owner_id"], []).append(deal)
# Notify each owner
notified = 0
for owner_id, deals in by_owner.items():
slack_user = OWNER_TO_SLACK.get(owner_id)
if not slack_user:
print(f"No Slack mapping for owner {owner_id} ({len(deals)} deals)")
continue
lines = []
for d in deals:
link = f"https://app.hubspot.com/contacts/{PORTAL_ID}/deal/{d['id']}"
lines.append(f"- <{link}|{d['name']}> -- missing *{', '.join(d['missing'])}*")
slack.chat_postMessage(
channel=slack_user,
text=f"Missing deal fields: {len(deals)} deals",
blocks=[
{"type": "header", "text": {"type": "plain_text",
"text": f"Missing Deal Fields ({len(deals)} deals)"}},
{"type": "section", "text": {"type": "mrkdwn",
"text": "\n".join(lines)}},
{"type": "context", "elements": [{"type": "mrkdwn",
"text": "Please update these deals today so forecasting stays accurate."}]},
],
)
notified += 1
print(f"Notified {slack_user} about {len(deals)} deals")
print(f"Done. {len(all_deals)} deals flagged, {notified} owners notified.")Step 3: Add the owner mapping
Create .claude/skills/missing-deal-fields/scripts/owner_map.json:
{
"12345678": "U0XXXXXXXXX",
"87654321": "U0YYYYYYYYY"
}Replace the keys with your HubSpot owner IDs and the values with the corresponding Slack user IDs.
Step 4: Run
/missing-deal-fieldsWhen to use this
- Before pipeline review meetings to make sure every deal has the basics filled in
- As a weekly hygiene check when forecast accuracy drops
- After importing or migrating deals to catch incomplete records
- For managers who want to audit data quality on-demand without waiting for a scheduled job
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.