Lead routing rules
@revopslab/lead-routing-rules
Design, test, and apply lead routing rules outside your CRM. Write ordered rules in a JSON file (existing-account match, partner, segment, region, junk filters), prove them with rule tests, then route a leads CSV with weighted round robin, out-of-office skips, and a fallback queue. Works with HubSpot, Salesforce, or any CSV.
- Activity
- New: be the first to try it
- Installs
- 0 installs
- Likes
- 0 likes
- Clones
- 0 clones
- 1{
- 2 "name": "lead-routing-rules",
- 3 "owner": "revopslab",
- 4 "version": "1.0.0",
- 5 "type": "skill",
- 6 "description": "Design, test, and apply lead routing rules outside your CRM. Write ordered rules in a JSON file (existing-account match, partner, segment, region, junk filters), prove them with rule tests, then route a leads CSV with weighted round robin, out-of-office skips, and a fallback queue. Works with HubSpot, Salesforce, or any CSV.",
- 7 "category": "revops",
- 8 "license": "MIT",
- 9 "compat": [
- 10 "claude-code"
- 11 ],
- 12 "permissions": {
- 13 "shell": [
- 14 "node scripts/route.mjs"
- 15 ],
- 16 "files": {
- 17 "read": [
- 18 "./data/**"
- 19 ],
- 20 "write": [
- 21 "./data/**",
- 22 "./out/**"
- 23 ]
- 24 }
- 25 },
- 26 "tests": {
- 27 "cases": [
- 28 {
- 29 "name": "routes by account match, partner, size pools, nurture, and fallback",
- 30 "inputs": {
- 31 "data/rules.json": "{\n \"pools\": {\n \"enterprise\": { \"members\": [\"Rep A\", \"Rep B\"] },\n \"mid-market\": { \"members\": [\"Rep C\", \"Rep D\"], \"weights\": { \"Rep C\": 2 } },\n \"smb\": { \"members\": [\"Rep E\", \"Rep F\", \"Rep G\"] }\n },\n \"out_of_office\": [\"Rep G\"],\n \"account_match\": { \"rule_id\": \"existing-account\" },\n \"rules\": [\n { \"id\": \"spam-or-test\", \"when_any\": [{ \"field\": \"email\", \"op\": \"contains\", \"value\": [\"test@\", \"example.com\"] }, { \"field\": \"company\", \"op\": \"in\", \"value\": [\"test\", \"asdf\", \"n/a\"] }], \"assign\": { \"queue\": \"discard-review\" }, \"reason\": \"Looks like a test or junk submission\" },\n { \"id\": \"free-email-students\", \"when\": [{ \"field\": \"is_free_email\", \"op\": \"exists\" }, { \"field\": \"employees\", \"op\": \"missing\" }], \"assign\": { \"queue\": \"nurture\" }, \"reason\": \"Personal email and no company size\" },\n { \"id\": \"partner-referral\", \"when\": [{ \"field\": \"lead_source\", \"op\": \"==\", \"value\": \"Partner referral\" }], \"assign\": { \"owner\": \"Partner Manager\" }, \"reason\": \"Partner-sourced leads go to the partner manager\" },\n { \"id\": \"enterprise\", \"when\": [{ \"field\": \"employees\", \"op\": \">=\", \"value\": 1000 }], \"assign\": { \"pool\": \"enterprise\" }, \"reason\": \"1,000+ employees\" },\n { \"id\": \"mid-market\", \"when\": [{ \"field\": \"employees\", \"op\": \">=\", \"value\": 200 }], \"assign\": { \"pool\": \"mid-market\" }, \"reason\": \"200 to 999 employees\" },\n { \"id\": \"smb\", \"when\": [{ \"field\": \"employees\", \"op\": \">=\", \"value\": 1 }], \"assign\": { \"pool\": \"smb\" }, \"reason\": \"Under 200 employees\" }\n ],\n \"fallback\": { \"queue\": \"routing-review\" },\n \"tests\": [\n { \"name\": \"big company goes to enterprise\", \"lead\": { \"email\": \"ops@bigco.test.org\", \"company\": \"BigCo\", \"employees\": \"5,000\" }, \"expect_rule\": \"enterprise\", \"expect_to\": \"enterprise\" },\n { \"name\": \"employee range uses the low end\", \"lead\": { \"email\": \"a@mid.io\", \"company\": \"Mid\", \"employees\": \"201-500\" }, \"expect_rule\": \"mid-market\" },\n { \"name\": \"gmail with no size is nurture\", \"lead\": { \"email\": \"someone@gmail.com\", \"company\": \"Self\" }, \"expect_rule\": \"free-email-students\", \"expect_to\": \"nurture\" },\n { \"name\": \"partner beats size\", \"lead\": { \"email\": \"x@huge.io\", \"employees\": \"20000\", \"lead_source\": \"Partner referral\" }, \"expect_rule\": \"partner-referral\" },\n { \"name\": \"no size and work email falls back\", \"lead\": { \"email\": \"x@unknown.io\", \"company\": \"Unknown\" }, \"expect_rule\": \"fallback\", \"expect_to\": \"routing-review\" }\n ]\n}\n",
- 32 "data/leads.csv": "First Name,Last Name,Email,Company,Employees,Country,Lead Source\nLead,One,buyer@northwind.example,Northwind Traders,350,US,Webinar\nLead,Two,cto@fabrikam.example,Fabrikam Health,4200,US,Demo request\nLead,Three,it@contoso.example,Contoso Robotics,1200,DE,Demo request\nLead,Four,someone@gmail.com,Self,,US,Ebook\nLead,Five,ops@tailspin.example,Tailspin Toys,45,US,Free trial\nLead,Six,owner@litware.example,Litware,12,CA,Free trial\nLead,Seven,head@adventure.example,Adventure Works,600,US,Partner referral\nLead,Eight,vp@wideworld.example,Wide World Importers,250,GB,Demo request\nLead,Nine,info@proseware.example,Proseware,30,US,Free trial\nLead,Ten,new@unknownco.example,UnknownCo,,US,Contact us\n",
- 33 "data/accounts.csv": "Company Name,Company Domain Name,Company Owner\nNorthwind Traders,northwind.example,Rep A\nLitware,https://www.litware.example/,Rep F\n"
- 34 },
- 35 "checks": [
- 36 {
- 37 "type": "command_exits_zero",
- 38 "command": "node scripts/route.mjs data/rules.json --check"
- 39 },
- 40 {
- 41 "type": "command_exits_zero",
- 42 "command": "node scripts/route.mjs data/rules.json data/leads.csv --accounts data/accounts.csv --out out"
- 43 },
- 44 {
- 45 "type": "file_contains",
- 46 "path": "out/routed.csv",
- 47 "text": "buyer@northwind.example,Northwind Traders,350,US,Webinar,Rep A,owner,existing-account"
- 48 },
- 49 {
- 50 "type": "file_not_contains",
- 51 "path": "out/routed.csv",
- 52 "text": "Rep G"
- 53 },
- 54 {
- 55 "type": "output_matches_schema",
- 56 "path": "out/routed-summary.json",
- 57 "schema": {
- 58 "type": "object",
- 59 "required": [
- 60 "by_rule",
- 61 "by_assignee",
- 62 "unused_rules"
- 63 ],
- 64 "properties": {
- 65 "leads": {
- 66 "const": 10
- 67 },
- 68 "fallback": {
- 69 "const": 1
- 70 },
- 71 "by_rule": {
- 72 "type": "object",
- 73 "properties": {
- 74 "existing-account": {
- 75 "const": 2
- 76 },
- 77 "enterprise": {
- 78 "const": 2
- 79 },
- 80 "mid-market": {
- 81 "const": 1
- 82 },
- 83 "smb": {
- 84 "const": 2
- 85 },
- 86 "partner-referral": {
- 87 "const": 1
- 88 },
- 89 "free-email-students": {
- 90 "const": 1
- 91 }
- 92 }
- 93 },
- 94 "tests": {
- 95 "type": "object",
- 96 "properties": {
- 97 "total": {
- 98 "const": 5
- 99 },
- 100 "passing": {
- 101 "const": 5
- 102 }
- 103 }
- 104 }
- 105 }
- 106 }
- 107 }
- 108 ]
- 109 },
- 110 {
- 111 "name": "round robin state carries over between runs",
- 112 "inputs": {
- 113 "data/rules.json": "{\n \"pools\": {\n \"enterprise\": { \"members\": [\"Rep A\", \"Rep B\"] },\n \"mid-market\": { \"members\": [\"Rep C\", \"Rep D\"], \"weights\": { \"Rep C\": 2 } },\n \"smb\": { \"members\": [\"Rep E\", \"Rep F\", \"Rep G\"] }\n },\n \"out_of_office\": [\"Rep G\"],\n \"account_match\": { \"rule_id\": \"existing-account\" },\n \"rules\": [\n { \"id\": \"spam-or-test\", \"when_any\": [{ \"field\": \"email\", \"op\": \"contains\", \"value\": [\"test@\", \"example.com\"] }, { \"field\": \"company\", \"op\": \"in\", \"value\": [\"test\", \"asdf\", \"n/a\"] }], \"assign\": { \"queue\": \"discard-review\" }, \"reason\": \"Looks like a test or junk submission\" },\n { \"id\": \"free-email-students\", \"when\": [{ \"field\": \"is_free_email\", \"op\": \"exists\" }, { \"field\": \"employees\", \"op\": \"missing\" }], \"assign\": { \"queue\": \"nurture\" }, \"reason\": \"Personal email and no company size\" },\n { \"id\": \"partner-referral\", \"when\": [{ \"field\": \"lead_source\", \"op\": \"==\", \"value\": \"Partner referral\" }], \"assign\": { \"owner\": \"Partner Manager\" }, \"reason\": \"Partner-sourced leads go to the partner manager\" },\n { \"id\": \"enterprise\", \"when\": [{ \"field\": \"employees\", \"op\": \">=\", \"value\": 1000 }], \"assign\": { \"pool\": \"enterprise\" }, \"reason\": \"1,000+ employees\" },\n { \"id\": \"mid-market\", \"when\": [{ \"field\": \"employees\", \"op\": \">=\", \"value\": 200 }], \"assign\": { \"pool\": \"mid-market\" }, \"reason\": \"200 to 999 employees\" },\n { \"id\": \"smb\", \"when\": [{ \"field\": \"employees\", \"op\": \">=\", \"value\": 1 }], \"assign\": { \"pool\": \"smb\" }, \"reason\": \"Under 200 employees\" }\n ],\n \"fallback\": { \"queue\": \"routing-review\" },\n \"tests\": [\n { \"name\": \"big company goes to enterprise\", \"lead\": { \"email\": \"ops@bigco.test.org\", \"company\": \"BigCo\", \"employees\": \"5,000\" }, \"expect_rule\": \"enterprise\", \"expect_to\": \"enterprise\" },\n { \"name\": \"employee range uses the low end\", \"lead\": { \"email\": \"a@mid.io\", \"company\": \"Mid\", \"employees\": \"201-500\" }, \"expect_rule\": \"mid-market\" },\n { \"name\": \"gmail with no size is nurture\", \"lead\": { \"email\": \"someone@gmail.com\", \"company\": \"Self\" }, \"expect_rule\": \"free-email-students\", \"expect_to\": \"nurture\" },\n { \"name\": \"partner beats size\", \"lead\": { \"email\": \"x@huge.io\", \"employees\": \"20000\", \"lead_source\": \"Partner referral\" }, \"expect_rule\": \"partner-referral\" },\n { \"name\": \"no size and work email falls back\", \"lead\": { \"email\": \"x@unknown.io\", \"company\": \"Unknown\" }, \"expect_rule\": \"fallback\", \"expect_to\": \"routing-review\" }\n ]\n}\n",
- 114 "data/leads.csv": "First Name,Last Name,Email,Company,Employees,Country,Lead Source\nLead,One,buyer@northwind.example,Northwind Traders,350,US,Webinar\nLead,Two,cto@fabrikam.example,Fabrikam Health,4200,US,Demo request\nLead,Three,it@contoso.example,Contoso Robotics,1200,DE,Demo request\nLead,Four,someone@gmail.com,Self,,US,Ebook\nLead,Five,ops@tailspin.example,Tailspin Toys,45,US,Free trial\nLead,Six,owner@litware.example,Litware,12,CA,Free trial\nLead,Seven,head@adventure.example,Adventure Works,600,US,Partner referral\nLead,Eight,vp@wideworld.example,Wide World Importers,250,GB,Demo request\nLead,Nine,info@proseware.example,Proseware,30,US,Free trial\nLead,Ten,new@unknownco.example,UnknownCo,,US,Contact us\n"
- 115 },
- 116 "checks": [
- 117 {
- 118 "type": "command_exits_zero",
- 119 "command": "node scripts/route.mjs data/rules.json data/leads.csv --state data/rr-state.json --out out"
- 120 },
- 121 {
- 122 "type": "command_exits_zero",
- 123 "command": "node scripts/route.mjs data/rules.json data/leads.csv --state data/rr-state.json --out out --name routed-2"
- 124 },
- 125 {
- 126 "type": "file_contains",
- 127 "path": "out/routed-2.csv",
- 128 "text": "buyer@northwind.example,Northwind Traders,350,US,Webinar,Rep D,pool,mid-market"
- 129 },
- 130 {
- 131 "type": "output_matches_schema",
- 132 "path": "data/rr-state.json",
- 133 "schema": {
- 134 "type": "object",
- 135 "properties": {
- 136 "enterprise": {
- 137 "const": 0
- 138 },
- 139 "mid-market": {
- 140 "const": 1
- 141 },
- 142 "smb": {
- 143 "const": 0
- 144 }
- 145 }
- 146 }
- 147 }
- 148 ]
- 149 }
- 150 ]
- 151 }
- 152}
