Scenario
Your sales, support, or appointment-reminder SMS flows generate thousands of two-way conversations. This job pulls conversations from Twilio’s Conversations API, aggregates message threads, and sends them to Mave for structured analysis — response rates, sentiment patterns, drop-off points, and messaging optimization recommendations. Flow: TwilioGET /v1/Conversations → GET /v1/Conversations/{sid}/Messages → Mavera POST /mave/chat → Conversation intelligence
Code
import os, requests, time
from datetime import datetime, timedelta
TW_SID = os.environ["TWILIO_ACCOUNT_SID"]
TW_TOKEN = os.environ["TWILIO_AUTH_TOKEN"]
TW_AUTH = (TW_SID, TW_TOKEN)
TW_CONV = "https://conversations.twilio.com/v1"
MV = os.environ["MAVERA_API_KEY"]
MV_BASE = "https://app.mavera.io/api/v1"
MV_H = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
# 1. List recent conversations
r = requests.get(f"{TW_CONV}/Conversations", auth=TW_AUTH,
params={"PageSize": 50})
r.raise_for_status()
conversations = r.json().get("conversations", [])
print(f"Found {len(conversations)} conversations")
# 2. Fetch messages for each conversation
all_threads = []
for conv in conversations[:30]:
sid = conv.get("sid", "")
label = conv.get("friendly_name", conv.get("unique_name", sid))
mr = requests.get(f"{TW_CONV}/Conversations/{sid}/Messages", auth=TW_AUTH,
params={"PageSize": 50, "Order": "asc"})
if not mr.ok:
continue
msgs = mr.json().get("messages", [])
thread_msgs = []
for m in msgs:
thread_msgs.append({
"author": m.get("author", "unknown"),
"body": m.get("body", "")[:300],
"date": m.get("date_created", "")[:16],
})
if thread_msgs:
all_threads.append({"label": label, "input": thread_msgs,
"msg_count": len(thread_msgs)})
time.sleep(0.1)
print(f"Threads with input: {len(all_threads)}, "
f"Total input: {sum(t['msg_count'] for t in all_threads)}")
# 3. Build corpus
corpus = []
for t in all_threads[:20]:
corpus.append(f"\n--- THREAD: {t['label']} ({t['msg_count']} messages) ---")
for m in t["messages"]:
corpus.append(f"[{m['date']}] {m['author']}: {m['body']}")
corpus_text = "\n".join(corpus)
# 4. Mave analysis
analysis = requests.post(f"{MV_BASE}/mave/chat", headers=MV_H, json={
"message": f"Conversation intelligence analyst. Analyze {len(all_threads)} SMS conversation threads ({sum(t['msg_count'] for t in all_threads)} total messages).\n\n"
f"{corpus_text[:8000]}\n\n"
"Produce a CONVERSATION INTELLIGENCE REPORT:\n\n"
"1. **Response Patterns** — Average messages per thread, response time distribution, who initiates\n"
"2. **Sentiment Analysis** — Overall tone, sentiment shifts within threads, frustration signals\n"
"3. **Drop-off Points** — Where conversations end prematurely, common last messages before silence\n"
"4. **Top Intents** — What customers are asking about (categories with counts)\n"
"5. **Winning Messages** — Our messages that get the best responses (highest engagement)\n"
"6. **Problem Messages** — Our messages that cause confusion, no-reply, or negative reactions\n"
"7. **Optimization Recommendations** — 5 specific changes to improve conversion/satisfaction\n\n"
"Include representative quotes."
}).json()
print(f"\n{'='*60}\nSMS CONVERSATION INTELLIGENCE\n{'='*60}")
print(analysis.get("content", "")[:3000])
const TW_SID = process.env.TWILIO_ACCOUNT_SID;
const TW_TOKEN = process.env.TWILIO_AUTH_TOKEN;
const TW_CREDS = btoa(`${TW_SID}:${TW_TOKEN}`);
const TW_CONV = "https://conversations.twilio.com/v1";
const TW_H = { Authorization: `Basic ${TW_CREDS}` };
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" };
// 1. List conversations
const conversations = (await (await fetch(
`${TW_CONV}/Conversations?PageSize=50`, { headers: TW_H })).json()).conversations || [];
console.log(`Found ${conversations.length} conversations`);
// 2. Fetch messages
const allThreads = [];
for (const conv of conversations.slice(0, 30)) {
const sid = conv.sid || "";
const mr = await fetch(`${TW_CONV}/Conversations/${sid}/Messages?PageSize=50&Order=asc`,
{ headers: TW_H });
if (!mr.ok) continue;
const msgs = (await mr.json()).messages || [];
const threadMsgs = msgs.map(m => ({
author: m.author || "unknown",
body: (m.body || "").slice(0, 300),
date: (m.date_created || "").slice(0, 16),
}));
if (threadMsgs.length)
allThreads.push({ label: conv.friendly_name || conv.unique_name || sid,
messages: threadMsgs, msg_count: threadMsgs.length });
await new Promise(r => setTimeout(r, 100));
}
const totalMsgs = allThreads.reduce((s, t) => s + t.msg_count, 0);
console.log(`Threads: ${allThreads.length}, Total messages: ${totalMsgs}`);
// 3. Corpus
let corpus = "";
for (const t of allThreads.slice(0, 20)) {
corpus += `\n--- THREAD: ${t.label} (${t.msg_count} msgs) ---\n`;
for (const m of t.messages) corpus += `[${m.date}] ${m.author}: ${m.body}\n`;
}
// 4. Analysis
const analysis = await fetch(`${MV_BASE}/mave/chat`, { method: "POST", headers: MV_H,
body: JSON.stringify({ message: `Conversation analyst. ${allThreads.length} SMS threads (${totalMsgs} messages).\n\n${corpus.slice(0,8000)}\n\n1. Response Patterns\n2. Sentiment Analysis\n3. Drop-off Points\n4. Top Intents\n5. Winning Messages\n6. Problem Messages\n7. Optimization Recommendations (5)\n\nInclude quotes.` }),
}).then(r => r.json());
console.log(`\n${"=".repeat(60)}\nSMS CONVERSATION INTELLIGENCE`);
console.log((analysis.content || "").slice(0, 3000));
Example Output
Found 50 conversations
Threads with messages: 28, Total messages: 247
SMS CONVERSATION INTELLIGENCE
============================================================
1. RESPONSE PATTERNS:
- Avg messages per thread: 8.8
- Customer response rate: 72% (first reply), drops to 41% by msg 5
- Business initiates 85% of threads
2. SENTIMENT ANALYSIS:
- Overall: Neutral-positive (0.34/1.0)
- Frustration spikes at appointment rescheduling messages
- Positive peak: confirmation messages with specific times
3. DROP-OFF POINTS:
- 28% drop after "Reply YES to confirm" — too many options confuse
- 19% drop after pricing messages — sticker shock
- Last message before silence: "I'll think about it" (14 instances)
5. WINNING MESSAGES:
"Your appointment is confirmed for Tuesday at 2pm with Dr. Smith.
Reply CHANGE to reschedule." → 94% response rate
6. PROBLEM MESSAGES:
"Please select from the following options: 1) Schedule 2) Cancel
3) Reschedule 4) Speak to agent 5) More info" → 31% drop-off
7. RECOMMENDATIONS:
a) Reduce options per message from 5 to 2 (binary choices)
b) Include specific time/date in every confirmation
c) Replace "Reply YES" with conversational language
Error Handling
Conversations vs Messages API
Conversations vs Messages API
The Conversations API (
conversations.twilio.com) manages multi-party threads. The older Messages API (api.twilio.com) stores individual SMS records. Use Conversations for thread-level analysis, Messages for volume metrics.Rate limits
Rate limits
Conversations API: 20 req/sec. The code fetches 30 conversations × 1 message request = 30 calls. At 100ms delay, ~3 seconds total. Well within limits.
Message encoding
Message encoding
SMS has 160-char segments. Long messages are concatenated by carriers. Twilio stores the full message body. No truncation handling needed.