Skip to main content

Product recommendations

Joryio's recommendation engine picks per-customer items for your messages - email, SMS, WhatsApp, Viber, push, in-app and landing pages, in campaigns and journeys alike (see Where recommendations work). It is deliberately transparent - you decide what it learns from:

  • Item source - your synced store products (Shopify/WooCommerce) or any Custom Entity that holds a catalog.
  • Training signals - which events teach the engine, each with a weight (e.g. purchase ×5, add_to_cart ×2, product_viewed ×1 - any events, including your custom ones). The item id property tells the engine which event property carries the item (e.g. productId); when an event uses a different property (say an ad_view event carrying ad_id), set it per signal - each signal row has its own optional property that overrides the model default.
  • Selection (optional) - pick a selection to narrow what the model may recommend: a product selection for store products (managed on the Products page), or an entity selection for a Custom Entity catalog (e.g. only one brand or location). Static filters only - per-recipient filter values can't resolve at ranking time. It applies in campaigns and journeys alike. When too few of the model's picks match, the selection's own products fill the gap. If the selection cannot be read (it was deleted, or the lookup fails), the model shows only its pinned products - never items outside the selection.
  • Training window and an option to never recommend items the customer already purchased (see Exclusions).

Multiple models​

A workspace can hold several named models, each with its own type, source and signals - create them under Settings → Recommendations and pick one per product block in the email editor (Source: Recommended → Model), or address one by its slot in Liquid. One model is marked as the default and is used when a block doesn't pick one.

Recommendation types​

Each model has one type, chosen under Settings → Recommendations → Model type:

TypeWhat each recipient getsTraining
PersonalizedItems matched to their own recent activity (trained co-occurrence)Nightly + Train now
Most popularThe same bestseller list for everyone, weighted by your signalsNightly + Train now
Trending nowWeighted activity over the last 7 daysNone - computed live
Recently viewedTheir own latest items. Falls back to Trending for new customers; never topped up with other itemsNone - computed live
Similar itemsContent-based: products that share categories, brand, attributes (e.g. Color, Material) and tags with a product. Categories weigh most, then brand and attributes; tags break tiesNone - computed live
Bought togetherProducts that appeared in the same order as a product, over the model's training window (from your synced orders). If the product has no co-purchases yet, similar items fill inNone - computed live
Viewed togetherProducts viewed by the same people as a product. Trains on product views (Product Viewed) only. A product with no co-view history falls back to similar itemsNightly + Train now
New arrivalsThe newest products in the catalog (by when they were added), optionally limited to one categoryNone - computed live
Popular in categoryBest sellers (units in orders) or most viewed products (Product Viewed events) - choose sales or views - in a category over the model's window. Category: the one set on the model, else the anchor product's first category, else the whole catalogNone - computed live
Top discountedThe biggest discount of compare-at price vs price, optionally in one categoryNone - computed live

The last six types (Similar items to Top discounted) work with store products only, not custom-entity catalogs.

Which product?​

Similar items, Bought together and Viewed together recommend around one product. It is picked in this order:

  1. A fixed product id set on the model.
  2. The product id set on the email product block (the optional Product id field).
  3. In a journey, the product of the event that triggered the journey - its productId / product_id property, or the first item of a products / items list.
  4. Otherwise, the recipient's most recently viewed product.

With none of these, the model's popular fallback is shown.

Pinned products​

Every type accepts a list of pinned product ids on the model. Pinned products are shown first, in that order, and the type's algorithm fills the remaining places. Pinned products are subject to the same exclusions - a sold-out or already-bought pinned product is skipped.

Exclusions​

By default:

  • Out-of-stock products - products your store marks out of stock are not recommended. Switch Hide out-of-stock products off on a model to allow them.
  • Already purchased - products the recipient already bought (from their orders) are not recommended. Don't recommend items the customer already purchased is on by default and applies to every type.
  • The anchor product itself is never recommended.
  • The model's Selection (allow-list) still applies; pinned products keep their place.
  • The block list beats everything, pinned products included.

