Skip to main content

The Scenario

You have five different ways to describe your product and five distinct audience segments. Which message works best for which audience? You build a message-testing matrix: every message gets evaluated by every persona, producing a quantitative fit score for each cell. The output is a heat map of message-persona fit that tells you exactly which message to use for which audience.
Mavera-only workflow. No survey platforms, no panel vendors, no statistical software. Just Mavera’s Personas and Focus Groups surfaces.

When to Use This

  • Pre-launch messaging finalization — pick the winning variant per audience segment.
  • Channel-specific copy — different channels reach different personas.
  • Website personalization — serve the right headline to the right visitor segment.
  • Sales enablement — give each rep the message that resonates with their territory.

Architecture

Mavera SurfaceRole in Pipeline
Personas (POST /personas)Create 5 distinct audience segments
Focus Groups (POST /focus-groups)Test each message with each persona (25 combinations)

What You Need

RequirementDetails
Mavera API keyStarts with mvra_live_. Get one at Developer Settings.
Python 3.8+ or Node.js 18+requests for Python; native fetch for Node.
Credits~375–650 total. See Credits Estimate.
MAVERA_API_KEY=mvra_live_your_key_here

Step 1 — Define 5 Message Variants

Each message takes a different angle: value, speed, trust, emotion, or technical.
import os, time, json, requests

API_KEY = os.environ["MAVERA_API_KEY"]
BASE = "https://app.mavera.io/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}

MESSAGES = {
    "value": {"label": "Value-First", "headline": "Cut Research Costs by 80%",
     "body": "Traditional audience research costs $15K–$50K per study. Mavera delivers the same insights for a fraction, in minutes."},
    "speed": {"label": "Speed-First", "headline": "From Question to Insight in 10 Minutes",
     "body": "Stop waiting weeks. Mavera's AI personas deliver focus group-quality feedback before your next standup."},
    "trust": {"label": "Trust & Credibility", "headline": "Research You Can Bet Your Campaign On",
     "body": "Personas built on data from millions of real consumers. 87% predictive accuracy. Trusted by 200+ marketing teams."},
    "emotion": {"label": "Emotional", "headline": "Never Launch Blind Again",
     "body": "That sinking feeling when a campaign underperforms? It starts with skipping research. Mavera makes it so fast, there's no excuse."},
    "technical": {"label": "Technical", "headline": "API-First Audience Intelligence",
     "body": "OpenAI-compatible Chat, Focus Groups with 12 question types, Video Analysis, Brand Voice extraction. One API key, unlimited surfaces."},
}

Step 2 — Create 5 Personas

PERSONA_DEFS = [
    {"name": "Startup CMO", "role": "CMO, Series B startup (50 people, $8M ARR)",
     "description": "Built the team from scratch. Budget-conscious but growth-aggressive. Measures everything by pipeline impact.",
     "traits": ["growth-focused", "budget-aware", "hands-on", "speed-oriented"]},
    {"name": "Enterprise Brand Mgr", "role": "Senior Brand Manager, Fortune 500 CPG",
     "description": "Manages $200M brand. Uses Nielsen, Kantar, Ipsos. Needs bulletproof methodology for internal buy-in.",
     "traits": ["process-driven", "methodology-focused", "risk-averse", "data-rigorous"]},
    {"name": "Agency Strategist", "role": "VP Strategy, creative agency with 40 clients",
     "description": "Strategic frameworks across all accounts. Differentiates in pitches. Needs tools that scale across clients.",
     "traits": ["multi-client thinker", "pitch-oriented", "insight-driven", "scalability-focused"]},
    {"name": "Product Marketer", "role": "PMM, mid-stage SaaS company",
     "description": "Owns positioning and launch for 3 product lines. Validates messaging before launch. Enables sales with battlecards.",
     "traits": ["cross-functional", "launch-focused", "sales-enablement-oriented", "messaging-obsessed"]},
    {"name": "Solo Consultant", "role": "Independent marketing consultant, 5–8 clients",
     "description": "One-person operation. Uses research to justify recs. Every dollar comes from margin. Needs tools that make one person look like a team.",
     "traits": ["price-sensitive", "time-constrained", "credibility-seeking", "simplicity-driven"]},
]

