A B2B real-estate developer I worked with was pulling 200 to 300 inbound leads a day off Facebook Lead Ads and a messaging channel, split across three large projects, each with its own sales team of eight to twelve people. The business problem they handed me sounded trivial: "just send each lead to the right team automatically." It is never trivial. Within a week of go-live we discovered that a chunk of leads were landing nowhere, that the team for the busiest project was quietly starved, and that nobody could explain why one rep got a lead and another did not.
Almost every lead-routing engagement I take on follows that same arc. The first hour is spent agreeing on the routing criteria, and the next several days are spent fighting the parts of Salesforce that do not behave the way people assume they do. Lead assignment rules are powerful, but they are also full of one-shot behaviors and silent failure modes, and round-robin distribution is something Salesforce does not actually ship out of the box at all. This is the practical version of how I think about both.
The reason I am careful here is that lead routing is where revenue leaks. A lead that sits unassigned for a day is, for most sales orgs, a lost deal. So I treat routing as a reliability problem, not a configuration checkbox.
Assignment rules, queues, and what each is actually for
A lead assignment rule is a server-side mechanism that runs the moment a lead is created, or on edit if you ask it to. It evaluates rule entries in sort order, and the first entry whose conditions match sets the owner and stops. That owner can be a specific user, a queue, or a partner user. The constraint people forget is that an org can have exactly one active assignment rule at a time. You get many entries inside that one rule, but only one rule live, which has real deployment consequences I will come back to.
A queue is a different animal from a user owner. A queue is a shared bucket: every member sees its leads in their "My Queues" list views, and whoever grabs one first owns it. For the developer client, I routed each project's leads into a queue rather than directly to individuals, plus a catch-all queue for leads whose source data was malformed. The queue gave the team leads a place to do manual triage when the automated criteria could not decide, which in practice happens more than anyone admits during discovery.
The configuration itself is straightforward. You add a custom picklist to capture the routing dimension (in this case the project of interest, populated from campaign UTM data through the integration), build a queue per team with Lead added as a supported object, then build rule entries: narrow conditions first, broad conditions last, with a final catch-all entry routing leftovers into an unassigned pool. The ordering is not cosmetic. Because the first match wins and the rest are skipped, an entry that is too broad sitting near the top will swallow leads that a more specific lower entry was meant to catch.
The gotchas that actually cost you leads
The single most common failure I see is integration-pushed leads not running the rule at all. When a lead is created through the UI, the page layout's "Assign using active assignment rule" checkbox handles it. When a lead arrives through the API from a Facebook Lead Ads connector or any middleware, the rule does not fire unless the call sets the Sforce-Auto-Assign: TRUE header (or a Flow sets the equivalent assignment DML option). Skip it, and every inbound lead lands silently under the integration user, owned by nobody who sells. This is the bug behind most "our leads disappeared" tickets, and it is invisible until someone goes looking for why the queues are empty.
The second is the assumption that the rule re-runs when a lead changes. It does not. Assignment rules are one-shot on create, and on edit only when a user re-saves with the assignment checkbox ticked. When the client asked why changing a lead's project did not re-route it, the honest answer was that native rules were never going to do that. If you genuinely need re-routing on field change, that is a Record-Triggered Flow, not an assignment rule.
Two more bite reliably. Overlapping entry conditions mean a lead matching two entries only ever hits the first, so a team can be starved without anyone noticing the cause; you fix it with exclusion conditions or a single combined field. And email notifications to queues do not reach individual members by default. The entry's notification goes to the queue's email address, so unless you tick "Send Email to Members" in the queue setup, reps will swear they are not being told about new leads.
There is also a deployment hazard hiding in the one-active-rule constraint. The day you activate a new rule, the old one deactivates and routing logic changes instantly for everyone. I schedule those changes for early on a quiet morning and warn the team beforehand, because flipping routing logic mid-day on a sales floor that has muscle memory for the old behavior creates chaos that looks like a system bug.
When even distribution is the requirement: round-robin
A different client, a consumer-finance call center running roughly 30 relationship managers against around 800 leads a day, had a requirement assignment rules cannot satisfy on their own: spread leads evenly, respect skill match, and never pile leads on someone who is already full. Salesforce has no native round-robin for leads. You either build it or you buy it.
A round-robin engine is, at its core, a stored "last assigned index." Each new lead reads the index, takes the next eligible user in an ordered roster, assigns the lead, and advances the index, wrapping back to the start at the end of the list. For this client I modeled it with a configuration object holding the pool definition, region, skills, capacity threshold, and the last-assigned pointer, plus a child roster object listing each user and their sort order. A Record-Triggered Flow on lead insert looked up the right pool by region and product, walked the roster from the pointer, checked each user's open-lead count against the threshold, assigned the first one with room, and advanced the pointer.
I divide load-balancing requirements into three patterns and pick deliberately. Pure round-robin ignores capacity entirely; it is fair and dead simple, fine below about a hundred leads a day, but it skews badly the moment someone is out. Capacity-aware routing skips anyone who is full and suits the medium-volume call-center band, which is what this client needed. Weighted distribution gives top performers a larger share and is worth the extra complexity only when the client explicitly wants to maximize conversion over fairness. Most clients say "fair" in discovery and mean capacity-aware once you walk them through it.
Building round-robin so it does not quietly fail
The custom build is where the real engineering lives, and every pitfall here cost a real client real leads. Concurrency is the nastiest: two leads arriving in the same second both count a rep as having room and both get assigned, pushing that rep over the threshold. You need a record lock (Apex FOR UPDATE on the config record) or a serialized queue; a counter cached in a custom setting will drift under concurrent updates and lie to you.
Availability is the next one. When a rep takes leave, their name stays in the roster, their open-lead count reads low, and the engine cheerfully assigns them lead after lead that nobody is working. The fix is an Is_Available__c flag on the user that the Flow filters on, plus a clear owner for toggling it. Pin that ownership down before go-live, because "who marks someone unavailable" is exactly the operational gap that turns into dead leads.
Three more are non-negotiable in my designs. There must be an overflow queue for when every eligible rep is full, with a dashboard and an alert when leads sit there too long, or the engine will route overflow back to the integration user and the leads vanish; this client lost a dozen leads in a single peak day before we added it. The roster must filter on active users, or a departed rep still in the roster throws "owner cannot be inactive" DML errors, so I pair the active-user filter with a nightly roster cleanup. And every assignment should write a human-readable reason onto the lead, something like "Pool North, round 47, capacity OK," so that when a rep asks why a lead went to them and not a colleague, operations can actually answer.
On the build-versus-buy question, I lean toward an AppExchange router such as LeanData or Distribution Engine when the client has no in-house Salesforce developers, when routing logic changes a few times a quarter with campaigns, or when they need timezone, holiday-calendar, or manager-override features that are expensive to build well. I build custom when the logic is genuinely unusual, or when the client already runs an Apel framework and the routing has to bind tightly to other integrations, which is exactly why the finance client built rather than bought.
The principle I keep coming back to is to use the lightest tool that meets the requirement and no lighter. Native assignment rules for simple create-time routing by region or source, a Flow when you need re-routing or capacity logic, and a real distribution engine when even, monitored, fault-tolerant assignment is the actual business requirement. The failures in this discipline are rarely loud. They are leads that quietly never reach a human, and the whole job is making sure that never happens.
Implementing or optimizing Sales Cloud? Our Salesforce team runs discovery, designs the sales process, and configures Sales Cloud on production engagements. Get in touch ->
See our full platform services for the stack we cover.








