Most Shopify wishlist apps have the same quiet flaw: the wishlist data lives in the app, and the merchant's email platform never sees it. The store knows who wants what — but can't email on it.
We built WishList Ninja to fix exactly that. Here's how the interesting bits work.
The core idea: profile properties, not just events
Everyone fires events ("Added to Wishlist"). Events are fine for triggering flows, but useless for segmentation — you can't build a clean segment of "people with items saved right now" from a two-month-old event.
So on every save/remove we write profile properties to Klaviyo, Mailchimp or Omnisend in real time:
-
has_active_wishlist— true only while the wishlist has items in it -
wishlist_count— items currently saved -
wishlist_value— what they're worth -
top_saved_item— the product they want most
That first flag sounds trivial. It isn't.
Why a boolean was the hard part
**
has_active_wishlist has to flip **false again — otherwise it's just "has ever used the wishlist," which is worthless. So every path that empties a wishlist has to re-evaluate it:
- shopper removes the last item ✅
- shopper buys the last item (orders webhook marks items purchased) ✅
- guest list merges into an account ✅
- and the subtle one: saved collections don't count. A shopper following a collection but holding zero items is not "active intent." Items only.
One rule, five code paths. The rule lives in exactly one function so the paths can't drift apart.
*7 flow triggers from 2 webhooks
*
The email moments merchants actually want are mostly store-side changes, not shopper actions:
Added to Wishlist · Price Drop · On Sale · Low Stock · Back in Stock · Collection Saved · New in Saved Collection
Five of those seven come from diffing products/update webhooks against what shoppers have saved: price went down → find everyone holding that product → fire Price Drop into their ESP. No polling, no cron scanning the catalog.
*Multi-ESP fan-out without domino failures
*
Supporting three ESPs means three APIs that can each be down, rate-limited, or misconfigured. Early version: Klaviyo throwing meant Mailchimp and Omnisend never got the event. Bad.
Now each delivery is isolated — a failure is caught and logged, and the job only retries if nothing was delivered. One dead API key can't block the other platforms.
*The storefront budget: ~12KB
*
The whole storefront (hearts on product cards, slide-out drawer, wishlist page, share links, save-for-later on the cart) is vanilla JS, minified to under ~12KB. No framework, no fonts, no CSS libraries. Core Web Vitals are a ranking signal; an app that slows the store is stealing from the merchant to fund itself.
*Stack
*
Laravel + MySQL + Redis on Cloudways, Shopify theme app extension for the storefront, Mailjet for transactional email — and a large amount of Claude Code for the build itself.
Launching on the Shopify App Store now. If you run (or build for) a Shopify store, I'd love your take: wishlist-ninja.com 🥷
Happy to go deeper on any of this in the comments — especially the webhook diffing.
Top comments (0)