This guide will walk you through the process of connecting Klevu’s Smart Recommendations to your Klaviyo email flows using the Custom Actions code block. Once integrated, you’ll be able to send dynamic, personalized product recs to shoppers based on their real-time interactions on your store.
Please note that each time the Custom Action runs, it will be counted towards your Klevu recommendation impression usage.
Prerequisites
Klevu Setup
- You are a Klevu customer with Product Recommendations (Recs) enabled.
- You have access to your (Find from Store Info)
- Klevu Store API Key (e.g., klevu-xxxxxxxxxxxxxxxxx)
- CloudSearch APIv2 URL (e.g. https://uscs32v2.ksearchnet.com/cs/v2/search)
- Recommendations API URL (e.g., https://config-cdn.ksearchnet.com/recommendations/)
- You’ve created at least one Recommendation campaign in Klevu Merchant Center (KMC).
- You have a Recommendation ID from KMC (e.g,. k-00000000-3333-2222-1111-55842b196718)
Klaviyo Setup
- You are a Klaviyo customer with onsite tracking enabled.
- You have access to Flows and can use the Custom Action (code block).
- You have a Klaviyo Private API Key with Full Access (Read & Write). Learn how to create one.
- Your Klaviyo product catalog is synced with your eCommerce platform.
- You have a Klaviyo flow trigger that includes Product ID or Variant ID (e.g. a "Viewed Product" or “Started Checkout”, “Order Placed” event).
- You (or someone on your team) has experience with JavaScript development.
How It Works (High Level)
Here’s the flow:
- A Klaviyo email flow is triggered (e.g., product view).
- A Custom Action is executed that:
- Extracts the Product/Variant ID from the event.
- Request recommendations from Klevu using that ID.
- Extracts the recommended product IDs.
- Saves them as a custom profile attribute in Klaviyo (e.g., klevu_cart_abandonment_you_may_also_like).
- You use this attribute to populate personalized product blocks in your email templates.
Step-by-Step Setup
Step 1: Create a Recommendation in KMC
- Login to Klevu Merchant Center (KMC).
- Create or Select Recommendation.
- (Optional) Merchandise it as desired.
- Copy the Recommendation ID (you’ll need it later).
Step 2: Set Up a Flow in Klaviyo
- Create a new flow or edit an existing one.
- Ensure the trigger includes product-level context (e.g., "Viewed Product", “Started Checkout”, “Order Placed”).
- Add a Custom Action block (Node.js supported).
Step 3: Set Environment Variables
In the Custom Action, define the following variables:
Variable | Description |
KLEVU_API_KEY | Your Klevu Store API Key |
KLEVU_API_URL | Klevu CloudSearch API URL |
KLEVU_RECOMMENDATIONS_API_URL | Klevu Recommendations API URL |
KLEVU_RECOMMENDATION_ID | The Recommendation ID from KMC |
KLAVIYO_API_KEY | Your Klaviyo Private API Key |
KLAVIYO_PROFILE_ATTRIBUTE | Custom attribute to store recommended product IDs (e.g., klevu_cart_abandonment_you_may_also_like). Make sure this is unique to the type of recommendation that you are creating. |
Step 4: Add Required Modules
- Click on Add new module
- Search and add:
- axios
- @klevu/core
- klaviyo-api
Step 5: Paste the Custom Action Code
Your devs may need to modify the below code to correctly fetch the product ID or variant ID from the trigger event.
Paste the following into the Klaviyo Custom Action code editor:
const axios = require('axios');
const { KlevuFetch, kmcRecommendation, sendRecommendationViewEvent, KlevuConfig } = require("@klevu/core");
async function handler(event, context) {
const recommendationId = process.env.KLEVU_RECOMMENDATION_ID;
const eventProperties = event.data.attributes.event_properties;
const itemGroupId = eventProperties?.Items?.[0]?.Product?.ID;
let itemVariantId = eventProperties?.Items?.[0]?.Product?.Variant?.ID || itemGroupId;
if (!itemGroupId || !itemVariantId) {
console.error("Missing product IDs");
throw new Error("Missing required product IDs");
}
const profileId = event.data.relationships.profile.data.id;
KlevuConfig.init({
recommendationsApiUrl: process.env.KLEVU_RECOMMENDATIONS_API_URL,
url: process.env.KLEVU_API_URL,
apiKey: process.env.KLEVU_API_KEY
});
const res = await KlevuFetch(
kmcRecommendation(
recommendationId,
{
id: "recommendation",
itemGroupId,
currentProductId: itemVariantId,
groupBy: "name"
},
sendRecommendationViewEvent()
)
);
const productIds = res.apiResponse.queryResults?.[0]?.records
?.map(p => p.productId)
.filter(Boolean);
if (productIds?.length > 0) {
await updateKlaviyoProfile(profileId, productIds);
console.log("Klaviyo profile updated:", productIds);
} else {
console.log("No product recommendations found.");
}
}
async function updateKlaviyoProfile(profileId, productIds) {
const klaviyoApiKey = process.env.KLAVIYO_API_KEY;
const klevuProfileKey = process.env.KLAVIYO_PROFILE_ATTRIBUTE;
const url = `https://a.klaviyo.com/api/profiles/${profileId}/`;
const options = {
method: 'PATCH',
url,
headers: {
accept: 'application/json',
revision: '2024-07-15',
'content-type': 'application/json',
Authorization: `Klaviyo-API-Key ${klaviyoApiKey}`,
},
data: {
data: {
type: 'profile',
id: profileId,
attributes: {
properties: { [klevuProfileKey]: productIds }
}
}
}
};
try {
const response = await axios.request(options);
console.log("Update response:", response.data);
} catch (error) {
console.error("Error updating profile:", error);
}
}
module.exports.handler = handler;
Step 6: Test the Custom Action
- Click on the Test button to test the custom action.
- You can select a recent trigger event (of an actual profile) and check logs if the recommended IDs were correctly stored on the profile or not.
Step 7: Render Recommendations in Email
Once the product IDs are saved in a custom profile attribute, you can render them using Klaviyo’s catalog tag inside your email template.
Below is a responsive HTML block that shows up to 4 recommended products in a 2-column layout (please feel free to modify as needed).
<div style="font-size: 0px; text-align: left; direction: ltr; vertical-align: top; width: 100%; max-width: 900px; margin: 0 auto;">
<table width="100%" role="presentation" cellspacing="0" cellpadding="0" border="0">
<tbody>
{% for item in person|lookup:'klevu_cart_abandonment_you_may_also_like' %}
{% if forloop.counter <= 4 %}
{% if forloop.counter0|divisibleby:2 %}
<tr>
{% endif %}
<td style="width: 50%; padding: 10px; vertical-align: top;">
<div style="text-align: center;">
{% catalog item %}
<a href="{{ catalog_item.url }}" style="text-decoration: none; color: inherit;">
<img src="{{ catalog_item.featured_image.thumbnail.src }}" style="border: none; width: 100%; max-width: 180px; height: auto;" />
<p style="font-family: 'Poppins', Arial, sans-serif; font-size: 14px; font-style: normal; font-weight: 400; letter-spacing: 0px; line-height: 1.5; color: #222427; text-align: center; margin-top: 10px;">
{{ catalog_item.title }}
</p>
</a>
{% endcatalog %}
</div>
</td>
{% if forloop.counter0|divisibleby:2 and forloop.counter0 != forloop.length %}
</tr>
{% endif %}
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
Debugging & Tips
- Use console.log() liberally in the custom action to inspect variables.
- Make sure the Recommendation ID is active and returns products in Postman/test.
- Test the flow using a dummy profile with known product interactions.
- Ensure your catalog is synced correctly to display product images, titles, and links.