Case study
Building the one feature instead of buying the platform
A retailer was about to buy an enterprise platform to show targeted offers. I built that one feature instead. It was live in four weeks, has shown over 765,000 offers since (production database, Oct 2026), and they own all of it.
- Client
- Online retailer, WooCommerce
- Role
- Architecture and build, at Comet CX
- Timeline
- Four weeks to build, a little more than a year in production
- Stack
- Next.js on Vercel, Redis, Google Tag Manager, Google Analytics
A retail client was close to signing with an enterprise personalisation platform. The contract was annual, the quote was large, and most of what it covered was never going to be switched on. What they did need was complex: rules for which products and categories each offer should include or exclude, and those rules would have been extremely difficult to achieve in the personalisation platform.
Before they signed, I sat down with them and worked out what they actually needed: to show a specific offer to specific shoppers, on specific products, at the right moment, and to let the marketing team control it without a developer.
That's good retail thinking. A sitewide sale hands margin to people who were going to buy anyway, and it trains everyone to wait for the next one. An offer shown to someone on their third visit to the same ring is spent where it's most likely to change a decision. To do that you need precise targeting and a way to change your mind quickly. You don't need a platform.
I told them that for a third of the price of the annual personalisation licence, we could build it and they would own all of it: the code, the data and the roadmap.
Two weeks of prototype, two weeks to production
On day one we scaffolded the admin UI together in a collaborative AI workshop, to get the initial features right. I also set up the database and sign-in, and wrote two documents: a solution architecture and an implementation plan.
The first two weeks went on the core loop: creating and editing campaigns, a dashboard listing them, and the first version of the storefront script. By the end of week two the whole path worked. A shopper opened a product page, the script called the API, and the API decided whether to show an offer.
The second two weeks were spent iterating, improving and making the software easier for the marketing team to use. Marketing got a live preview that shows a banner exactly as shoppers will see it, and I added a kill switch. Then came the work of fitting the real storefront: badges on listing pages, visitor-history rules, brand targeting, skipping out-of-stock products, campaign priority and impression counts. The first offers were being shown to customers by the end of week four.
So half the build was the engine, and the other half was everything that lets a marketing team run it without calling a developer.
How it fits together
There are four parts: a script on the storefront, a small service that decides who sees which offer, an admin console for marketing, and a single Redis database.
One script tag
The storefront needs one line added:
<script src="https://<deployment>/api/snippet.js" async></script>
Everything else happens inside that script. It sets a first-party cookie holding a random ID that lasts a year, which is all the identity it needs. There's no third-party tracking and no login.
The store already ran Google Tag Manager, so SKU, brand and category were already on every page. The script reads them from the tag manager data and only falls back to reading the page itself if they're missing. The storefront team had nothing new to build.
On a product page, the script asks the API whether to show a banner. On a listing page, it sends every product in one request and adds an OFFER badge to the ones that qualify. It also catches tiles that load later, after filtering or infinite scroll.
Each time a banner is shown, the script sends a campaign-view event to Google Analytics, so marketing sees results in the reports they already use and has no separate dashboard to check. If the API errors or returns nothing, the shopper just sees the normal page.
The script is served from the app, not pasted into the theme, so I can change it without touching the store. The kill switch is one environment variable. Flip it and the script comes back empty, which turns every campaign off at once.
The decision
Every product view runs one check:
- Load the active campaigns and drop any outside their dates.
- Read how many times this visitor has viewed this product, but only if a live campaign uses that number.
- Apply each campaign's rules: included or excluded SKUs, brands, a minimum number of views ("show the offer on the third visit"), and new or returning visitor.
- If more than one campaign qualifies, pick the one with the highest priority, so the result is always predictable.
- Record the view and the impression in one round trip to the database.
Personalisation platforms sell "a returning visitor sees an offer on the product they keep coming back to" as a feature. Here it's about 50 lines of TypeScript that anyone on the team can read and test.
The admin
Marketing builds campaigns in a five-step wizard: basics, products, display settings, rules, then preview and publish.
Development, staging and production are separate environments, and the admin shows a badge for each so nobody edits the live store thinking it's staging.
One database
Campaigns, visit counts and a page-view log all live in Redis. There's no warehouse and no second database to keep in step, which keeps each decision fast and the running cost low. Fifteen months in, it holds view counts for about 3.4 million visitor and product pairs. The page-view log is there if the client ever wants deeper reporting, but day-to-day use doesn't need it.
Access
Only the marketing team can sign in to the admin. The storefront endpoints only answer the client's own domains, and the service rejects malformed input before it reaches the rules.
After launch
It has been in production for a little more than a year. In that time it has run 25 campaigns and shown a targeted offer more than 765,000 times (production Redis, queried Oct 2026). That figure counts banners on product pages only. Badges on listing pages aren't counted, so the real number of offers seen is higher.
Figures from read-only queries against the production Redis database, October 2026.
What the client ended up with
The code is theirs, and the data stays in their own database and their own Google Analytics, not a vendor's. It runs on standard tools with usage-based pricing and nothing that locks them in. They aren't paying for a visual editor, an A/B testing suite or a recommendation engine they'd never open. And when they want something changed, they decide when it happens.
If you're weighing this up
Buying a platform makes sense when you need most of what it does. When you need one or two features, check how much of the platform those features really depend on. Here it came down to a script tag, a rules function, a form and a key-value store.
Building isn't free, and this does need a small amount of maintenance. But the end result is software that the client owns, doesn't pay a monthly licence for, and can shape however they want from here.
Wireframes are simplified drawings of the real screens. Brands, products, prices and figures in them are examples, not client data.