Free for 30 days on Scale.Start free
Inbound lead qualification and routing
Every step in the template

Inbound lead qualification and routing

A workspace where every inbound lead is scored against your ICP, routed to the right rep in under 15 minutes, and surfaced as a Leads row + Slack ping + Routing log section. Nothing falls through the cracks.

Outcome

A workspace where every inbound lead is scored against your ICP, routed to the right rep in under 15 minutes, and surfaced as a Leads row + Slack ping + Routing log section. Nothing falls through the cracks.

Time45 min setup, ongoing zero touchDifficultybeginnerForVP Sales / Head of Revenue / sales ops at $5M-$50M revenue companies handling 20-200 inbound leads per month.
How this works

Open it, hand it to your agent, walk the steps.

Paste this to your agent (Claude / Cursor / Codex)
You are the agent running on the "Inbound lead qualification and routing" template workspace, connected via MCP at your-org/inbound-lead-qualification-routing.

Your job: every 15 minutes, poll the CRM for new leads, score against ICP, route to a rep, flip CRM status, write to Dock. Never reverse a tier after the rep takes action.

User-loop protocol:
- You propose. The rep decides. Never edit a tier after the rep takes action. Never edit the contact identity in the CRM.
- Every 15 minutes (or "run lead routing"): load processed_leads.json. Query CRM for all contacts where hs_lead_status=NEW. For each contact whose CRM ID is not in processed_leads.json:
  - Score 1-10 via Claude against ICP_CRITERIA. Tier = Hot (8-10), Warm (5-7), Cold (1-4).
  - Route by territory: employees >= 500 -> enterprise rep; employees <= 200 -> SMB rep; otherwise -> round-robin among ROUND_ROBIN_REPS.
  - PATCH the CRM contact: hs_lead_status=IN_PROGRESS (scoring notes live in Dock, not in CRM fields).
  - Append CRM ID to processed_leads.json.
  - On Hot + near-term Warm: POST a Slack message with tier emoji, name + company, score, strongest signal, biggest concern, assigned rep.
  - Write one Leads row with Score + Tier + Assigned To + Scoring Notes + Lead Source + CRM ID + Routed At.
- Round-robin: load round_robin_state.json (single integer index). Increment after each round-robin assignment. Wraps at len(ROUND_ROBIN_REPS).
- End of every run, write 1 paragraph to Status: leads scored, Hot count, Warm count, Cold count, anything errored.

Don't touch:
- processed_leads.json from prior runs (only append).
- The contact identity (firstname, lastname, email, company) in the CRM.
- A Leads row's Tier or Assigned To once the row has been written.

First MCP tool calls:
1. list_surfaces(workspace_slug="inbound-lead-qualification-routing")
2. list_rows(workspace_slug="inbound-lead-qualification-routing", surface_slug="leads")
3. get_doc(workspace_slug="inbound-lead-qualification-routing", surface_slug="status")
The template · 5 steps

Top to bottom. Each step has tasks, pointers, gotchas.

Write ICP_CRITERIA + ROUTING_RULES with the operator

20 min

Two pieces of operator input drive everything. ICP_CRITERIA tells Claude what a good lead looks like (target size + industry + buyer title). ROUTING_RULES tells the script which rep gets which segment. The more specific the inputs, the better the routing.

Tasks
  • Ask the operator to describe their ICP in plain language. Target company size, industries (good + bad), buyer title (good + bad), pain point.
  • Translate the description into ICP_CRITERIA as a multi-line string in the script. Concrete ranges beat vague descriptions (100-1000 employees beats 'mid-market').
  • Get the list of routing reps. Names, Slack user IDs (Uxxxxx, not display names), and any territory rules ('Sarah handles enterprise 500+', 'Tom handles SMB under 200', 'round-robin Sarah/Tom/Jordan for mid-market').
  • Confirm the routing rules back with a worked example: 'A 350-person SaaS company with VP of Sales as the buyer scores Warm and routes via round-robin to whoever is next'.
Gotchas
  • Generic ICP_CRITERIA produces generic scores. 'We sell software to mid-market' scores most leads the same. Be specific: industries, size brackets, buyer titles you've won before.
  • Slack user IDs not display names. Display names break when someone changes theirs.

Wire .env + the Python script

20 min

One script: qualify_leads.py runs every 15 minutes. Reads NEW leads from the CRM, scores via Claude, routes, flips CRM status, writes Dock, fires Slack. Idempotent via processed_leads.json + round_robin_state.json.

Tasks
  • Open Setup guide (doc) and copy qualify_leads.py into a local folder
  • Run pip install anthropic requests python-dotenv
  • Create .env with DOCK_API_KEY, DOCK_WORKSPACE_SLUG, ANTHROPIC_API_KEY, HUBSPOT_ACCESS_TOKEN, SLACK_WEBHOOK_URL, CLAUDE_MODEL=claude-sonnet-4-6
  • Generate a Dock API key at trydock.ai/settings/api
  • Create a Slack incoming webhook for the sales channel, paste into SLACK_WEBHOOK_URL
  • Set up the Leads table column schema explicitly before the first run (see Setup guide doc for the full column list).
Gotchas
  • Lead status field name. HubSpot defaults to hs_lead_status; Salesforce uses Status; Pipedrive varies. The Setup guide doc shows how to verify via the CRM's API before going live.
  • Employee count field. HubSpot has numemployees; Salesforce has NumberOfEmployees; Pipedrive uses a custom field. Confirm before going live or the routing rules silently misfire.

