Scenario
Your product has 10 features, but only 3 are widely adopted. You pull feature usage metrics from Mixpanel, identify underused features with high retention correlation, then run a Focus Group asking “Are you aware of ?” (dichotomous yes/no + open-ended follow-up) and generate feature awareness campaigns for the gaps.Architecture
Code
import os, requests, time, json
MP_SA = os.environ["MIXPANEL_SERVICE_ACCOUNT"]
MP_SECRET = os.environ["MIXPANEL_SECRET"]
MP_PROJECT = os.environ["MIXPANEL_PROJECT_ID"]
MV = os.environ["MAVERA_API_KEY"]
MB = "https://app.mavera.io/api/v1"
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
FEATURES = {
"Dashboard View": "Main dashboard with analytics overview",
"Report Create": "Custom report builder",
"Alert Set": "Automated alerts and notifications",
"API Call": "Programmatic API access",
"Export Data": "CSV/Excel data export",
"Team Invite": "Add team members to workspace",
"Integration Connect": "Connect third-party tools",
"Template Use": "Pre-built report templates",
"Scheduled Report": "Automated recurring reports",
"Custom Widget": "Build custom dashboard widgets",
}
r = requests.post(
"https://mixpanel.com/api/query/insights",
auth=(MP_SA, MP_SECRET),
json={
"project_id": MP_PROJECT,
"bookmark_id": None,
"params": {
"type": "unique",
"events": [{"event": name} for name in FEATURES.keys()],
"from_date": "2026-02-15",
"to_date": "2026-03-17",
},
},
)
if r.status_code == 429:
time.sleep(60)
r.raise_for_status()
insights = r.json()
total_users_r = requests.post(
"https://mixpanel.com/api/query/insights",
auth=(MP_SA, MP_SECRET),
json={
"project_id": MP_PROJECT,
"params": {"type": "unique", "events": [{"event": "Login"}],
"from_date": "2026-02-15", "to_date": "2026-03-17"},
},
)
total_users_data = total_users_r.json()
total_users = 5000
feature_adoption = []
series = insights.get("series", {})
for feature_name, desc in FEATURES.items():
values = series.get(feature_name, {})
unique_users = sum(values.values()) if isinstance(values, dict) else 0
adoption_rate = unique_users / max(total_users, 1)
feature_adoption.append({
"feature": feature_name,
"description": desc,
"unique_users": unique_users,
"adoption_rate": adoption_rate,
})
feature_adoption.sort(key=lambda f: f["adoption_rate"])
underused = [f for f in feature_adoption if f["adoption_rate"] < 0.3]
well_adopted = [f for f in feature_adoption if f["adoption_rate"] >= 0.5]
adoption_table = "\n".join(
f"- {f['feature']}: {f['unique_users']} users ({f['adoption_rate']:.0%} adoption) — {f['description']}"
for f in feature_adoption
)
PERSONA_IDS = os.environ.get("FEATURE_PERSONA_IDS", "").split(",")
if not PERSONA_IDS[0]:
for name, desc in [
("Active Free User", "Uses the product daily on a free plan. Hasn't upgraded because they don't know what they're missing."),
("New Pro User", "Recently upgraded. Using 2-3 features but hasn't explored the full platform."),
("Power User Champion", "Uses most features. Internal advocate. Wants to get more from the tool."),
]:
p = requests.post(f"{MB}/personas", headers=MH, json={"name": name, "description": desc}).json()
PERSONA_IDS.append(p["id"])
time.sleep(0.3)
underused_list = ", ".join(f["feature"] for f in underused[:5])
fg = requests.post(f"{MB}/focus-groups", headers=MH, json={
"name": "Feature Awareness & Adoption",
"persona_ids": [pid for pid in PERSONA_IDS if pid],
"questions": [
f"Are you aware of these features: {underused_list}? For each, answer Yes or No, then explain why you do or don't use it.",
"If you discovered a feature that could save you 2 hours per week, how would you want to learn about it? (in-app tooltip, email, video tutorial, peer recommendation, or other?)",
f"Rank these underused features by how useful they WOULD be if you knew about them: {underused_list}. Explain your #1.",
"What's the biggest gap in this product — a feature you wish existed?",
],
"context": f"""Feature adoption data for our product (last 30 days):
{adoption_table}
Well-adopted (>50%): {', '.join(f['feature'] for f in well_adopted)}
Underused (<30%): {underused_list}
Total active users: ~{total_users}""",
"responses_per_persona": 2,
}).json()
for _ in range(24):
time.sleep(5)
data = requests.get(f"{MB}/focus-groups/{fg['id']}", headers=MH).json()
if data.get("status") == "completed":
break
print("--- Focus Group Results ---\n")
for resp in data.get("responses", []):
print(f"[{resp.get('persona_id','?')}] {resp.get('question','')[:70]}")
print(f" → {resp.get('answer','')[:250]}\n")
print("\n--- Generating Feature Campaigns ---\n")
for feature in underused[:3]:
gen = requests.post(f"{MB}/generations", headers=MH, json={
"prompt": (
f"Create a feature awareness campaign for '{feature['feature']}' ({feature['description']}). "
f"Current adoption: {feature['adoption_rate']:.0%} of users. "
f"Generate:\n"
f"1. In-app banner copy (max 20 words + CTA)\n"
f"2. Email subject line + 50-word body\n"
f"3. Tooltip text (max 15 words)\n"
f"4. 30-second video script outline\n"
f"Focus on the user benefit, not the feature name."
),
}).json()
print(f"Feature: {feature['feature']} ({feature['adoption_rate']:.0%} adoption)")
print(gen.get("output", gen.get("content", ""))[:500])
print()
const MP_SA = process.env.MIXPANEL_SERVICE_ACCOUNT;
const MP_SECRET = process.env.MIXPANEL_SECRET;
const MP_PROJECT = process.env.MIXPANEL_PROJECT_ID;
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 mpAuth = "Basic " + Buffer.from(`${MP_SA}:${MP_SECRET}`).toString("base64");
const FEATURES = {
"Dashboard View": "Main dashboard with analytics overview",
"Report Create": "Custom report builder",
"Alert Set": "Automated alerts and notifications",
"API Call": "Programmatic API access",
"Export Data": "CSV/Excel data export",
"Team Invite": "Add team members",
"Integration Connect": "Connect third-party tools",
"Template Use": "Pre-built report templates",
"Scheduled Report": "Automated recurring reports",
"Custom Widget": "Custom dashboard widgets",
};
const insights = await fetch("https://mixpanel.com/api/query/insights", {
method: "POST",
headers: { Authorization: mpAuth, "Content-Type": "application/json" },
body: JSON.stringify({
project_id: MP_PROJECT,
params: {
type: "unique",
events: Object.keys(FEATURES).map((event) => ({ event })),
from_date: "2026-02-15", to_date: "2026-03-17",
},
}),
}).then((r) => r.json());
const totalUsers = 5000;
const series = insights.series || {};
const featureAdoption = Object.entries(FEATURES).map(([name, desc]) => {
const vals = series[name] || {};
const uniqueUsers = Object.values(vals).reduce((s, v) => s + v, 0);
return { feature: name, description: desc, uniqueUsers, adoptionRate: uniqueUsers / totalUsers };
}).sort((a, b) => a.adoptionRate - b.adoptionRate);
const underused = featureAdoption.filter((f) => f.adoptionRate < 0.3);
const adoptionTable = featureAdoption.map((f) =>
`- ${f.feature}: ${f.uniqueUsers} users (${(f.adoptionRate * 100).toFixed(0)}%) — ${f.description}`
).join("\n");
const personaIds = [];
for (const [name, desc] of [
["Active Free User", "Daily free user. Doesn't know what they're missing."],
["New Pro User", "Recently upgraded. Using 2-3 features."],
["Power User Champion", "Uses most features. Internal advocate."],
]) {
const p = await fetch(`${MB}/personas`, { method: "POST", headers: MH,
body: JSON.stringify({ name, description: desc }) }).then((r) => r.json());
personaIds.push(p.id);
await new Promise((r) => setTimeout(r, 300));
}
const underusedList = underused.slice(0, 5).map((f) => f.feature).join(", ");
const fg = await fetch(`${MB}/focus-groups`, { method: "POST", headers: MH,
body: JSON.stringify({
name: "Feature Awareness",
persona_ids: personaIds,
questions: [
`Are you aware of: ${underusedList}? Yes/No for each, then explain.`,
"How would you want to learn about a feature saving 2hr/week?",
`Rank by usefulness: ${underusedList}. Explain #1.`,
"What feature gap exists in this product?",
],
context: `Feature adoption (30d):\n${adoptionTable}\n\nTotal: ~${totalUsers}`,
responses_per_persona: 2,
}),
}).then((r) => r.json());
let data;
for (let i = 0; i < 24; 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 Results ---\n");
for (const resp of data.responses || []) {
console.log(`[${resp.persona_id}] ${(resp.question || "").slice(0, 70)}`);
console.log(` → ${(resp.answer || "").slice(0, 250)}\n`);
}
console.log("\n--- Feature Campaigns ---\n");
for (const feature of underused.slice(0, 3)) {
const gen = await fetch(`${MB}/generations`, { method: "POST", headers: MH,
body: JSON.stringify({
prompt: `Feature awareness campaign for '${feature.feature}' (${feature.description}). Adoption: ${(feature.adoptionRate * 100).toFixed(0)}%. Generate: 1) In-app banner 2) Email subject+body 3) Tooltip 4) 30s video script.`,
}),
}).then((r) => r.json());
console.log(`${feature.feature} (${(feature.adoptionRate * 100).toFixed(0)}% adoption)`);
console.log((gen.output || gen.content || "").slice(0, 500));
console.log();
}
Example Output
--- Focus Group Results ---
[Active Free User] Are you aware of: Alert Set, API Call, Custom Widget?
→ Alert Set: No — I didn't know I could get notified. I check the
dashboard manually every morning. If I could get a Slack ping when
a metric drops, that changes my workflow entirely.
API Call: No — I'm not a developer.
Custom Widget: No — what does it do?
[New Pro User] Rank by usefulness
→ #1: Scheduled Report. I build the same report every Monday. If I
could automate it, I'd save 45 minutes/week and look more organized
to my manager. That alone justifies the Pro upgrade.
--- Feature Campaigns ---
Scheduled Report (12% adoption)
1. Banner: "Stop rebuilding Monday's report. Automate it in 30 seconds." [Set Up →]
2. Email: Subject: "You rebuilt this report 4 times last month"
Body: We noticed you create similar reports weekly. Scheduled Reports
delivers them to your inbox automatically — same filters, same format,
zero effort. Set it up in 30 seconds.
3. Tooltip: "Run this report automatically every week"
4. Video: Open on frustrated user rebuilding report → show 3-click
setup → reveal report landing in inbox → "45 minutes back, every week."
Error Handling
Insights API query format
Insights API query format
The Insights API requires specific
params structure with type (unique, general, average), events, and date range. Test queries in Mixpanel’s Insights UI first, then export the API call from the UI’s “API” button.Feature event naming
Feature event naming
Features must correspond to tracked Mixpanel events. If your events are named differently (e.g.
report.created instead of Report Create), update the FEATURES dict to match your event names exactly.Focus Group + Generate sequencing
Focus Group + Generate sequencing
This job chains two Mavera calls — Focus Group then Generate. Budget 2–4 minutes total. The Focus Group must complete before generating campaigns, since the results inform messaging strategy.
What’s Next
Mixpanel Integration
Back to Mixpanel integration overview
Event-Based Persona Enrichment
Enrich personas with raw event patterns
Focus Groups API
Full reference for POST /api/v1/focus-groups
Generate API
Full reference for POST /api/v1/generations