Personalization doesn’t need a six-month project anymore

Adding personalization to Croozie took less than a day. Existing models and an ordinary stack make it possible to start with a useful experience, measure it, and improve.

Adding Jev-powered personalization to Croozie took less than a day. Building a useful personalized experience doesn’t have to start with a specialist team and months of infrastructure work anymore.

Before this turns into one of those “I built an entire company before lunch” posts… Croozie already existed. The cruise listings, deals, and application were there.

What I added was a better way to decide which deals a person should see.

Because a deals page showing everyone the same list has a fairly generous definition of “for you.”

A bargain for whom?

Someone traveling solo doesn’t necessarily want the same cruise as a family of four. Someone looking for a quick Caribbean trip probably doesn’t need a two-week itinerary on the other side of the planet.

And the cheapest advertised fare isn’t automatically the best deal. Especially when that price assumes you brought another person to split the cabin.

The goal was straightforward: move from a static deals list toward “Deals for You.” Rank the options around the person browsing, not just the catalogue.

That’s where this gets interesting.

The stack doesn’t need to be a science project

The stack uses Postgres with pgvector, Inngest, Jev, and OpenRouter. Each has a specific job.

Postgres keeps the facts straight.

Dates. Departure ports. Prices. Occupancy. Cruise length. These are constraints, not creative-writing prompts. If someone has a $1,500 budget, a model shouldn’t get to recommend a $3,000 sailing because the vibes are immaculate.

Filter what’s actually eligible first.

pgvector helps find relevant candidates.

Embeddings let you represent descriptive information about ships and itineraries in a way that supports similarity search. pgvector lets that live in Postgres alongside the ordinary data.

That means you can look beyond exact keyword matches without immediately adding another database to your life.

But similar doesn’t necessarily mean suitable. Retrieval gets a cruise onto the shortlist. It doesn’t have to decide the winner.

Jev helps evaluate the fit.

This is the part I find interesting about Jev. It’s a structured decision model, rather than just another chatbot you ask to write an enthusiastic paragraph.

Its Score primitive evaluates information against defined levels and returns structured scores, probabilities, and confidence.

For recommendations, the question becomes: given these preferences and these cruise facts, how strong a match is this option?

Define what a good match means. Provide the relevant context. Use the score as one input to ranking.

Not “here’s my entire database… good luck.”

Inngest handles the background work.

Catalogue changes, refreshed representations, and recommendation updates shouldn’t all become work the user waits for when opening a page. Inngest gives you workflows with durable steps and retries for that preparation.

OpenRouter provides model access.

Jev is available through OpenRouter, giving the application an integration route without making the model provider the architecture of the entire product.

Database. Retrieval. Decision. Background work.

A manageable set of pieces, not an infrastructure shopping spree.

One foundation, more than one interface

The same personalization foundation now powers Croozie Ask.

The deals page and the assistant share retrieval and ranking, rather than developing completely different ideas about what suits the same person.

The page shows the options. The conversation helps refine the request and explain the choices. Both use real sailings, cruise data, and the person’s current constraints.

Start with good. Give yourself something to improve.

Personalization used to sound like a staffing plan. A few ML experts, several mad scientists, a pile of infrastructure, and six months before anyone saw a different recommendation.

That level of investment still makes sense for some problems. But it doesn’t have to be the starting point anymore. With existing models and a fairly ordinary stack, you can get a useful personalized experience up and running in hours. Croozie’s first release took less than a day.

Will it beat a recommendation engine with ten years of tuning? Probably not. Does it need to before it can improve on showing everyone the exact same list? Also no.

Don’t let the pursuit of a great recommendation engine stop you from shipping a good one. Get it in front of people. Measure whether it helps. Improve the parts that actually matter… instead of spending six months preparing to find out.

Get to the useful questions sooner

The next questions are practical. Are these recommendations better than the generic list? What happens when we know almost nothing about someone? Can a user change their mind without one random click following them around forever?

Those are the things worth measuring.

The exciting part is how quickly you can now get to that stage. Start with an existing product, define a narrow problem, and assemble the capabilities you need to test a better experience.

You don’t need to solve personalization forever before you can make a useful start. Build something people can try, learn whether it helps, and make the next version better.

Written by Adib Kadir. Personal notes on software, AI, and building things.

About the author