Dock
Sign in & remix
REMIX PREVIEWTemplate

Inbound lead qualification and routing

Polls your CRM every 15 minutes for new inbound leads, scores each against your ICP, routes by territory rule or round-robin, and pings the right rep in Slack before the lead goes cold.

· 13 min read· from trydock.ai

Agent: read this first

You're working in the Inbound lead qualification and routing workspace at trydock.ai/<org>/inbound-lead-qualification-routing.

## Your role

Polls CRM every 15 minutes for status=NEW leads, scores each against ICP via Claude, routes by territory rule (enterprise / SMB / round-robin), flips CRM status to IN_PROGRESS, pings the assigned rep in Slack on Hot + Warm, writes one Leads row. Never edits Hot/Warm/Cold tiers after the rep takes action.

## Cadence, when to update Dock

- After EVERY tool call that changes state (row create / update, doc append), append an entry to the **Status** surface.
- After every milestone, update the canonical doc surface for that work.
- When you start a session, READ the last 5 Status entries first to know what happened since you last worked.

## User-loop protocol

- End EVERY reply to the user with: "Check Dock for the latest at trydock.ai/<org>/inbound-lead-qualification-routing."
- If you make a decision the user might want to override (priority, scope cut, tone change), append to **Status** AND mention it in your reply: "Decided X. If you'd rather Y, edit [surface] and I'll re-plan."
- If you're blocked, post to Status with prefix `BLOCKED:` and ask one specific question.

## Don't touch

- Canonical phase / column / surface titles.
- Anything in a section titled "Locked" or "Decisions sealed."
- The agentPrompt itself (this section).

## First tool calls

1. `list_surfaces(workspace_slug="inbound-lead-qualification-routing")`
2. `get_doc(workspace_slug="inbound-lead-qualification-routing", surface_slug="status")`
3. `list_rows(workspace_slug="inbound-lead-qualification-routing", surface_slug="leads")`

---

# Inbound lead qualification and routing

A rolling 15-minute poll. Every new inbound lead scored + routed + alerted before it goes cold. Open in Dock and you get four surfaces seeded:

- **Leads** (table) one row per processed lead: Company, Contact, Email, Title, Company Size, Score (1-10), Tier (Hot / Warm / Cold), Assigned To, Lead Source, Scoring Notes, CRM ID, Routed At.
- **Setup guide** (doc) how the agent connects to the CRM + Slack + the Python recipe + ICP scoring rubric + routing rules.
- **Routing log** (doc) running narrative log of every routing decision. Useful for sales managers reviewing rep coverage + ICP fit over time.
- **Status** (doc) the agent's working session log. End of every run: 1 paragraph of leads scored + routed.

## Bring your own agent

Connect one agent to this workspace (Claude in Cursor, Claude Code, Codex, any MCP client). The agent runs every 15 minutes against the CRM, no manual trigger needed once the cron is wired.

## The user-loop protocol

Your agent proposes; you decide.

- Every 15 minutes: agent queries CRM for all contacts with status=NEW (no time window; processed_leads.json handles de-dup so a backlog catches up after downtime).
- For each unprocessed lead: score 1-10 via Claude against ICP_CRITERIA. Tier = Hot (8-10), Warm (5-7), Cold (1-4).
- Route by territory rule: Enterprise (employees >= 500) to the enterprise rep; SMB (employees <= 200) to the SMB rep; everything else to round-robin among the mid-market reps.
- Slack ping on Hot + near-term Warm. Cold leads write to Dock + Routing log only (no Slack noise).
- Update CRM status to IN_PROGRESS. Mark processed in processed_leads.json.
- Agent NEVER reverses a tier after the rep takes action. Agent NEVER edits the contact identity in the CRM.
- End of every run, agent writes 1 paragraph to **Status**: leads scored, Hot count, Warm count, Cold count, anything errored.

## First run

1. Confirm with the operator: CRM, ICP description (target size + industry + buyer title), routing rules (which rep handles which segment), Slack channel for alerts.
2. Open Setup guide and follow the install steps. Configure ICP_CRITERIA + ROUTING_RULES + ROUND_ROBIN_REPS in the script.
3. Create a test contact in the CRM with status=NEW (your own name, a company that clearly fits the ICP). Run python qualify_leads.py. Confirm Slack ping fires + Leads row appears + Routing log gets a section + CRM status flips.
4. Schedule via cron or CueAPI for every 15 minutes.

## Status

### [00:00 UTC] seeded by Dock
Workspace created from the inbound lead qualification and routing template. Agent: read the Setup guide, then confirm CRM + ICP + routing rules + Slack channel with the operator before the first run.
Next: confirm CRM + ICP + routing.

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.

Estimated time: 45 min setup, ongoing zero touch
Difficulty: beginner
For: VP Sales / Head of Revenue / sales ops at $5M-$50M revenue companies handling 20-200 inbound leads per month.

What you'll need

Pre-register or install before you start.

  • Dock (Free plan covers this template; Pro $19/mo unlocks more workspaces.) — The workspace itself, the Leads table, the Routing log doc surface for manager review.
  • Anthropic API (Pay per token, ~$0.01-$0.03 per lead. At 100 leads/month, ~$2/month.) — Claude scores each lead 1-10 against the ICP rubric with reasoning + strongest signal + biggest concern.
  • HubSpot / Salesforce / Pipedrive API (Free with existing CRM seat.) — Read NEW leads, write status=IN_PROGRESS after scoring. Script ships with HubSpot defaults.
  • Slack incoming webhook (Free) — Rep alerts on Hot + Warm leads. Block Kit message with tier emoji, score, strongest signal, biggest concern.
  • CueAPI (recommended) (Free tier covers per-15-min cadence.) — Cloud-scheduled 15-min cron so the poll keeps running when your laptop is closed. Replaces local cron for reliability.

The template · 5 steps

Step 1: Write ICP_CRITERIA + ROUTING_RULES with the operator

Estimated time: 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'.

[!CAUTION] 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.

Step 2: Wire .env + the Python script

Estimated time: 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).

[!CAUTION] 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.

Step 3: Test with one real lead

Estimated time: 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).

[!CAUTION] 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.

Step 4: Schedule the 15-minute poll

Estimated time: 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.

[!CAUTION] 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.

Step 5: Tune the routing + ICP after week one

Estimated time: 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.

[!CAUTION] 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.

Hand the template to your agent

Paste the prompt below into your agent's permanent system prompt so the agent reads, writes, and maintains this workspace as you work through the steps.

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")

FAQ

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.

Remix this into Dock

Make this yours. Edit, extend, run agents on it.

Sign in (free, 20 workspaces) — Dock mints a copy of this in your own workspace. The original stays untouched.

No Dock account? Sign-in is signup. Magic-link in 30 seconds.