Test with one real lead

15 min

Before scheduling, run end-to-end with a real lead. Create a test contact in the CRM with status=NEW (your own name, a company that clearly fits the ICP). Run the script manually. Confirm every surface fires.

Tasks
  • Create a test contact in the CRM. Your name + a company that obviously fits the ICP (so the score should be 8+). Set hs_lead_status=NEW.
  • Run python qualify_leads.py
  • Confirm: Slack ping arrived with the right rep + score + tier emoji. Leads row in Dock with all fields. Routing log got a new section. CRM contact's status flipped to IN_PROGRESS.
  • Repeat with a clearly cold lead (wrong industry + wrong size). Confirm: Cold tier, Leads row, Routing log section, but NO Slack noise.
  • If scores feel off: tighten ICP_CRITERIA, re-run on the same test contact (delete its CRM ID from processed_leads.json first).
Gotchas
  • Don't delete processed_leads.json wholesale during testing; just remove the specific test contact ID. Wholesale delete = re-score every lead in the CRM = API quota burn.
  • Slack alert silence on Cold is intentional. Cold leads get logged to Dock + Routing log; managers review in batch. Reps don't get pinged for things they shouldn't waste time on.
Agent prompt for this step
Run a first lead-routing cycle. Load processed_leads.json. Query CRM for all contacts with hs_lead_status=NEW. For each contact whose CRM ID is not in processed_leads.json: score_lead() via Claude, route_lead() by territory + round-robin, update_crm_contact() (flip to IN_PROGRESS), send_slack_alert() on Hot + near-term Warm, write_to_dock() with all fields. Append to processed_leads.json. Post a Status entry summarizing: scored, Hot, Warm, Cold, errored.

Schedule the 15-minute poll

10 min

Once test runs are clean, schedule every 15 minutes. The script is idempotent: re-runs no-op on already-processed leads. After downtime, the next run catches up automatically (no time window on the query).

Tasks
  • Option A, cron: crontab -e, add `*/15 * * * * cd /path && source .env && python3 qualify_leads.py >> qualify_leads.log 2>&1`
  • Option B, CueAPI: cueapi create --schedule '*/15 * * * *' --timezone 'America/New_York' --name 'inbound-lead-qualification' --handler ./qualify_leads.py
  • Confirm 30 minutes later: Status has at least one fresh session entry; if any NEW leads existed, they got scored and flipped to IN_PROGRESS.
Gotchas
  • Closed laptop + cron = no poll. Real inbound traffic happens 9-5 most days, so closed laptop overnight is fine. For 24/7 coverage switch to CueAPI.
  • If reps complain about latency, drop the cron to */5 minutes. Cost stays under $5/month even at 5x cadence.

Tune the routing + ICP after week one

15 min after week 1, then quarterly

Sales-ops feedback loop. After the first week, walk through Routing log with the sales manager. Did any leads go to the wrong rep? Did any obvious-fit leads score Cold? Adjust ICP_CRITERIA + ROUTING_RULES, then run with the updated config. The script picks up the new rules on the next cron tick.

Tasks
  • Open Routing log (doc). Walk through the week with the sales manager.
  • Mis-routings: did the territory rule fire the wrong way? Add an industry rule, a state rule, or a custom property to route_lead().
  • Mis-scorings: did high-fit leads score Cold? ICP_CRITERIA is too tight. Did low-fit leads score Hot? Too loose. Tighten/loosen the bracket descriptions.
  • Commit the updated config. Next cron tick picks it up.
Gotchas
  • Don't tune from a single lead. Look at 10-20 routings before changing rules. Single-lead tunes oscillate.
  • Round-robin fairness. If one rep complains they get more leads, check round_robin_state.json + the territory rules. Mid-market is often most of the volume + rotates evenly; enterprise + SMB are stickier.
FAQ

Common questions on this template.

How does this handle the case where I have multiple lead sources (forms, demo booking, inbound email)?
The agent treats them uniformly. As long as the lead enters the CRM with hs_lead_status=NEW, the 15-min poll picks it up. Lead Source from the CRM (hs_analytics_source on HubSpot) shows up on the Leads row + Slack alert so the rep knows the channel.
What if my CRM uses a different status name?
Edit fetch_new_leads() in the script. The default queries hs_lead_status=NEW; Salesforce uses Status='Open - Not Contacted'; Pipedrive uses a custom field. The Setup guide doc has the verification query for each CRM.
Can I add more routing rules beyond company size?
Yes. Edit route_lead(). The default routes by territory size (enterprise / SMB / round-robin). Common extensions: route by state, industry, or a custom CRM field. Read the contact's full properties + branch the logic.
What if a lead gets routed wrong?
Manually reassign in the CRM + edit the Leads row's Assigned To. The agent never reverses a routing decision; that's a rep-controlled action. The Status entry will note the manual override if you log it.
Does the agent edit the lead's identity (name, email, company)?
No. The agent only reads identity fields from the CRM. The only field it writes back is hs_lead_status (flipped to IN_PROGRESS so the same lead doesn't fire twice). Everything else lives in Dock.

Open this template as a workspace.

We mint a fresh copy in your org with the steps as table rows, the pointers as a separate table, and the brief as a doc. Bring your agents, start checking off boxes.