Block list​

Products that must never be recommended - gift cards, samples, a discontinued line. There are two lists, and both always apply:

  • Per model - Never recommend on the model: product ids, and for store products also categories and tags (not case-sensitive).
  • Workspace-wide - Settings → Recommendations, below the models: the same three lists, applied to every model.

A blocked product is never shown by any type - not as a pick, not when pinned, not from the popular fallback, and not from the fallback used when a slot runs out of time. A product id may be the store's id or Joryio's. Each list holds up to 500 product ids and 100 categories and 100 tags. Changes reach messages within 5 minutes.

Boost and bury​

Merchandising rules on top of relevance, for store products. On a model, open Boost and bury, click Add rule and pick:

  • What - a product field (numeric or yes/no - the list shows the fields your products actually have: price, compare-at price, discount percent, units in stock, in stock, and attributes such as a rating or a margin), a category or a tag.
  • Direction - Boost lifts matching products, Bury pushes them down.
  • Strength - Light, Medium or Strong (0.2, 0.5 and 1.0).

How the rules score: the model first finds and orders its candidates (relevance, including the learned ranker); the block list, the Selection and the stock filter remove what may not be shown; then the rules re-order what is left, before the list is cut to the number of items the message shows. A candidate's relevance comes from its place in that order - the first of 10 scores 1, the last 0.1. A numeric field is scaled across the candidates from 0 (lowest) to 1 (highest); a yes/no field, a category or a tag counts 1 when it matches and 0 when it does not; a product without the field counts 0. Boost multiplies relevance by (1 + strength x value), Bury divides by it, and several rules multiply.

Rules never add a product the model did not find, never show a blocked product and never move a pinned product - pinned products keep their places. They apply the same way in every channel and in journeys. Up to 10 rules per model.

Examples:

  • Push the sale - Boost, category "Sale", Medium: a sale item just below a full-price item moves ahead of it; one far down the list does not jump to the top.
  • Margin first - Boost, attribute "Margin", Strong: among similarly relevant products, the highest-margin ones lead.
  • Do not lead with add-ons - Bury, tag "accessory", Light.
  • Plenty in stock first - Boost, Units in stock, Light.

The card's Preview shows one contact's list Without rules and With rules side by side, using the rules on screen before you save (moved items are outlined). Pick a contact by email, phone or id; with none picked it shows the list an anonymous visitor gets. A preview never counts as a served recommendation.

Fallback and speed​

Every list is topped up from the model's popular items (trained popularity, else 7-day trending, else the newest products), so a slot is never empty while the store has products.

Each recipient has a time budget (1.5 seconds by default). A slot that does not resolve in time gets the model's popular items, so a send never stalls. Lists that do not depend on the recipient - a product's related items, a category's best sellers, new arrivals - are computed once per campaign send and reused for every recipient.

How it learns​

Training (nightly for enabled configs, or Train now) runs two passes over your event stream: weighted popularity per item, and item-to-item affinity - which items the same customers engage with together, normalized so niche pairs aren't drowned out by bestsellers. Viewed together models train the same affinity on product views only.

Learning from outcomes (v2 ranker)​

Once a model has real serving outcomes (recommendations served → items bought), a second-stage ranker trains on them nightly and starts re-ordering each recipient's candidates by learned conversion probability - "when we show X to customers like this, they buy". It includes an exploration slot so new items keep getting a fair chance to earn data. The ranker activates per model only after enough outcomes exist; until then (and whenever it's unreachable) the engine's standard ordering is used, so nothing ever breaks. Sends carry a flag distinguishing ranked from standard ordering, so their performance stays comparable.