def create_persona(p):
    resp = requests.post(f"{BASE}/personas", headers=HEADERS, json={
        "name": p["name"], "role": p["role"], "description": p["description"], "traits": p["traits"]})
    resp.raise_for_status()
    data = resp.json()
    print(f"  Created: {data['name']}{data['id']}")
    return data["id"]

persona_map = {p["name"]: create_persona(p) for p in PERSONA_DEFS}

Step 3 — Run the 5×5 Matrix

Create all 25 Focus Groups, then poll for completion. Mavera processes them concurrently.
def build_questions(msg):
    block = f"**{msg['headline']}**\n\n{msg['body']}"
    return [
        {"question": f"Rate how compelling this is to you (0-10):\n\n{block}", "type": "NPS", "order": 1},
        {"question": "How would you describe this product to a colleague?", "type": "OPEN_ENDED", "order": 2},
        {"question": "Strongest element?", "type": "MULTIPLE_CHOICE",
         "options": ["Headline", "Value proposition", "Proof points", "Tone/voice", "Nothing stood out"], "order": 3},
        {"question": "Biggest gap or weakness?", "type": "OPEN_ENDED", "order": 4},
        {"question": "Would this make you want to learn more?", "type": "MULTIPLE_CHOICE",
         "options": ["Yes — click immediately", "Probably — with right CTA", "Unlikely — not relevant", "No — off-putting"], "order": 5},
    ]

def poll_fg(fg_id, timeout_min=10):
    for _ in range(timeout_min * 6):
        resp = requests.get(f"{BASE}/focus-groups/{fg_id}", headers=HEADERS).json()
        if "error" in resp: raise Exception(resp["error"]["message"])
        if resp["status"] == "COMPLETED": return resp
        time.sleep(10)
    raise TimeoutError(f"Focus Group {fg_id} timed out")

print("Creating 25 Focus Groups...")
matrix_jobs = {}
for mk, msg in MESSAGES.items():
    for pname, pid in persona_map.items():
        key = f"{mk}|{pname}"
        resp = requests.post(f"{BASE}/focus-groups", headers=HEADERS, json={
            "name": f"Matrix: {msg['label']} × {pname}",
            "persona_ids": [pid], "sample_size": 5, "questions": build_questions(msg),
        })
        resp.raise_for_status()
        matrix_jobs[key] = resp.json()["id"]

print(f"{len(matrix_jobs)} Focus Groups created. Polling...")
matrix_results = {k: poll_fg(fid) for k, fid in matrix_jobs.items()}
print("All 25 complete.")
25 Focus Groups take 5–15 minutes total. They run concurrently — the bottleneck is the slowest individual group.

Step 4 — Build and Display the Fit Matrix

def build_and_print_matrix(results):
    msg_keys = list(MESSAGES.keys())
    p_names = list(persona_map.keys())

    matrix = {}
    for mk in msg_keys:
        matrix[mk] = {}
        for pn in p_names:
            cell = results.get(f"{mk}|{pn}", {})
            nps = None
            for qr in cell.get("results", []):
                if qr["type"] == "NPS":
                    nps = qr.get("nps_score")
                    break
            matrix[mk][pn] = nps

    print("\n" + "=" * 90)
    print("MESSAGE-PERSONA FIT MATRIX (NPS Scores)")
    print("=" * 90)

    header = f"{'Message':<18}" + "".join(f" {pn:>14}" for pn in p_names) + f" {'AVG':>8}"
    print(header)
    print("─" * 90)

    best = (None, -100)
    for mk in msg_keys:
        label = MESSAGES[mk]["label"]
        scores = [matrix[mk].get(pn) for pn in p_names if matrix[mk].get(pn) is not None]
        avg = sum(scores) / len(scores) if scores else 0
        row = f"{label:<18}" + "".join(
            f" {matrix[mk].get(pn, 'N/A'):>14}" for pn in p_names) + f" {avg:>7.1f}"
        print(row)
        if avg > best[1]: best = (mk, avg)

    print("─" * 90)
    print(f"\n  Best overall: {MESSAGES[best[0]]['label']} (avg NPS: {best[1]:.1f})")
    for pn in p_names:
        bm = max(msg_keys, key=lambda mk: matrix[mk].get(pn, -100) or -100)
        print(f"  Best for {pn}: {MESSAGES[bm]['label']} (NPS: {matrix[bm].get(pn)})")

    return matrix

