> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mavera.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Message Testing Matrix

> Test 5 message variants × 5 personas = 25 Focus Group combinations to find optimal message-persona fit

## 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.

<Info>
  **Mavera-only workflow.** No survey platforms, no panel vendors, no statistical software. Just Mavera's Personas and Focus Groups surfaces.
</Info>

***

## 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

```mermaid theme={"dark"}
flowchart LR
    A["Define 5 Messages"] --> B["Define 5 Personas"]
    B --> C["Run 25 Focus Groups"]
    C --> D["Build Fit Matrix"]
    D --> E["Report"]
```

| Mavera Surface                          | Role 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

| Requirement                        | Details                                                                                              |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------- |
| **Mavera API key**                 | Starts with `mvra_live_`. Get one at [Developer Settings](https://app.mavera.io/settings/developer). |
| **Python 3.8+** or **Node.js 18+** | `requests` for Python; native `fetch` for Node.                                                      |
| **Credits**                        | \~375–650 total. See [Credits Estimate](#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.

<CodeGroup>
  ```python Python theme={"dark"}
  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."},
  }
  ```

  ```javascript JavaScript theme={"dark"}
  const API_KEY = process.env.MAVERA_API_KEY;
  const BASE = "https://app.mavera.io/api/v1";
  const HEADERS = { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" };

  const MESSAGES = {
    value: { label: "Value-First", headline: "Cut Research Costs by 80%",
      body: "Traditional 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. 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. 200+ teams." },
    emotion: { label: "Emotional", headline: "Never Launch Blind Again",
      body: "That sinking feeling when a campaign underperforms? 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, Video Analysis, Brand Voice. One API key, unlimited surfaces." },
  };
  ```
</CodeGroup>

***

## Step 2 — Create 5 Personas

<CodeGroup>
  ```python Python theme={"dark"}
  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}
  ```

  ```javascript JavaScript theme={"dark"}
  const PERSONA_DEFS = [
    { name: "Startup CMO", role: "CMO, Series B startup",
      description: "Built team from scratch. Budget-conscious but growth-aggressive. Pipeline-focused.",
      traits: ["growth-focused", "budget-aware", "hands-on", "speed-oriented"] },
    { name: "Enterprise Brand Mgr", role: "Senior Brand Manager, Fortune 500 CPG",
      description: "$200M brand. Uses Nielsen, Kantar. Needs bulletproof methodology.",
      traits: ["process-driven", "methodology-focused", "risk-averse", "data-rigorous"] },
    { name: "Agency Strategist", role: "VP Strategy, creative agency",
      description: "Frameworks across 40 accounts. Differentiates in pitches. Needs scale.",
      traits: ["multi-client thinker", "pitch-oriented", "insight-driven", "scalability-focused"] },
    { name: "Product Marketer", role: "PMM, mid-stage SaaS",
      description: "Owns positioning for 3 product lines. Validates before launch. Enables sales.",
      traits: ["cross-functional", "launch-focused", "sales-enablement-oriented", "messaging-obsessed"] },
    { name: "Solo Consultant", role: "Independent consultant, 5–8 clients",
      description: "One-person op. Research justifies recs. Every dollar from margin.",
      traits: ["price-sensitive", "time-constrained", "credibility-seeking", "simplicity-driven"] },
  ];

  async function createPersona(p) {
    const resp = await fetch(`${BASE}/personas`, { method: "POST", headers: HEADERS,
      body: JSON.stringify({ name: p.name, role: p.role, description: p.description, traits: p.traits }) });
    const data = await resp.json();
    if (data.error) throw new Error(data.error.message);
    return data.id;
  }
  const personaMap = {};
  for (const p of PERSONA_DEFS) personaMap[p.name] = await createPersona(p);
  ```
</CodeGroup>

***

## Step 3 — Run the 5×5 Matrix

Create all 25 Focus Groups, then poll for completion. Mavera processes them concurrently.

<CodeGroup>
  ```python Python theme={"dark"}
  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.")
  ```

  ```javascript JavaScript theme={"dark"}
  function buildQuestions(msg) {
    const block = `**${msg.headline}**\n\n${msg.body}`;
    return [
      { question: `Rate how compelling (0-10):\n\n${block}`, type: "NPS", order: 1 },
      { question: "How would you describe this 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", "No — off-putting"], order: 5 },
    ];
  }

  async function pollFG(fgId, timeoutMin = 10) {
    for (let i = 0; i < timeoutMin * 6; i++) {
      const resp = await fetch(`${BASE}/focus-groups/${fgId}`, { headers: HEADERS }).then((r) => r.json());
      if (resp.error) throw new Error(resp.error.message);
      if (resp.status === "COMPLETED") return resp;
      await new Promise((r) => setTimeout(r, 10000));
    }
    throw new Error("Timed out");
  }

  const matrixJobs = {};
  for (const [mk, msg] of Object.entries(MESSAGES)) {
    for (const [pname, pid] of Object.entries(personaMap)) {
      const key = `${mk}|${pname}`;
      const resp = await fetch(`${BASE}/focus-groups`, { method: "POST", headers: HEADERS,
        body: JSON.stringify({ name: `Matrix: ${msg.label} × ${pname}`, persona_ids: [pid],
          sample_size: 5, questions: buildQuestions(msg) }) });
      matrixJobs[key] = (await resp.json()).id;
    }
  }
  const matrixResults = {};
  for (const [k, fid] of Object.entries(matrixJobs)) matrixResults[k] = await pollFG(fid);
  ```
</CodeGroup>

<Warning>
  25 Focus Groups take 5–15 minutes total. They run concurrently — the bottleneck is the slowest individual group.
</Warning>

***

## Step 4 — Build and Display the Fit Matrix

<CodeGroup>
  ```python Python theme={"dark"}
  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)
  ```

  ```javascript JavaScript theme={"dark"}
  function buildAndPrintMatrix(results) {
    const msgKeys = Object.keys(MESSAGES);
    const pNames = Object.keys(personaMap);
    const matrix = {};
    for (const mk of msgKeys) {
      matrix[mk] = {};
      for (const pn of pNames) {
        const cell = results[`${mk}|${pn}`] || {};
        matrix[mk][pn] = (cell.results || []).find((q) => q.type === "NPS")?.nps_score ?? null;
      }
    }
    console.log("\nMESSAGE-PERSONA FIT MATRIX (NPS)");
    console.log("─".repeat(90));
    for (const mk of msgKeys) {
      const scores = pNames.map((pn) => matrix[mk][pn]).filter((s) => s !== null);
      const avg = scores.length ? (scores.reduce((a, b) => a + b) / scores.length).toFixed(1) : "N/A";
      console.log(`  ${MESSAGES[mk].label.padEnd(18)} ${pNames.map((pn) => String(matrix[mk][pn] ?? "N/A").padStart(14)).join("")} ${String(avg).padStart(8)}`);
    }
    for (const pn of pNames) {
      const best = msgKeys.reduce((b, mk) => (matrix[mk][pn] || -100) > (matrix[b][pn] || -100) ? mk : b);
      console.log(`  Best for ${pn}: ${MESSAGES[best].label} (NPS: ${matrix[best][pn]})`);
    }
    return matrix;
  }
  buildAndPrintMatrix(matrixResults);
  ```
</CodeGroup>

***

## 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

<AccordionGroup>
  <Accordion title="Export as CSV">
    ```python theme={"dark"}
    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")])
    ```
  </Accordion>

  <Accordion title="Increase sample size for confidence">
    Bump `sample_size` from 5 to 25 for more stable NPS. Credits scale linearly.
  </Accordion>

  <Accordion title="Add a 6th message mid-study">
    ```python theme={"dark"}
    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)})
    ```
  </Accordion>

  <Accordion title="Auto-improve the weakest message with Generate">
    ```python theme={"dark"}
    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]}")
    ```
  </Accordion>

  <Accordion title="Visualize as a heat map">
    ```python theme={"dark"}
    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)
    ```
  </Accordion>
</AccordionGroup>

***

## Credits Estimate

| Operation                                      | Typical Cost  | Notes                       |
| ---------------------------------------------- | ------------- | --------------------------- |
| Persona creation (×5)                          | 0–25          | One-time; reuse across runs |
| Focus Groups (×25, 5 respondents, 5 questions) | 375–625       | \~15–25 per group           |
| **Total**                                      | **\~375–650** |                             |

<Tip>
  Start with `sample_size: 5` for directional signal. Increase to 15–25 only for top-performing cells where you need higher confidence.
</Tip>

***

## What's Next

<CardGroup cols={2}>
  <Card title="Industry Panel Simulation" icon="users" href="/playbooks/industry-panel-simulation">
    Deep-dive a single message with 10 buying-committee personas
  </Card>

  <Card title="Persona Debate" icon="scale-balanced" href="/playbooks/persona-debate">
    Pit opposing personas against each other for pricing insights
  </Card>

  <Card title="Generational Content Testing" icon="people-group" href="/playbooks/generational-content-testing">
    Test across age demographics instead of role-based segments
  </Card>

  <Card title="A/B Copy Production" icon="clone" href="/playbooks/ab-copy-production">
    Generate production-ready copy from matrix winners
  </Card>

  <Card title="Persona Selection Guide" icon="user" href="/cookbooks/persona-selection">
    Choose the right persona types for your research goal
  </Card>

  <Card title="Credits & Budget" icon="coins" href="/cookbooks/credits-budget-alerts">
    Pre-flight checks and usage tracking
  </Card>
</CardGroup>
