Code Agency
6 min read

Lead scoring in Odoo CRM: routing the hot ones before they cool

Every form submission is not equal. Scoring rules on source, behaviour and firmographics that push the right leads to the right salesperson while they're still warm.

A lead that fills in "book a demo" and a lead that downloads a PDF from an ad are not the same lead. Most CRM setups treat them the same anyway — both land in the same pool, both wait for the same rep to work through the list top to bottom, and by the time anyone gets to the PDF download, the demo request has gone cold waiting behind it. The fix isn't hiring more SDRs. It's making the pipeline tell reps which leads to call first.

The signal is already in the record — it's just not scored

Every lead that reaches crm.lead carries more than a name and an e-mail. The website form writes utm_source_id, utm_medium_id and utm_campaign_id on creation, so "organic search" and "paid retargeting" are already distinguishable at the database level, not something you reconstruct later from a spreadsheet. email_state and phone_state tell you whether the contact details are even valid before a rep wastes a call. If the lead came through the website, website_id and the visitor's page trail are on the linked website.visitor record — someone who viewed pricing three times in a day is a different lead than someone who read one blog post.

None of that is scoring yet. It's just data sitting unused in fields nobody built a query for. Scoring is the layer that turns "this lead visited /pricing twice" into a number a rep can act on without reading the timeline.

Odoo's built-in engine: predictive lead scoring

Odoo CRM ships an actual scoring model, not just automation glue — Predictive Lead Scoring, under CRM → Configuration → Predictive Lead Scoring. It trains on your own closed pipeline: every lead that reached Won or Lost becomes a training example, and it builds a frequency table across the variables you select — country, language, source, medium, campaign, tags, stage, and any custom field you add to the list. The result lands in probability_automated, a system-computed win probability, kept separate from the probability field a rep can override by dragging the kanban card. You always see both; the automated number is a suggestion, not a lock on the record.

The catch is the cold-start problem. A frequency table needs closed history to mean anything — a pipeline with forty leads and six outcomes will produce a probability that swings wildly on noise, and we tell every client this upfront rather than let a false-confidence number drive routing in month one. Our rule of thumb: don't route on the automated score until there are at least a few hundred closed leads behind it, and recompute the frequency table after any material change to how leads are sourced. Until then, run the manual rules below and let them build the history the model needs.

Manual scoring rules for the leads that can't wait for history

Predictive scoring answers "how likely is this to close." It doesn't answer "should someone call in the next ten minutes" — for that you want a point system a rep can glance at and trust immediately. We build this as a computed field plus a small set of automation rules on crm.lead, not a Python module, for the same reason we default to server actions over custom code for logic this size: it stays visible to whoever configures CRM after we leave.

A typical rule set we start clients with:

  • Source weight: demo request or pricing-page form → +40. Content download or newsletter signup → +10.
  • Behavioural weight: 3+ page views on a website visitor linked to the lead in the last 24h → +15. No previous e-mail bounce → +5.
  • Firmographic weight: business e-mail domain (not gmail/outlook/etc., checked against a small denylist) → +10. Company size field filled from enrichment → +5 to +20 depending on tier.
server action — recompute lead score on write
score = 0
if lead.utm_source_id.name in ("demo-request", "pricing-page"):
    score += 40
elif lead.utm_source_id.name in ("content-download", "newsletter"):
    score += 10
if lead.email_state == "correct" and "@gmail." not in lead.email_from:
    score += 10
lead.x_lead_score = score

Trigger the action on lead creation and on any write to the fields the rule depends on — Odoo's automation rules let you scope that trigger precisely instead of firing on every save. Above a threshold (we usually start at 50), a second rule sets team_id to the priority queue and schedules an immediate call activity for the assigned rep, so the "hot" signal shows up as a task, not a number someone has to remember to check.

Routing: score decides the queue, not just the order

Scoring only pays off if it changes what happens next. Odoo's Lead Assignment (CRM → Configuration → Lead Assignment) runs on a scheduled action and distributes leads across sales teams and members based on domains you define per team — country, industry, lead source — plus each member's assignment_max, so nobody gets buried while a teammate sits idle. We layer the score on top of that domain: leads above the hot threshold skip the batch cron entirely and assign in real time through the automation rule, while everything else waits for the next assignment run. The batch cron is the same mechanism we cover in scheduled actions that won't wake you at 3 AM — lead routing is one more job on that same clock, just with a fast lane carved out above it.

Scores also decay. A lead sitting at 55 points today is not the same lead in three weeks with zero activity — the interest that generated the score has probably evaporated. A nightly scheduled action that shaves points off leads with no engagement in N days keeps the "hot" list honest, and it catches the leads that should be handed to a nurture sequence instead of a phone queue.

What breaks this, and how we guard against it

  • Silent trust erosion. If the automated score routes a rep to a bad lead twice, they stop looking at the score at all. Keep the rule set visible and editable by the sales manager, not buried in a module only IT can touch — the same guardrail we apply to every server action: [AUTO] naming, one action per rule, documented in the process map.
  • Scoring what's easy to measure, not what predicts conversion. Page views are easy to log and weakly predictive on their own. Validate scoring weights against actual close rates each quarter — Predictive Lead Scoring's frequency table is a good sanity check even for leads you route manually, because it tells you which variables your closed deals actually correlate with.
  • Enrichment fields nobody fills in. A firmographic weight is worthless if the company-size field is empty on 80% of leads. Either wire an enrichment integration on lead creation or drop the field from the scoring formula — a rule that silently contributes zero is worse than no rule, because it hides in the total.

Lead scoring is a CRM configuration problem before it's a data-science problem — the variables above live in Odoo already, and the routing logic is automation rules, not a project. We map exactly this kind of flow in the fit-gap analysis at the start of every Odoo implementation: what counts as "hot" for your business, and who should be holding the phone when a lead crosses that line.

Want us to publish something specific?

Tell us what you'd like to read and we'll add it to our writing queue.

Get the next one in your inbox

New articles, videos and the occasional engineering note — a short mail when there’s something worth reading, nothing else.