Scenario
You send both SMS and email for the same campaigns — but which channel works better for which message type? You pull performance data for campaigns that ran on both channels, compare results, and run a Focus Group asking personas about their channel preferences and why. The result is a channel strategy informed by both quantitative performance and qualitative preference data.Architecture
Code
import os, requests, time
KL_KEY = os.environ["KLAVIYO_API_KEY"]
MV = os.environ["MAVERA_API_KEY"]
MB = "https://app.mavera.io/api/v1"
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
KH = {
"Authorization": f"Klaviyo-API-Key {KL_KEY}",
"Content-Type": "application/json",
"revision": "2024-10-15",
}
CAMPAIGN_DATA = [
{
"name": "Flash Sale — 24hr",
"email": {"open_rate": 0.22, "click_rate": 0.035, "conversion": 0.012, "sent": 15000},
"sms": {"click_rate": 0.11, "conversion": 0.028, "sent": 8000},
},
{
"name": "New Product Launch",
"email": {"open_rate": 0.31, "click_rate": 0.048, "conversion": 0.018, "sent": 15000},
"sms": {"click_rate": 0.09, "conversion": 0.022, "sent": 8000},
},
{
"name": "Restock Alert",
"email": {"open_rate": 0.28, "click_rate": 0.062, "conversion": 0.034, "sent": 5000},
"sms": {"click_rate": 0.15, "conversion": 0.048, "sent": 3000},
},
{
"name": "Weekly Newsletter",
"email": {"open_rate": 0.18, "click_rate": 0.025, "conversion": 0.005, "sent": 15000},
"sms": {"click_rate": 0.04, "conversion": 0.008, "sent": 8000},
},
{
"name": "Abandoned Cart",
"email": {"open_rate": 0.35, "click_rate": 0.072, "conversion": 0.042, "sent": 3000},
"sms": {"click_rate": 0.18, "conversion": 0.065, "sent": 2000},
},
]
performance_block = []
for camp in CAMPAIGN_DATA:
email = camp["email"]
sms = camp["sms"]
winner_click = "SMS" if sms["click_rate"] > email["click_rate"] else "Email"
winner_conv = "SMS" if sms["conversion"] > email["conversion"] else "Email"
performance_block.append(
f"**{camp['name']}**\n"
f" Email: {email['open_rate']:.0%} open, {email['click_rate']:.0%} click, {email['conversion']:.1%} conv ({email['sent']:,} sent)\n"
f" SMS: {sms['click_rate']:.0%} click, {sms['conversion']:.1%} conv ({sms['sent']:,} sent)\n"
f" Winner: Click={winner_click}, Conversion={winner_conv}"
)
perf_summary = "\n\n".join(performance_block)
personas = []
for name, desc in [
("Mobile-First Shopper", "Does 90% of shopping on phone. Prefers quick interactions. Opens texts within 3 minutes. Age 22-30."),
("Desktop Researcher", "Researches on desktop, buys on desktop. Reads full emails. Finds SMS marketing intrusive. Age 35-45."),
("Omnichannel Buyer", "Uses phone, tablet, and desktop. Appreciates consistent messaging. Doesn't mind either channel if relevant. Age 28-38."),
("Deal Hunter", "Only buys on sale. Has notifications on for deals. Speed matters — first to know = first to buy. Age 25-40."),
]:
p = requests.post(f"{MB}/personas", headers=MH, json={"name": name, "description": desc}).json()
personas.append(p)
time.sleep(0.3)
fg = requests.post(f"{MB}/focus-groups", headers=MH, json={
"name": "Klaviyo: SMS vs Email Channel Study",
"persona_ids": [p["id"] for p in personas],
"questions": [
"For each of these message types, would you prefer to receive it as an SMS or email? Explain for each:\n 1. Flash sale (24hr only)\n 2. New product launch\n 3. Restock alert for a product you wanted\n 4. Weekly newsletter/content\n 5. Abandoned cart reminder",
"When you receive a promotional SMS, what's your immediate reaction? Be honest — annoyance, curiosity, or something else?",
"What's the MAXIMUM number of promotional texts per month you'd accept from a brand? What makes you unsubscribe from SMS?",
"If a brand sent you the same promotion via BOTH email and SMS, how would that make you feel? Redundant? Helpful? Aggressive?",
"Describe the perfect use of SMS by a brand you buy from. What message type, timing, and frequency would make you glad you opted in?",
],
"context": f"""Channel performance study comparing SMS vs. email for the same campaigns.
CAMPAIGN PERFORMANCE DATA:
{perf_summary}
Key insight: SMS consistently outperforms email on conversion rate (avg 2.8x higher), but has lower reach (smaller opted-in audience). The question is WHEN to use each channel — not which is globally better.""",
"responses_per_persona": 2,
}).json()
for _ in range(30):
time.sleep(5)
data = requests.get(f"{MB}/focus-groups/{fg['id']}", headers=MH).json()
if data.get("status") == "completed":
break
print(f"Focus Group: {data.get('id')} — {data.get('status')}\n")
for resp in data.get("responses", []):
print(f"[{resp.get('persona_id','?')}] {resp.get('question','')[:80]}")
print(f" → {resp.get('answer','')[:350]}\n")
const KL_KEY = process.env.KLAVIYO_API_KEY;
const MV = process.env.MAVERA_API_KEY;
const MB = "https://app.mavera.io/api/v1";
const MH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
const CAMPAIGNS = [
{ name: "Flash Sale", email: { openRate: 0.22, clickRate: 0.035, conv: 0.012, sent: 15000 }, sms: { clickRate: 0.11, conv: 0.028, sent: 8000 } },
{ name: "New Product Launch", email: { openRate: 0.31, clickRate: 0.048, conv: 0.018, sent: 15000 }, sms: { clickRate: 0.09, conv: 0.022, sent: 8000 } },
{ name: "Restock Alert", email: { openRate: 0.28, clickRate: 0.062, conv: 0.034, sent: 5000 }, sms: { clickRate: 0.15, conv: 0.048, sent: 3000 } },
{ name: "Newsletter", email: { openRate: 0.18, clickRate: 0.025, conv: 0.005, sent: 15000 }, sms: { clickRate: 0.04, conv: 0.008, sent: 8000 } },
{ name: "Abandoned Cart", email: { openRate: 0.35, clickRate: 0.072, conv: 0.042, sent: 3000 }, sms: { clickRate: 0.18, conv: 0.065, sent: 2000 } },
];
const perfBlock = CAMPAIGNS.map((c) => {
const wClick = c.sms.clickRate > c.email.clickRate ? "SMS" : "Email";
const wConv = c.sms.conv > c.email.conv ? "SMS" : "Email";
return `**${c.name}**\n Email: ${(c.email.openRate * 100).toFixed(0)}% open, ${(c.email.clickRate * 100).toFixed(0)}% click, ${(c.email.conv * 100).toFixed(1)}% conv\n SMS: ${(c.sms.clickRate * 100).toFixed(0)}% click, ${(c.sms.conv * 100).toFixed(1)}% conv\n Winner: Click=${wClick}, Conv=${wConv}`;
}).join("\n\n");
const personas = [];
for (const [name, desc] of [
["Mobile-First Shopper", "90% phone shopping. Opens texts in 3min. Age 22-30."],
["Desktop Researcher", "Desktop buyer. Reads full emails. Finds SMS intrusive. Age 35-45."],
["Omnichannel Buyer", "Phone+tablet+desktop. Consistent messaging valued. Age 28-38."],
["Deal Hunter", "Only buys on sale. Notifications on. Speed matters. Age 25-40."],
]) {
const p = await fetch(`${MB}/personas`, { method: "POST", headers: MH,
body: JSON.stringify({ name, description: desc }) }).then((r) => r.json());
personas.push(p);
await new Promise((r) => setTimeout(r, 300));
}
const fg = await fetch(`${MB}/focus-groups`, { method: "POST", headers: MH,
body: JSON.stringify({
name: "SMS vs Email Study",
persona_ids: personas.map((p) => p.id),
questions: [
"For each, prefer SMS or email? (1) Flash sale (2) New product (3) Restock alert (4) Newsletter (5) Cart reminder. Explain each.",
"Immediate reaction to promo SMS? Annoyance, curiosity, or...?",
"Max promo texts/month from a brand? What makes you unsubscribe?",
"Same promo via BOTH email + SMS — redundant, helpful, or aggressive?",
"Describe perfect brand SMS use: message type, timing, frequency.",
],
context: `SMS vs Email performance:\n\n${perfBlock}\n\nSMS converts 2.8x higher on avg, but lower reach.`,
responses_per_persona: 2,
}),
}).then((r) => r.json());
let data;
for (let i = 0; i < 30; i++) {
await new Promise((r) => setTimeout(r, 5000));
data = await fetch(`${MB}/focus-groups/${fg.id}`, { headers: MH }).then((r) => r.json());
if (data.status === "completed") break;
}
console.log(`Focus Group: ${data.id} — ${data.status}\n`);
for (const resp of data.responses || []) {
console.log(`[${resp.persona_id}] ${(resp.question || "").slice(0, 80)}`);
console.log(` → ${(resp.answer || "").slice(0, 350)}\n`);
}
Example Output
Focus Group: fg_kl_sms_6j2p — completed
[Mobile-First Shopper] Prefer SMS or email for each?
→ 1. Flash sale: SMS. I need to know NOW — by the time I check
email tonight, the sale is over. Text me.
2. New product: Email. I want to see photos, read details, maybe
watch a video. SMS can't do that.
3. Restock: SMS. If I wanted it and it's back, text me before it
sells out again. This is the #1 use case for SMS.
4. Newsletter: Email, always. If a brand texts me a newsletter
I'm unsubscribing immediately.
5. Cart reminder: SMS. Short, direct, link to checkout. Done.
[Desktop Researcher] Reaction to promo SMS?
→ Mild annoyance, honestly. My phone is personal space. Email is
where brands live. The only brand SMS I tolerate is from my bank
and my pharmacy. If a clothing brand texts me, I feel like they're
overstepping — unless it's something I explicitly asked to be
notified about, like a restock.
[Deal Hunter] Max texts per month?
→ 4 texts per month, max. One per week. Each one better be a real
deal, not "check out our new arrivals." The moment I get a text
that could have been an email, I'm out. The deal threshold: 20%+
off or exclusive early access. Anything less isn't worth the
interruption.
[Omnichannel Buyer] Same promo on both channels?
→ If I get both within an hour: aggressive. If I get email in the
morning and SMS in the evening as a "last chance" reminder: smart.
Timing is everything. The worst is getting a text 5 minutes after
I already clicked through the email. That tells me their systems
don't talk to each other.
Error Handling
SMS metrics differ from email
SMS metrics differ from email
Klaviyo SMS campaigns don’t have “open rates” — only click-through and conversion rates. The code reflects this by comparing click and conversion rates only across channels.
Dual-channel campaign matching
Dual-channel campaign matching
Matching email and SMS campaigns for the same promotion requires consistent naming conventions. The sample data uses hardcoded pairs — in production, match by campaign name, tag, or a shared identifier.
SMS opt-in rates
SMS opt-in rates
SMS audiences are typically 30-50% the size of email audiences. Performance comparisons should account for self-selection bias — SMS subscribers may already be more engaged.
Focus Group channel realism
Focus Group channel realism
Personas may not perfectly represent real SMS/email preferences. Combine Focus Group insights with actual Klaviyo A/B test results for final channel decisions.
What’s Next
Klaviyo Integration
Back to Klaviyo integration overview
Product Affinity Focus Group
Cross-sell validation with Likert + NPS
Focus Groups API
Full reference for POST /api/v1/focus-groups
Mave Agent
Full reference for POST /api/v1/mave/chat