matrix = build_and_print_matrix(matrix_results)

Example Output

MESSAGE-PERSONA FIT MATRIX (NPS Scores)
══════════════════════════════════════════════════════════════════════════════════════
Message            Startup CMO  Enterprise BM  Agency Strat  Product Mktr  Solo Conslt      AVG
──────────────────────────────────────────────────────────────────────────────────────
Value-First                 72             35            58            55           82     60.4
Speed-First                 85             28            65            70           75     64.6
Trust & Credibility         40             78            55            48           30     50.2
Emotional                   68             22            70            62           55     55.4
Technical                   55             45            38            80           20     47.6
──────────────────────────────────────────────────────────────────────────────────────

  Best overall: Speed-First (avg NPS: 64.6)
  Best for Startup CMO: Speed-First (NPS: 85)
  Best for Enterprise Brand Mgr: Trust & Credibility (NPS: 78)
  Best for Agency Strategist: Emotional (NPS: 70)
  Best for Product Marketer: Technical (NPS: 80)
  Best for Solo Consultant: Value-First (NPS: 82)

Variations

import csv
with open("matrix.csv", "w", newline="") as f:
    w = csv.writer(f)
    w.writerow(["Message", "Persona", "NPS"])
    for mk in MESSAGES:
        for pn in persona_map:
            w.writerow([MESSAGES[mk]["label"], pn, matrix[mk].get(pn, "N/A")])
Bump sample_size from 5 to 25 for more stable NPS. Credits scale linearly.
new_msg = {"label": "Social Proof", "headline": "Join 500+ Teams", "body": "..."}
for pn, pid in persona_map.items():
    resp = requests.post(f"{BASE}/focus-groups", headers=HEADERS, json={
        "name": f"Matrix: Social Proof × {pn}", "persona_ids": [pid],
        "sample_size": 5, "questions": build_questions(new_msg)})
worst = min(matrix, key=lambda mk: sum((matrix[mk].get(pn) or 0) for pn in persona_map))
gen_resp = requests.post(f"{BASE}/generations", headers=HEADERS, json={
    "app": "ad_copy", "inputs": {"original": json.dumps(MESSAGES[worst]),
        "feedback": "Underperformed across all persona segments. Improve."}}).json()
print(f"Improved: {gen_resp.get('content', '')[:200]}")
import matplotlib.pyplot as plt
import numpy as np
data = np.array([[matrix[mk].get(pn, 0) or 0 for pn in persona_map] for mk in MESSAGES])
fig, ax = plt.subplots(figsize=(10, 5))
ax.imshow(data, cmap="RdYlGn", aspect="auto", vmin=-100, vmax=100)
ax.set_xticks(range(len(persona_map))); ax.set_xticklabels(persona_map.keys(), rotation=45, ha="right")
ax.set_yticks(range(len(MESSAGES))); ax.set_yticklabels([m["label"] for m in MESSAGES.values()])
plt.colorbar(ax.images[0], label="NPS"); plt.tight_layout(); plt.savefig("heatmap.png", dpi=150)

Credits Estimate

OperationTypical CostNotes
Persona creation (×5)0–25One-time; reuse across runs
Focus Groups (×25, 5 respondents, 5 questions)375–625~15–25 per group
Total~375–650
Start with sample_size: 5 for directional signal. Increase to 15–25 only for top-performing cells where you need higher confidence.

What’s Next

Industry Panel Simulation

Deep-dive a single message with 10 buying-committee personas

Persona Debate

Pit opposing personas against each other for pricing insights

Generational Content Testing

Test across age demographics instead of role-based segments

A/B Copy Production

Generate production-ready copy from matrix winners

Persona Selection Guide

Choose the right persona types for your research goal

Credits & Budget

Pre-flight checks and usage tracking