Skip to content
SlashClone
Made by@revopslab

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.

SkillRevOpsv1.0.0 (latest)Tested
Activity
New: be the first to try it
  1. 1{
  2. 2 "name": "lead-routing-rules",
  3. 3 "owner": "revopslab",
  4. 4 "version": "1.0.0",
  5. 5 "type": "skill",
  6. 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. 7 "category": "revops",
  8. 8 "license": "MIT",
  9. 9 "compat": [
  10. 10 "claude-code"
  11. 11 ],
  12. 12 "permissions": {
  13. 13 "shell": [
  14. 14 "node scripts/route.mjs"
  15. 15 ],
  16. 16 "files": {
  17. 17 "read": [
  18. 18 "./data/**"
  19. 19 ],
  20. 20 "write": [
  21. 21 "./data/**",
  22. 22 "./out/**"
  23. 23 ]
  24. 24 }
  25. 25 },
  26. 26 "tests": {
  27. 27 "cases": [
  28. 28 {
  29. 29 "name": "routes by account match, partner, size pools, nurture, and fallback",
  30. 30 "inputs": {
  31. 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. 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. 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. 34 },
  35. 35 "checks": [
  36. 36 {
  37. 37 "type": "command_exits_zero",
  38. 38 "command": "node scripts/route.mjs data/rules.json --check"
  39. 39 },
  40. 40 {
  41. 41 "type": "command_exits_zero",
  42. 42 "command": "node scripts/route.mjs data/rules.json data/leads.csv --accounts data/accounts.csv --out out"
  43. 43 },
  44. 44 {
  45. 45 "type": "file_contains",
  46. 46 "path": "out/routed.csv",
  47. 47 "text": "buyer@northwind.example,Northwind Traders,350,US,Webinar,Rep A,owner,existing-account"
  48. 48 },
  49. 49 {
  50. 50 "type": "file_not_contains",
  51. 51 "path": "out/routed.csv",
  52. 52 "text": "Rep G"
  53. 53 },
  54. 54 {
  55. 55 "type": "output_matches_schema",
  56. 56 "path": "out/routed-summary.json",
  57. 57 "schema": {
  58. 58 "type": "object",
  59. 59 "required": [
  60. 60 "by_rule",
  61. 61 "by_assignee",
  62. 62 "unused_rules"
  63. 63 ],
  64. 64 "properties": {
  65. 65 "leads": {
  66. 66 "const": 10
  67. 67 },
  68. 68 "fallback": {
  69. 69 "const": 1
  70. 70 },
  71. 71 "by_rule": {
  72. 72 "type": "object",
  73. 73 "properties": {
  74. 74 "existing-account": {
  75. 75 "const": 2
  76. 76 },
  77. 77 "enterprise": {
  78. 78 "const": 2
  79. 79 },
  80. 80 "mid-market": {
  81. 81 "const": 1
  82. 82 },
  83. 83 "smb": {
  84. 84 "const": 2
  85. 85 },
  86. 86 "partner-referral": {
  87. 87 "const": 1
  88. 88 },
  89. 89 "free-email-students": {
  90. 90 "const": 1
  91. 91 }
  92. 92 }
  93. 93 },
  94. 94 "tests": {
  95. 95 "type": "object",
  96. 96 "properties": {
  97. 97 "total": {
  98. 98 "const": 5
  99. 99 },
  100. 100 "passing": {
  101. 101 "const": 5
  102. 102 }
  103. 103 }
  104. 104 }
  105. 105 }
  106. 106 }
  107. 107 }
  108. 108 ]
  109. 109 },
  110. 110 {
  111. 111 "name": "round robin state carries over between runs",
  112. 112 "inputs": {
  113. 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. 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. 115 },
  116. 116 "checks": [
  117. 117 {
  118. 118 "type": "command_exits_zero",
  119. 119 "command": "node scripts/route.mjs data/rules.json data/leads.csv --state data/rr-state.json --out out"
  120. 120 },
  121. 121 {
  122. 122 "type": "command_exits_zero",
  123. 123 "command": "node scripts/route.mjs data/rules.json data/leads.csv --state data/rr-state.json --out out --name routed-2"
  124. 124 },
  125. 125 {
  126. 126 "type": "file_contains",
  127. 127 "path": "out/routed-2.csv",
  128. 128 "text": "buyer@northwind.example,Northwind Traders,350,US,Webinar,Rep D,pool,mid-market"
  129. 129 },
  130. 130 {
  131. 131 "type": "output_matches_schema",
  132. 132 "path": "data/rr-state.json",
  133. 133 "schema": {
  134. 134 "type": "object",
  135. 135 "properties": {
  136. 136 "enterprise": {
  137. 137 "const": 0
  138. 138 },
  139. 139 "mid-market": {
  140. 140 "const": 1
  141. 141 },
  142. 142 "smb": {
  143. 143 "const": 0
  144. 144 }
  145. 145 }
  146. 146 }
  147. 147 }
  148. 148 ]
  149. 149 }
  150. 150 ]
  151. 151 }