If you are adding recommendations to a SaaS product that already exists, the first thing to build is not a model. It is a ranked list of the most popular items in the right context, shipped behind a feature flag, with the click logging switched on.
That sounds like a cop-out until you have watched a team spend four months on matrix factorisation and then find that nobody knows whether it beats "most used this month", because "most used this month" was never on screen long enough to measure. The baseline is not a placeholder you skip past. It is the only number that tells you whether anything you build afterwards was worth building.
There is a ladder here, and each rung costs roughly an order of magnitude more than the one below it in engineering time, data requirements and operational load. Popularity and rules. Content similarity. Collaborative filtering. Learned ranking. The mistake almost everyone makes is starting at rung three, because that is what the blog posts talk about.
Climb one rung at a time and stop as soon as the next stops paying for itself. A large share of SaaS products never need to go past rung two, and shipping rung two well beats shipping rung four badly by a margin that is embarrassing when you finally measure it.
The four rungs, from cheapest to most expensive
Rung one: popularity and rules
Trending this week. Most purchased in this category. Recently viewed. Most opened by people on your plan tier. Bought together, computed as a plain co-occurrence count over the last ninety days of orders. None of that is machine learning. All of it is a SQL query and a cache.
It ships in days, with no training pipeline, no model server, no drift and no on-call surprises. It is also hard to beat on a new surface, because popularity is not a naive signal. It encodes real aggregate preference, and when a learned model does beat it the margin is usually smaller than anyone expected.
The part people skip: make the rules contextual rather than global. "Most popular overall" is a bestseller chart. "Most popular among accounts in this industry, on this plan, who did the same thing you just did" is a recommender with no model in it, and the gap between those two is often wider than the gap between rules and collaborative filtering.
Rung two: content similarity
Recommend items that resemble the one the user is looking at, using attributes you already store. Category, tags, price band, author, difficulty, description text. Turn each item into a vector, either a sparse one from TF-IDF over its text and categorical fields or a dense one from a hosted embedding endpoint, and find nearest neighbours.
This is the rung most SaaS products should actually be on, for one reason: it works on day one with zero interaction history. A brand new catalogue, a brand new tenant, a product you launched last Tuesday. Content similarity does not care, because it reads the items rather than the behaviour.
Its weakness is that it is a filter bubble by construction. Show a user a blue running shoe and it will show nine more blue running shoes. Fix that by taking the top candidates by similarity and then applying a rule that caps how many results share a category or attribute value.
Rung three: collaborative filtering
People who did this also did that. No item attributes needed, which is the appeal: it finds relationships nobody encoded, including ones that make no semantic sense and work anyway.
The honest threshold is not "10,000 users" or any other round number you read somewhere. It is interactions per item, and specifically the median item, not the mean. Sort your catalogue by distinct users who have interacted with each item and look at the middle of that distribution. If the median item has been touched by a handful of people, any co-occurrence between two items is coincidence and the model will confidently recommend noise.
The mean will lie to you here. A catalogue where twenty items have thousands of interactions and four thousand items have two produces a healthy-looking average and a recommender that has learned only your bestseller list. Check the median and the tail before you write a line of training code.
Rung four: learned ranking
A model that scores a candidate set using many signals at once: user features, item features, context, recency, interaction history, and the output of the rungs below it as input features. This is where the real gains are, and where the real cost is.
The cost is not the training run. It is the feature pipeline that must produce identical values at training time and serving time, the label definition you will argue about for weeks, the retraining cadence, the monitoring, and the fact that a product change can now degrade recommendations silently through a feature that quietly changed meaning. Learned ranking is not a feature you add. It is a system you operate.
Do not climb here until rungs one to three are live, measured, and visibly at their ceiling. If you cannot say what your current click-through is, you are not ready.
Cold start is the actual hard problem, and there are three of them
Every recommender discussion collapses "cold start" into one bullet. It is three different problems with three different answers, and confusing them wastes months.
A new user, in a product that has plenty of data. The easiest of the three. You have a working model, you just have no history for this person. Fall back to popularity, scoped as tightly as the signup data allows: their role, their plan, their industry, the thing they imported during onboarding. Then update fast. The first three or four actions of a session carry more information than most profile fields, so treat the in-session sequence as a signal, not just the persisted profile. Onboarding questions help, but only ask what you will actually use in a query.
A new item, in a catalogue that has plenty of data. Harder, because it is self-reinforcing. An item with no interactions never gets recommended, so it never gets interactions. Two fixes, and you need both. Use content similarity to place the new item near known items so it inherits their position. And spend a deliberate exploration budget: reserve a slot in the results for items with thin data, accept that this costs you a little measured performance today, and treat it as the price of knowing anything about your catalogue tomorrow.
A new product, where nobody has any data. The one people refuse to admit they are in. There is no clever answer. Collaborative filtering is not available to you and no amount of engineering changes that. Build rungs one and two, instrument everything properly, and wait. The valuable work at this stage is the event stream, not the algorithm.
The engineering underneath is what decides whether this works
Almost every failed recommender project failed on plumbing, not on maths.
The event stream comes first
Before anything else, you need an append-only log of who did what to which item, when, and in what context. One table, one shape: user_id, tenant_id, item_id, event_type, timestamp, context. Never updated, never deleted, and written from the server rather than the browser so an ad blocker cannot quietly delete a third of your training data.
If you do nothing else this quarter, do this. Recommenders are trained on history and you cannot backfill history you never wrote down. A team that starts logging today has options in six months. A team that starts logging when the project kicks off starts the clock at zero.
Implicit signals are weaker and far more plentiful
An explicit rating is unambiguous and vanishingly rare. Almost nobody rates anything, and the ones who do are unrepresentative, usually because they were delighted or furious.
Implicit signals are the opposite. A click, a view longer than a few seconds, an add to cart, a download, a repeat visit. Each is a weak and noisy indicator, because a click can mean interest or a misleading thumbnail. But you get thousands of them for every rating, and volume wins.
Two consequences. Weight your event types deliberately, so a purchase or a plan upgrade counts for far more than a hover. And remember that implicit data has no negatives: a user not clicking an item usually means they never saw it, not that they disliked it. Treating unseen as negative is the most common modelling error here, and it produces a recommender that has learned your existing UI layout rather than your users' preferences.
Candidate generation, then ranking
You cannot score an entire catalogue per request. So the standard shape is two stages. Candidate generation cuts the catalogue down to a few hundred plausible items using something cheap: popularity, similarity lookup, a vector index, a co-occurrence table. Ranking then applies your expensive scoring to that shortlist.
This split is not an optimisation you add later. It is the architecture, and it also means each stage can sit on a different rung. Cheap candidate generation with a good ranker is a perfectly sensible system, and so is the reverse.
Batch precomputation versus real-time scoring
| Approach | How it runs | What it costs | When it is right |
|---|---|---|---|
| Batch precompute | A nightly or hourly job writes a ranked list per user or per item into a table or cache. Serving is a key lookup. | Cheap to serve, cheap to reason about. Compute cost grows with users times items, not with traffic. | Recommendations that do not need to react within a session. Most SaaS dashboards, email digests, "you might also like" on item pages. |
| Real-time scoring | Candidates fetched and scored per request, using in-session behaviour as input. | A model server on the critical path, a latency budget, a feature store, and an infrastructure bill that grows with traffic. | Sessions where intent changes fast: search, browse, anything where what the user did sixty seconds ago should change what they see now. |
| Hybrid | Batch candidates, real-time reranking of a short list against session context. | Middle. One cheap lookup plus a small scoring step over a few hundred rows. | The default for most teams once rung one is proven. Gets most of the responsiveness for a fraction of the operating cost. |
Start batch. It is astonishing how far a nightly job and a Redis key gets you, and the failure mode of a stale recommendation is much gentler than the failure mode of a model server timing out inside your page load.
Can you build this without hiring ML engineers?
Yes, and for a first version you probably should. The question usually arrives in roughly this shape: I need a recommendation engine inside my SaaS dashboard, my data is in Postgres or a CSV export, and I am not hiring an ML team.
The fast path is real. Export items and events, embed each item's text using a hosted embedding API, push the vectors into a managed vector index or into Postgres with a vector extension you already run, and expose one endpoint returning nearest neighbours filtered by tenant and availability. That is a day or two for a backend engineer with no ML background. Add an LLM reranking step over the top twenty candidates, with a prompt describing your ranking objective, and you have something that demos well by the end of the week.
The managed recommendation services from the large cloud providers take the other route: upload interaction data in a defined schema, they train and host the model, you call an endpoint. Also real, also fast, and retraining is their problem.
So the question is not whether you can be live in a week. You can. The question is what month six looks like.
- Explaining a recommendation. A customer asks why item X was suggested to their user. With a similarity lookup or a rules layer, you can answer. With a hosted black box or an LLM prompt, you often cannot, and "the model decided" is not an answer that survives an enterprise review.
- Fixing one bad recommendation. You will get a complaint about a specific pairing. Can you suppress it in five minutes without retraining? If the answer is no, build a rules and blocklist layer in front of whatever you use, on day one.
- Changing the objective. Today you optimise for clicks. In month six someone wants to optimise for retention, or margin, or filling inventory that is not moving. In a system you control that is a change to a scoring function. In a managed one it may be impossible.
- Cost at volume. Per-request LLM calls are pleasant at prototype traffic and unpleasant at production traffic. Price the endpoint at your projected request volume before it becomes a line item somebody notices. The same discipline applies across a SaaS build budget: model API fees and the software bill are separate lines and behave differently.
- Where the data goes. Sending tenant behavioural data to a third party has contractual and regional implications. Check before you build, not after a customer's security questionnaire arrives.
The sensible position: take the fast path for version one, but own the two pieces that are hard to migrate later, your event stream and your rules and suppression layer. Then swapping the engine underneath is a week of work rather than a rewrite. The same reasoning applies to most AI features inside an existing product, where the durable asset is the data pipeline and the guardrails rather than the model of the month.
What changes when your product is multi-tenant
Nearly every recommender tutorial assumes one shared user base. SaaS does not work that way, and the differences are structural.
Per-tenant or shared model. A per-tenant model respects data boundaries and captures each customer's own patterns, but every new tenant starts from nothing and you now operate N models instead of one. A shared model learns much faster and generalises to new customers, but only if cross-tenant learning is contractually acceptable and nothing tenant-identifying can leak into an output. The usual middle ground is a shared model with tenant identity as a feature plus hard filtering at serving time, so a tenant can only ever be shown their own items regardless of what the scorer wants.
Cold start repeats on every onboarding. You solve cold start once, ship it, and then it returns in full for every customer you sign, forever. Your third customer had no data. So will your three hundredth. Any design that treats cold start as a launch-phase problem will break in sales demos, which is the worst possible place for it to break.
A small tenant still expects the feature to work. A customer with forty items and a fortnight of history pays the same list price as one with forty thousand, and judges the feature on their own account on day one. That argues for content similarity as the floor everywhere, with collaborative signals layered on only where a tenant's data supports it, and a per-tenant switch that picks the rung automatically from data volume rather than a global setting.
Tenant scoping belongs in the schema from the first migration. Every event row, every item row, every precomputed recommendation row carries a tenant id, and the queries filter on it in the data layer rather than in application code that somebody will forget to write. Retrofitting tenant scoping onto a live system is one of the more painful migrations in multi-tenant product work. When we built Denti360, branch scoping went into the database schema from the first migration rather than being added later, and that decision keeps paying back every time a new query is written.
How to tell whether it is actually working
Offline metrics measure how well your model reproduces the past. Precision at k, recall at k, NDCG: all of them replay historical interactions and check whether your model would have predicted them. But that history was produced by whatever was on screen at the time, so a model that agrees with your existing UI scores beautifully and changes nothing. Offline evaluation is good for rejecting candidates cheaply before you spend traffic on them. It is not evidence that the feature works.
Online evaluation with a holdout is the evidence. Keep a slice of users who see only the rung-one baseline, and keep it running permanently rather than for the length of one experiment. Without a live holdout, six months from now you cannot answer whether the recommender is still contributing anything, and the honest answer is sometimes no.
Then look past accuracy at two numbers most teams never compute:
- Coverage. What fraction of your catalogue appeared in any recommendation to any user over the last month? If it is a tiny slice, you have built a bestseller chart with extra infrastructure. A recommender that only ever surfaces the top ten items is a failed recommender even when its click-through looks excellent, because those items would have been found anyway and the rest of your catalogue is now invisible.
- Diversity within a result set. How similar are the items in a single list to each other? Ten near-identical suggestions convert worse than five varied ones, and they teach the user that the feature is not worth looking at.
Add one guard metric: whether recommendations are cannibalising search or navigation rather than adding anything. And watch retention over weeks alongside click-through, because a recommender tuned purely on immediate clicks will happily learn to show whatever is most clickable and least useful.
Which rung should you build?
| Your situation | Catalogue | Interaction volume | Build this | Rough effort |
|---|---|---|---|---|
| Just launched, or a brand new surface | Any | Nothing usable yet | Rung one. Contextual popularity plus recently viewed, and get the event stream logging properly. | Days |
| Rich item attributes, thin behaviour | Hundreds to tens of thousands | Median item touched by very few distinct users | Rung two. Content similarity over attributes and description text, with a diversity cap. | One to two weeks |
| Small catalogue, engaged users | Under a few hundred items | Most items touched by many distinct users | Rung three, but start with item-to-item co-occurrence rather than a factorisation model. Simpler and usually as good at this size. | Two to four weeks |
| Large catalogue, real traffic | Tens of thousands and up | Median item has a healthy distinct-user count | Rung three properly, with candidate generation split out and a batch precompute pipeline. | One to two months |
| Rungs one to three live and measured, ceiling visible | Any | High, with clean labels | Rung four. Learned ranking over the existing candidate sources, with a permanent holdout. | A quarter, then ongoing |
| Multi-tenant, wide range of customer sizes | Varies per tenant | Varies per tenant | Rung two as the floor for everyone, rung three switched on per tenant once that tenant's data supports it. | Two to four weeks plus the switch |
Two notes on reading that table. Effort assumes the event stream already exists; if it does not, add the time to build it to every row. And every row above rung one carries an ongoing cost, not just a build cost. Recommenders decay, because catalogues change and behaviour shifts, and a pipeline nobody has looked at in a year is quietly serving last year's preferences.
If you are still deciding whether this belongs in the next release at all, the cheapest useful move is rung one behind a flag with a holdout. Two weeks, no model, and at the end you have a real number instead of an argument. That is also the right shape for a first slice of an MVP build: ship the version that produces evidence, then spend on the version that produces gains.
Adding recommendations to a SaaS product and not sure which rung your data can actually support? A Scoping Sprint ($2,300, two weeks) ends with a recommendation architecture and data readiness assessment made for your case, a prototype, and a fixed quote. Or just start a conversation.


