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

# Sales Channel → Messaging Playbook

### Scenario

Your `#sales` or `#deals` channel is full of real objections, buyer quotes, competitive mentions, and win patterns — all in the words your reps actually use. This job pulls recent messages, sends them to Mave for structured extraction, and produces a messaging playbook: top objections with rebuttals, winning phrases, competitive positioning, and persona-specific talk tracks.

**Flow:** Slack `conversations.history` (#sales) → Mavera `POST /mave/chat` → Messaging playbook

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests, time

  SL_TOKEN = os.environ["SLACK_BOT_TOKEN"]
  SL_BASE = "https://slack.com/api"
  SL_H = {"Authorization": f"Bearer {SL_TOKEN}"}
  MV = os.environ["MAVERA_API_KEY"]
  MV_BASE = "https://app.mavera.io/api/v1"
  MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

  CHANNEL_ID = "C0123SALES"  # Replace with your #sales channel ID
  DAYS_BACK = 30

  # 1. Fetch channel history
  oldest = str(int(time.time()) - DAYS_BACK * 86400)
  messages = []
  cursor = None

  while True:
      params = {"channel": CHANNEL_ID, "limit": 200, "oldest": oldest}
      if cursor:
          params["cursor"] = cursor
      r = requests.get(f"{SL_BASE}/conversations.history", headers=SL_H, params=params)
      r.raise_for_status()
      data = r.json()
      if not data.get("ok"):
          print(f"Slack error: {data.get('error')}")
          break
      messages.extend(data.get("messages", []))
      cursor = data.get("response_metadata", {}).get("next_cursor")
      if not cursor:
          break
      time.sleep(1)

  # 2. Filter to substantive messages (skip bot messages, reactions-only, etc.)
  human_msgs = [m for m in messages if m.get("type") == "message"
                and not m.get("bot_id") and len(m.get("text","")) > 30]
  print(f"Fetched {len(messages)} messages, {len(human_msgs)} substantive")

  # 3. Build corpus (anonymize user IDs)
  corpus = "\n\n".join(
      f"[{time.strftime('%Y-%m-%d', time.localtime(float(m.get('ts',0))))}] "
      f"{m.get('text','')[:500]}"
      for m in sorted(human_msgs, key=lambda x: float(x.get("ts",0)))[-100:]
  )

  # 4. Mave extraction
  playbook = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
      "message": f"Sales messaging analyst. Analyze {len(human_msgs)} messages from our #sales channel (past {DAYS_BACK} days).\n\n"
          f"MESSAGES:\n{corpus[:8000]}\n\n"
          "Extract and structure:\n\n"
          "1. **TOP 5 OBJECTIONS** — Exact objection, frequency, winning rebuttal\n"
          "2. **WINNING PHRASES** — Language that appears in won deals\n"
          "3. **COMPETITIVE MENTIONS** — Which competitors, in what context, rep response\n"
          "4. **BUYER QUOTES** — Direct quotes from prospects (useful for marketing)\n"
          "5. **PERSONA TALK TRACKS** — If different buyer personas are mentioned, create a mini talk track per persona\n"
          "6. **DEAL VELOCITY PATTERNS** — What accelerates deals? What stalls them?\n\n"
          "Be specific. Quote directly from the messages."
  }).json()

  print(f"\n{'='*60}\nSALES MESSAGING PLAYBOOK\n{'='*60}")
  print(playbook.get("content", "")[:3000])
  ```

  ```javascript JavaScript theme={"dark"}
  const SL_TOKEN = process.env.SLACK_BOT_TOKEN;
  const SL_BASE = "https://slack.com/api";
  const SL_H = { Authorization: `Bearer ${SL_TOKEN}` };
  const MV = process.env.MAVERA_API_KEY;
  const MV_BASE = "https://app.mavera.io/api/v1";
  const MV_H = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };

  const CHANNEL_ID = "C0123SALES";
  const DAYS_BACK = 30;
  const oldest = String(Math.floor(Date.now() / 1000) - DAYS_BACK * 86400);

  // 1. Paginate history
  const messages = [];
  let cursor;
  do {
    const params = new URLSearchParams({ channel: CHANNEL_ID, limit: "200", oldest });
    if (cursor) params.set("cursor", cursor);
    const r = await fetch(`${SL_BASE}/conversations.history?${params}`, { headers: SL_H });
    const data = await r.json();
    if (!data.ok) { console.log(`Slack error: ${data.error}`); break; }
    messages.push(...(data.messages || []));
    cursor = data.response_metadata?.next_cursor;
    await new Promise(r => setTimeout(r, 1000));
  } while (cursor);

  // 2. Filter
  const humanMsgs = messages.filter(m => m.type === "message" && !m.bot_id && (m.text||"").length > 30);
  console.log(`Fetched ${messages.length} messages, ${humanMsgs.length} substantive`);

  // 3. Corpus
  const corpus = humanMsgs
    .sort((a, b) => parseFloat(a.ts) - parseFloat(b.ts))
    .slice(-100)
    .map(m => `[${new Date(parseFloat(m.ts)*1000).toISOString().slice(0,10)}] ${(m.text||"").slice(0,500)}`)
    .join("\n\n");

  // 4. Playbook
  const playbook = await fetch(`${MV_BASE}/mave/chat`, { method: "POST", headers: MV_H,
    body: JSON.stringify({ message: `Sales analyst. ${humanMsgs.length} messages from #sales (${DAYS_BACK} days).\n\n${corpus.slice(0,8000)}\n\n1. TOP 5 OBJECTIONS (objection, frequency, rebuttal)\n2. WINNING PHRASES\n3. COMPETITIVE MENTIONS\n4. BUYER QUOTES\n5. PERSONA TALK TRACKS\n6. DEAL VELOCITY PATTERNS\n\nQuote directly.` }),
  }).then(r => r.json());

  console.log(`\n${"=".repeat(60)}\nSALES MESSAGING PLAYBOOK`);
  console.log((playbook.content || "").slice(0, 3000));
  ```
</CodeGroup>

### Example Output

```text theme={"dark"}
Fetched 847 messages, 312 substantive

