Klaviyo email/SMS marketing - profiles, events, flows, segmentation
56
66%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Advisory
Suggest reviewing before use
Fix and improve this skill with Tessl
tessl review fix ./skills/klaviyo/SKILL.mdFor integrating Klaviyo email/SMS marketing - customer profiles, event tracking, campaigns, flows, and segmentation.
Sources: Klaviyo API Docs | API Reference
| Feature | Benefit |
|---|---|
| E-commerce Native | Built for online stores, deep integrations |
| Event-Based | Trigger flows from any customer action |
| Segmentation | Advanced filtering on behavior + properties |
| Email + SMS | Unified platform for both channels |
| Analytics | Revenue attribution per campaign |
| Type | URL |
|---|---|
| Server-side (Private) | https://a.klaviyo.com/api |
| Client-side (Public) | https://a.klaviyo.com/client |
// Server-side: Private API Key
const headers = {
"Authorization": "Klaviyo-API-Key pk_xxxxxxxxxxxxxxxxxxxxxxxx",
"Content-Type": "application/json",
"revision": "2024-10-15", // API version
};
// Client-side: Public API Key (6 characters)
const publicKey = "XXXXXX"; // Company ID
// Use as query param: ?company_id=XXXXXX| Scope | Access |
|---|---|
| Read-only | View data only |
| Full | Read + write (default) |
| Custom | Specific permissions |
npm install klaviyo-api// lib/klaviyo.ts
import { ApiClient, EventsApi, ProfilesApi, ListsApi } from "klaviyo-api";
const client = new ApiClient();
client.setApiKey(process.env.KLAVIYO_PRIVATE_KEY!);
export const eventsApi = new EventsApi(client);
export const profilesApi = new ProfilesApi(client);
export const listsApi = new ListsApi(client);pip install klaviyo-api# lib/klaviyo.py
from klaviyo_api import KlaviyoAPI
klaviyo = KlaviyoAPI(
api_key=os.environ["KLAVIYO_PRIVATE_KEY"],
max_delay=60,
max_retries=3
)// lib/klaviyo.ts
const KLAVIYO_BASE_URL = "https://a.klaviyo.com/api";
async function klaviyoRequest(
endpoint: string,
method: "GET" | "POST" | "PATCH" | "DELETE" = "GET",
body?: object
) {
const response = await fetch(`${KLAVIYO_BASE_URL}${endpoint}`, {
method,
headers: {
Authorization: `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`,
"Content-Type": "application/json",
revision: "2024-10-15",
},
body: body ? JSON.stringify(body) : undefined,
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Klaviyo API error: ${JSON.stringify(error)}`);
}
return response.json();
}// Upsert profile (create or update)
async function upsertProfile(data: ProfileInput) {
return klaviyoRequest("/profiles", "POST", {
data: {
type: "profile",
attributes: {
email: data.email,
phone_number: data.phone, // E.164 format: +1234567890
first_name: data.firstName,
last_name: data.lastName,
properties: {
// Custom properties
lifetime_value: data.ltv,
plan: data.plan,
signup_source: data.source,
},
location: {
city: data.city,
region: data.state,
country: data.country,
zip: data.zip,
},
},
},
});
}# Python
def upsert_profile(data):
return klaviyo.Profiles.create_or_update_profile({
"data": {
"type": "profile",
"attributes": {
"email": data["email"],
"first_name": data["first_name"],
"last_name": data["last_name"],
"properties": {
"plan": data.get("plan"),
}
}
}
})async function getProfileByEmail(email: string) {
const response = await klaviyoRequest(
`/profiles?filter=equals(email,"${email}")`
);
return response.data[0];
}
async function getProfileById(profileId: string) {
return klaviyoRequest(`/profiles/${profileId}`);
}async function updateProfileProperties(
profileId: string,
properties: Record<string, any>
) {
return klaviyoRequest(`/profiles/${profileId}`, "PATCH", {
data: {
type: "profile",
id: profileId,
attributes: {
properties,
},
},
});
}
// Usage
await updateProfileProperties("profile_id", {
last_purchase_date: new Date().toISOString(),
total_orders: 5,
vip_status: true,
});async function trackEvent(data: EventInput) {
return klaviyoRequest("/events", "POST", {
data: {
type: "event",
attributes: {
profile: {
data: {
type: "profile",
attributes: {
email: data.email,
// or phone_number, or external_id
},
},
},
metric: {
data: {
type: "metric",
attributes: {
name: data.eventName,
},
},
},
properties: data.properties,
value: data.value, // For revenue tracking
unique_id: data.uniqueId, // Deduplication
time: data.timestamp || new Date().toISOString(),
},
},
});
}// Viewed Product
await trackEvent({
email: customer.email,
eventName: "Viewed Product",
properties: {
ProductID: product.id,
ProductName: product.name,
ProductURL: product.url,
ImageURL: product.image,
Price: product.price,
Categories: product.categories,
},
});
// Added to Cart
await trackEvent({
email: customer.email,
eventName: "Added to Cart",
properties: {
ProductID: product.id,
ProductName: product.name,
Quantity: quantity,
Price: product.price,
CartTotal: cart.total,
ItemNames: cart.items.map(i => i.name),
},
value: product.price * quantity,
});
// Started Checkout
await trackEvent({
email: customer.email,
eventName: "Started Checkout",
properties: {
CheckoutURL: checkout.url,
ItemCount: cart.itemCount,
Categories: cart.categories,
ItemNames: cart.items.map(i => i.name),
},
value: cart.total,
});
// Placed Order
await trackEvent({
email: customer.email,
eventName: "Placed Order",
properties: {
OrderId: order.id,
ItemCount: order.itemCount,
Categories: order.categories,
ItemNames: order.items.map(i => i.name),
Items: order.items.map(i => ({
ProductID: i.productId,
ProductName: i.name,
Quantity: i.quantity,
Price: i.price,
ImageURL: i.image,
ProductURL: i.url,
})),
BillingAddress: order.billingAddress,
ShippingAddress: order.shippingAddress,
},
value: order.total,
uniqueId: order.id, // Prevent duplicate orders
});
// Fulfilled Order
await trackEvent({
email: customer.email,
eventName: "Fulfilled Order",
properties: {
OrderId: order.id,
TrackingNumber: fulfillment.trackingNumber,
TrackingURL: fulfillment.trackingUrl,
Carrier: fulfillment.carrier,
},
});
// Cancelled Order
await trackEvent({
email: customer.email,
eventName: "Cancelled Order",
properties: {
OrderId: order.id,
Reason: cancellation.reason,
},
value: -order.total, // Negative value for refunds
});<!-- Add to your site -->
<script async src="https://static.klaviyo.com/onsite/js/klaviyo.js?company_id=XXXXXX"></script>
<script>
// Identify user
klaviyo.identify({
email: "customer@example.com",
first_name: "John",
last_name: "Doe",
});
// Track event
klaviyo.track("Viewed Product", {
ProductID: "prod_123",
ProductName: "Blue T-Shirt",
Price: 29.99,
});
// Track with value
klaviyo.track("Added to Cart", {
ProductID: "prod_123",
ProductName: "Blue T-Shirt",
Price: 29.99,
$value: 29.99, // Revenue tracking
});
</script>async function addToList(listId: string, emails: string[]) {
return klaviyoRequest(`/lists/${listId}/relationships/profiles`, "POST", {
data: emails.map(email => ({
type: "profile",
attributes: { email },
})),
});
}
// By profile ID
async function addProfileToList(listId: string, profileId: string) {
return klaviyoRequest(`/lists/${listId}/relationships/profiles`, "POST", {
data: [{ type: "profile", id: profileId }],
});
}async function removeFromList(listId: string, profileId: string) {
return klaviyoRequest(
`/lists/${listId}/relationships/profiles`,
"DELETE",
{
data: [{ type: "profile", id: profileId }],
}
);
}async function getListMembers(listId: string, cursor?: string) {
const params = new URLSearchParams({
"page[size]": "100",
});
if (cursor) {
params.set("page[cursor]", cursor);
}
return klaviyoRequest(`/lists/${listId}/profiles?${params}`);
}async function createList(name: string) {
return klaviyoRequest("/lists", "POST", {
data: {
type: "list",
attributes: { name },
},
});
}async function getCampaigns(status?: "draft" | "scheduled" | "sent") {
const params = new URLSearchParams();
if (status) {
params.set("filter", `equals(status,"${status}")`);
}
return klaviyoRequest(`/campaigns?${params}`);
}async function getCampaignMetrics(campaignId: string) {
return klaviyoRequest(
`/campaign-recipient-estimations/${campaignId}`,
"GET"
);
}async function getFlows() {
return klaviyoRequest("/flows");
}
async function getFlowById(flowId: string) {
return klaviyoRequest(`/flows/${flowId}`);
}| Flow Type | Trigger Event |
|---|---|
| Welcome Series | Added to List |
| Abandoned Cart | Added to Cart + No Purchase |
| Browse Abandon | Viewed Product + No Cart |
| Post-Purchase | Placed Order |
| Winback | No Order in X Days |
| Review Request | Fulfilled Order |
async function createWebhook(data: WebhookInput) {
return klaviyoRequest("/webhooks", "POST", {
data: {
type: "webhook",
attributes: {
name: data.name,
endpoint_url: data.url,
secret_key: data.secret,
topics: data.topics, // e.g., ["profile.created", "event.created"]
},
},
});
}| Topic | Trigger |
|---|---|
profile.created | New profile created |
profile.updated | Profile properties changed |
profile.merged | Profiles merged |
event.created | New event tracked |
list.member.added | Profile added to list |
list.member.removed | Profile removed from list |
import crypto from "crypto";
function verifyKlaviyoWebhook(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("base64");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Express handler
app.post("/webhooks/klaviyo", (req, res) => {
const signature = req.headers["klaviyo-webhook-signature"] as string;
if (!verifyKlaviyoWebhook(JSON.stringify(req.body), signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: "Invalid signature" });
}
const { type, data } = req.body;
switch (type) {
case "profile.created":
handleNewProfile(data);
break;
case "event.created":
handleNewEvent(data);
break;
}
res.status(200).json({ received: true });
});| Window | Limit |
|---|---|
| Burst | 75 requests/second |
| Steady | 700 requests/minute |
async function klaviyoRequestWithRetry(
endpoint: string,
method: "GET" | "POST" | "PATCH" | "DELETE" = "GET",
body?: object,
retries = 3
): Promise<any> {
for (let attempt = 0; attempt < retries; attempt++) {
const response = await fetch(`${KLAVIYO_BASE_URL}${endpoint}`, {
method,
headers: {
Authorization: `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`,
"Content-Type": "application/json",
revision: "2024-10-15",
},
body: body ? JSON.stringify(body) : undefined,
});
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "5");
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
if (!response.ok) {
throw new Error(`Klaviyo error: ${response.status}`);
}
return response.json();
}
throw new Error("Max retries exceeded");
}async function getAllProfiles() {
const profiles = [];
let cursor: string | undefined;
do {
const params = new URLSearchParams({ "page[size]": "100" });
if (cursor) {
params.set("page[cursor]", cursor);
}
const response = await klaviyoRequest(`/profiles?${params}`);
profiles.push(...response.data);
cursor = response.links?.next
? new URL(response.links.next).searchParams.get("page[cursor]")
: undefined;
} while (cursor);
return profiles;
}// Filter by date
const recentEvents = await klaviyoRequest(
`/events?filter=greater-than(datetime,2024-01-01T00:00:00Z)`
);
// Filter by property
const vipProfiles = await klaviyoRequest(
`/profiles?filter=equals(properties.vip_status,true)`
);
// Multiple filters (AND)
const filtered = await klaviyoRequest(
`/profiles?filter=and(equals(properties.plan,"pro"),greater-than(properties.ltv,1000))`
);
// Sorting
const sorted = await klaviyoRequest(
`/profiles?sort=-created` // Descending by created date
);
// Sparse fieldsets (only return specific fields)
const sparse = await klaviyoRequest(
`/profiles?fields[profile]=email,first_name,properties`
);// After order is placed
async function syncOrderToKlaviyo(order: Order) {
// 1. Upsert customer profile
await upsertProfile({
email: order.customerEmail,
firstName: order.customerFirstName,
lastName: order.customerLastName,
phone: order.customerPhone,
});
// 2. Update lifetime metrics
await updateProfileProperties(
await getProfileIdByEmail(order.customerEmail),
{
last_order_date: new Date().toISOString(),
total_orders: order.customerOrderCount,
lifetime_value: order.customerLifetimeValue,
}
);
// 3. Track order event
await trackEvent({
email: order.customerEmail,
eventName: "Placed Order",
properties: {
OrderId: order.id,
Items: order.items,
// ... other properties
},
value: order.total,
uniqueId: order.id,
});
}// When subscription changes
async function syncSubscriptionStatus(user: User, status: string) {
await updateProfileProperties(user.klaviyoProfileId, {
subscription_status: status,
subscription_plan: user.plan,
subscription_updated_at: new Date().toISOString(),
});
await trackEvent({
email: user.email,
eventName: `Subscription ${status}`,
properties: {
plan: user.plan,
mrr: user.mrr,
},
value: status === "cancelled" ? -user.mrr : user.mrr,
});
}# .env
KLAVIYO_PRIVATE_KEY=pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
KLAVIYO_PUBLIC_KEY=XXXXXX
KLAVIYO_WEBHOOK_SECRET=your_webhook_secretAdd to credentials.md:
'KLAVIYO_PRIVATE_KEY': r'pk_[a-f0-9]{32}',
'KLAVIYO_PUBLIC_KEY': r'[A-Z0-9]{6}',bb6195f
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.