Besides past outcomes, the ranker weighs how each product fits the recipient: its price against what they usually spend (order value and item price), whether it is in their top categories or brands (from their orders), whether it is on sale for someone who tends to buy on sale, and whether it is selling faster this week than usual. These are taken as they were when the recommendation was served, so the ranker never learns from what happened afterwards; recommendations served before this existed count as neutral. A model starts using them at its next nightly training.

Where recommendations work​

Recommendations work in every channel, in campaigns and journeys:

ChannelHow
EmailThe product block: Source: Recommended, choose the model and a count, plus an optional Product id for the anchored types. Liquid loops over recommendations work too, in campaigns and journeys; a block and a loop that use the same model show the same items
SMS, WhatsApp, ViberThe Liquid variable {{ recommendations.<slot> }} in the text (for WhatsApp: session / reply text and template variable values)
Push{{ recommendations.<slot> }} in title, body, image and link
In-appProduct cards in HTML messages, or Liquid in title / body / image of native messages
Landing pagesProduct cards or Liquid

Liquid slots​

The slot is the model name turned into a key: lower-case, with spaces and punctuation becoming _ ("For you" → for_you, "Bestsellers" → bestsellers). recommendations.default always means the workspace default model. A model whose name has no Latin letters (e.g. a Hebrew name) gets the slot model_<first 8 characters of its id> - the Settings page and the personalization picker show each model's slot. An unknown slot renders as an empty list.

Each item has:

FieldMeaning
name (also title)Product name
priceFormatted with currency, e.g. $19.90
compareAtPrice, discountPercentWhen the product is discounted
urlProduct link
image (also imageUrl)Product image
descriptionProduct description
idProduct id

A slot holds up to 4 items by default (up to 12 via a card's data-jr-count).

Item links are click-tracked like any link in the message: UTM parameters and Joryio ids are added at the click redirect, and SMS / WhatsApp / Viber links are shortened (respecting the per-message Shorten & track links switch).

SMS / WhatsApp / Viber text:

{% for item in recommendations.for_you limit: 3 %}
{{ item.name }} - {{ item.price }} {{ item.url }}
{% endfor %}

Push:

Title: {{ recommendations.for_you.first.name }}
Body: Now {{ recommendations.for_you.first.price }}
Image: {{ recommendations.for_you.first.image }}
Link: {{ recommendations.for_you.first.url }}

WhatsApp messages carry text and a link only - catalog / product messages are a separate feature, not yet available.

In-app native messages: Liquid in title, body and image, e.g. image {{ recommendations.for_you.first.image }}.

Product cards​

In in-app HTML messages and landing pages, this element becomes a row of product cards (image, name, price, struck-through compare-at price when discounted, link):

<div data-jr-recommendations="for_you" data-jr-count="4" data-jr-button-label="Shop now"></div>

Liquid loops also work there.

On landing pages, product cards work on every published page: anonymous visitors see the model's non-personal list (refreshed every few minutes), and an identified visitor gets their own. The Liquid form works on personalized pages.

Previews and test sends​

Editor previews and test sends in every channel show real items: for the test contact when the test goes to one, otherwise the model's list for someone with no history. They use the same fallback, block list and Selection as a real send. A test send never counts as a served recommendation in the analytics below.

Not yet supported​

  • In journey in-app messages, Similar items, Bought together and Viewed together do not use the product that triggered the journey - they use the model's fixed product, else the recipient's most recently viewed product.

Performance analytics​

Once a model's recommendations go out in real sends, its page shows the last 30 days: how many recommendations were served, to how many recipients, the share who clicked the carrying message, and the share who bought a recommended item within 7 days of receiving it (attributed via the recommendation.served platform event). recommendation.served is recorded for email, SMS, push, WhatsApp and Viber sends (not in-app), so the panel counts every one of those channels.

How it serves​

At send time each recipient's recent activity votes for related items; customers without history get the model's popular items. Configure models under Settings → Recommendations, then use them through the email product block (Source: Recommended with a count) or the recommendations Liquid variable in any other channel.