SALES MESSAGING PLAYBOOK
============================================================

1. TOP 5 OBJECTIONS:
   a) "We already use [Competitor X]" (23 mentions)
      Rebuttal: "Most of our enterprise clients migrated from [X].
      Here's what they gained: [ROI stat]"
   b) "Need to get IT approval" (18 mentions)
      Rebuttal: "We have a 2-page IT security doc. Can we send
      that to your CTO directly?"
   c) "Budget is locked until Q3" (14 mentions)
      Rebuttal: "We offer quarterly billing with a 90-day pilot."

2. WINNING PHRASES:
   - "This paid for itself in week two"
   - "Our VP of Marketing saw the demo and said 'why aren't we using this?'"
   - "The time-to-value was under 48 hours"

3. COMPETITIVE MENTIONS:
   - Competitor X: 31 mentions, mostly "switching from" context
   - Competitor Y: 12 mentions, "evaluating alongside"
```

### Error Handling

<AccordionGroup>
  <Accordion title="Channel not found">Ensure the bot is invited to the channel. `not_in_channel` error means you need to `/invite @bot`. `channel_not_found` means the ID is wrong — use `conversations.list` to verify.</Accordion>
  <Accordion title="Rate limits">`conversations.history` is Tier 3 (50 req/min). Pagination with 200 messages per page keeps most channels under 5 calls. Add 1s delay between pages.</Accordion>
  <Accordion title="Thread replies">Channel history doesn't include threaded replies by default. For important threads, fetch `conversations.replies` with the thread's `ts`.</Accordion>
  <Accordion title="PII in messages">Sales channels often contain prospect names, emails, and deal amounts. Strip PII before sending to external APIs. The code limits to 500 chars per message to reduce exposure.</Accordion>
</AccordionGroup>
