twilio-conversations-classic-api — Claude skill: multi-channel threads
A Claude and Claude skill for building multi-channel messaging on the Twilio Conversations (classic) API, with SMS, WhatsApp and web chat sharing one thread.
npx skills add openai/plugins --skill twilio-conversations-classic-api -a codexWhat twilio-conversations-classic-api does
The skill covers the Conversations (classic) API, v1. These are persistent threads where SMS, WhatsApp and web chat participants message together. History is kept, and several agents can join the same conversation — that is the difference from single-message APIs.
Inside you get Python and Node.js examples: creating a Conversation Service, opening a conversation, adding participants per channel, sending text and media. Media has to sit behind public URLs, supports JPG, PNG, GIF, PDF and vCard, and allows up to 10 URLs per message. SMS and WhatsApp participants need a Twilio number assigned to a Conversations Service, plus TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN in the environment. The SDK installs with pip install twilio or npm install twilio.
One thing to watch: this is the classic API, not Conversations v2. If your project already runs on the newer version, the examples will not drop in as-is. Worth checking which version you are on before wiring anything up.
What it helps with
- merge a customer's SMS and WhatsApp replies into one thread
- let several support agents join the same conversation
- send a PDF or image across every channel at once
- add a web chat participant by identity
- handle webhooks for conversation events
Example prompts for the agent
- Create a Conversation Service and open a conversation for order #12345
- Add a WhatsApp participant and send them a PDF document
- Show me how to handle Conversations (classic) webhooks in Node.js
How to install twilio-conversations-classic-api
1Skills CLI (Vercel)
npx skills add openai/plugins --skill twilio-conversations-classic-api -a codex2Manually: put the files in place
.agents/skills/twilio-conversations-classic-api/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add openai/plugins --skill twilio-conversations-classic-api -a claude-code2Manually: put the files in place
~/.claude/skills/twilio-conversations-classic-api/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/twilio-conversations-classic-api/
Manually: put the files in place
.cursor/skills/twilio-conversations-classic-api/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install openai/plugins twilio-conversations-classic-api2Manually: put the files in place
.github/skills/twilio-conversations-classic-api/SKILL.mdPlatform CLI
gemini skills install https://github.com/openai/plugins --path plugins/twilio-developer-kit/skills/twilio-conversations-classic-apiManually: put the files in place
.devin/skills/twilio-conversations-classic-api/SKILL.mdThe legacy .windsurf/skills/ path still works
Claude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: Codex skills: how to install, where they live, how to call them
Questions about twilio-conversations-classic-api
How do I install twilio-conversations-classic-api in Claude?
Run npx skills add openai/plugins --skill twilio-conversations-classic-api -a codex. The skill file then lands at .agents/skills/twilio-conversations-classic-api/SKILL.md.
What does twilio-conversations-classic-api do?
It gives the agent working examples of the Conversations (classic) API: creating conversations, adding SMS, WhatsApp and chat participants, sending messages and media, and handling webhooks.
What do I need before using the skill?
A Twilio account with Conversations (classic) enabled, TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN set as environment variables, and the twilio SDK installed. SMS and WhatsApp participants also need a Twilio number assigned to a Conversations Service.
Source text
SKILL.md · MIT
---
name: twilio-conversations-classic-api
description: >
Build multi-channel messaging experiences using Twilio Conversations (classic) API.
Covers creating conversations, adding participants (SMS, WhatsApp, chat),
sending messages, and handling webhooks. Use this skill to manage persistent
multi-party or multi-channel conversations beyond single-message SMS/WhatsApp.
---
## Overview
Conversations (classic) API provides persistent, multi-channel threads where participants on SMS, WhatsApp, and web chat can message together. Unlike single-message APIs, Conversations maintains history and supports multi-agent access.
**Note:** This is the Conversations (classic) API (v1).
---
## Prerequisites
- Twilio account with Conversations (classic) enabled
— New to Twilio? See `twilio-account-setup`
— Enable at: [Console > Conversations > Manage > Overview](https://console.twilio.com/us1/develop/conversations/manage/overview) > **Enable Conversations**
- Environment variables:
- `TWILIO_ACCOUNT_SID`
- `TWILIO_AUTH_TOKEN`
— See `twilio-iam-auth-setup` for credential setup and best practices
- SDK: `pip install twilio` / `npm install twilio`
- For SMS/WhatsApp participants: a Twilio number assigned to a Conversations Service
---
## Setup: Create a Conversation Service (classic)
A Conversation Service is the parent configuration container for all your conversations in the classic API. You need one before creating conversations with SMS/WhatsApp participants.
**Python**
```python
import os
from twilio.rest import Client
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
# Create a Conversation Service
service = client.conversations.v1.services.create(
friendly_name="Customer Support Service"
)
print(f"Service SID: {service.sid}")
```
**Node.js**
```node
const twilio = require("twilio");
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
// Create a Conversation Service
const service = await client.conversations.v1.services.create({
friendlyName: "Customer Support Service"
});
console.log(`Service SID: ${service.sid}`);
```
**Next:** Assign your Twilio phone number to this service at [Console > Conversations > Manage > Services](https://console.twilio.com/us1/develop/conversations/manage/services) > Select your service > Add phone number.
---
## Quickstart
**Python**
```python
import os
from twilio.rest import Client
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
# Create a conversation (use default service or specify service_sid)
conversation = client.conversations.v1.conversations.create(
friendly_name="Customer Support - Order #12345"
)
# Add an SMS participant
client.conversations.v1 \
.conversations(conversation.sid) \
.participants \
.create(
messaging_binding_address="+15558675310",
messaging_binding_proxy_address="+15017122661"
)
# Send a message
client.conversations.v1 \
.conversations(conversation.sid) \
.messages \
.create(body="Hello! How can I help you today?", author="support-agent")
```
**Node.js**
```node
const twilio = require("twilio");
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
// Create a conversation (use default service or specify serviceSid)
const conversation = await client.conversations.v1.conversations.create({
friendlyName: "Customer Support - Order #12345",
});
// Add an SMS participant
await client.conversations.v1
.conversations(conversation.sid)
.participants.create({
messagingBindingAddress: "+15558675310",
messagingBindingProxyAddress: "+15017122661",
});
// Send a message
await client.conversations.v1
.conversations(conversation.sid)
.messages.create({ body: "Hello! How can I help you today?", author: "support-agent" });
```
---
## Key Patterns
### Add Participants by Channel
**WhatsApp participant — Python**
```python
client.conversations.v1 \
.conversations(conversation.sid) \
.participants \
.create(
messaging_binding_address="whatsapp:+15558675310",
messaging_binding_proxy_address="whatsapp:+14155238886"
)
```
**WhatsApp participant — Node.js**
```node
await client.conversations.v1
.conversations(conversationSid)
.participants.create({
messagingBindingAddress: "whatsapp:+15558675310",
messagingBindingProxyAddress: "whatsapp:+14155238886",
});
```
**Chat participant (web/mobile) — Python**
```python
client.conversations.v1 \
.conversations(conversation.sid) \
.participants \
.create(identity="user-123")
```
**Chat participant (web/mobile) — Node.js**
```node
await client.conversations.v1
.conversations(conversationSid)
.participants.create({ identity: "user-123" });
```
### Send Media (All Channels)
**Python**
```python
# Send a message with media
client.conversations.v1 \
.conversations(conversation.sid) \
.messages \
.create(
body="Check out this image!",
author="support-agent",
media_url="https://example.com/image.jpg"
)
# Multiple media URLs (up to 10)
client.conversations.v1 \
.conversations(conversation.sid) \
.messages \
.create(
body="Here are the documents",
author="support-agent",
media_url=[
"https://example.com/doc1.pdf",
"https://example.com/doc2.pdf"
]
)
```
**Node.js**
```node
// Send a message with media
await client.conversations.v1
.conversations(conversationSid)
.messages.create({
body: "Check out this image!",
author: "support-agent",
mediaUrl: "https://example.com/image.jpg"
});
// Multiple media URLs (up to 10)
await client.conversations.v1
.conversations(conversationSid)
.messages.create({
body: "Here are the documents",
author: "support-agent",
mediaUrl: [
"https://example.com/doc1.pdf",
"https://example.com/doc2.pdf"
]
});
```
Media must be publicly accessible URLs. Supported: JPG, PNG, GIF, PDF, vCard. Max 10 URLs per message. Works across all channels: SMS (as MMS), WhatsApp, and chat participants all receive media.
### Add Multiple Participants
**Python**
```python
# Add multiple SMS participants to a conversation
participant_numbers = [
"+15558675310",
"+15558675311",
"+15558675312"
]
twilio_number = "+15017122661"
for phone_number in participant_numbers:
client.conversations.v1 \
.conversations(conversation.sid) \
.participants \
.create(
messaging_binding_address=phone_number,
messaging_binding_proxy_address=twilio_number
)
```
**Node.js**
```node
// Add multiple SMS participants to a conversation
const participantNumbers = [
"+15558675310",
"+15558675311",
"+15558675312"
];
const twilioNumber = "+15017122661";
for (const phoneNumber of participantNumbers) {
await client.conversations.v1
.conversations(conversationSid)
.participants.create({
messagingBindingAddress: phoneNumber,
messagingBindingProxyAddress: twilioNumber
});
}
```
### Fetch Message History
**Python**
```python
# Get all messages from a conversation
messages = client.conversations.v1 \
.conversations(conversation.sid) \
.messages \
.list(limit=50)
for message in messages:
print(f"{message.author}: {message.body}")
```
**Node.js**
```node
// Get all messages from a conversation
const messages = await client.conversations.v1
.conversations(conversationSid)
.messages
.list({ limit: 50 });
messages.forEach(message => {
console.log(`${message.author}: ${message.body}`);
});
```
### List Conversations
**Python**
```python
# List all conversations
conversations = client.conversations.v1.conversations.list(limit=20)
for conv in conversations:
print(f"{conv.friendly_name} - {conv.sid}")
# Filter by state
active_conversations = client.conversations.v1.conversations.list(
state="active",
limit=20
)
```
**Node.js**
```node
// List all conversations
const conversations = await client.conversations.v1.conversations.list({ limit: 20 });
conversations.forEach(conv => {
console.log(`${conv.friendlyName} - ${conv.sid}`);
});
// Filter by state
const activeConversations = await client.conversations.v1.conversations.list({
state: "active",
limit: 20
});
```
### Remove Participants
**Python**
```python
# Remove a participant by participant SID
client.conversations.v1 \
.conversations(conversation.sid) \
.participants(participant_sid) \
.delete()
# Find and remove by phone number
participants = client.conversations.v1 \
.conversations(conversation.sid) \
.participants \
.list()
for p in participants:
if p.messaging_binding and p.messaging_binding.get("address") == "+15558675310":
client.conversations.v1 \
.conversations(conversation.sid) \
.participants(p.sid) \
.delete()
```
**Node.js**
```node
// Remove a participant by participant SID
await client.conversations.v1
.conversations(conversationSid)
.participants(participantSid)
.remove();
// Find and remove by phone number
const participants = await client.conversations.v1
.conversations(conversationSid)
.participants
.list();
for (const p of participants) {
if (p.messagingBinding?.address === "+15558675310") {
await client.conversations.v1
.conversations(conversationSid)
.participants(p.sid)
.remove();
}
}
```
### Close/Complete Conversations
**Python**
```python
# Close a conversation (marks it inactive)
client.conversations.v1 \
.conversations(conversation.sid) \
.update(state="closed")
# Delete a conversation completely
client.conversations.v1 \
.conversations(conversation.sid) \
.delete()
```
**Node.js**
```node
// Close a conversation (marks it inactive)
await client.conversations.v1
.conversations(conversationSid)
.update({ state: "closed" });
// Delete a conversation completely
await client.conversations.v1
.conversations(conversationSid)
.remove();
```
### Handle Incoming Messages (Webhook)
Configure at Console > Conversations > Manage > Global Webhooks.
> **Security:** Always validate the `X-Twilio-Signature` header in production to confirm requests originate from Twilio. See `twilio-webhook-architecture` for validation patterns.
**Python (Flask)**
```python
from twilio.request_validator import RequestValidator
@app.route("/conversations/webhook", methods=["POST"])
def conversations_webhook():
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])
if not validator.validate(request.url, request.form, request.headers.get("X-Twilio-Signature", "")):
return "", 403
event_type = request.form.get("EventType")
conversation_sid = request.form.get("ConversationSid")
author = request.form.get("Author")
if event_type == "onMessageAdded" and author != "support-agent":
client.conversations.v1.conversations(conversation_sid).messages.create(
body="Thanks — an agent will be with you shortly.",
author="support-bot"
)
return "", 204
```
**Node.js (Express)**
```node
app.post("/conversations/webhook", async (req, res) => {
const valid = twilio.validateRequest(
process.env.TWILIO_AUTH_TOKEN,
req.headers["x-twilio-signature"],
`https://${req.headers.host}${req.originalUrl}`,
req.body
);
if (!valid) return res.status(403).send("Forbidden");
const { EventType, ConversationSid, Author } = req.body;
if (EventType === "onMessageAdded" && Author !== "support-agent") {
await client.conversations.v1
.conversations(ConversationSid)
.messages.create({ body: "Thanks — an agent will be with you shortly.", author: "support-bot" });
}
res.sendStatus(204);
});
```
---
## Advanced Context
### Message Delivery Status
Track message delivery through webhooks. Configure at Console > Conversations > Manage > Global Webhooks.
**Available delivery events:**
- `onMessageAdded` — Message created
- `onMessageUpdated` — Message status changed
- `onDeliveryUpdated` — Delivery receipt received (SMS/WhatsApp only)
**Python (Flask)**
```python
@app.route("/conversations/webhook", methods=["POST"])
def delivery_webhook():
event_type = request.form.get("EventType")
if event_type == "onDeliveryUpdated":
delivery_status = request.form.get("DeliveryStatus")
message_sid = request.form.get("MessageSid")
print(f"Message {message_sid}: {delivery_status}")
# Status values: sent, delivered, failed, undelivered
return "", 204
```
**Node.js (Express)**
```node
app.post("/conversations/webhook", (req, res) => {
const { EventType, DeliveryStatus, MessageSid } = req.body;
if (EventType === "onDeliveryUpdated") {
console.log(`Message ${MessageSid}: ${DeliveryStatus}`);
// Status values: sent, delivered, failed, undelivered
}
res.sendStatus(204);
});
```
### Conversation Attributes (Metadata)
Store custom metadata on conversations (order IDs, customer info, tags).
**Python**
```python
# Set attributes when creating
conversation = client.conversations.v1.conversations.create(
friendly_name="Customer Support - Order #12345",
attributes='{"order_id": "12345", "priority": "high", "customer_tier": "gold"}'
)
# Update attributes on existing conversation
client.conversations.v1 \
.conversations(conversation.sid) \
.update(attributes='{"order_id": "12345", "status": "resolved"}')
# Read attributes
conv = client.conversations.v1.conversations(conversation.sid).fetch()
import json
attrs = json.loads(conv.attributes)
print(f"Order ID: {attrs['order_id']}")
```
**Node.js**
```node
// Set attributes when creating
const conversation = await client.conversations.v1.conversations.create({
friendlyName: "Customer Support - Order #12345",
attributes: JSON.stringify({ orderId: "12345", priority: "high", customerTier: "gold" })
});
// Update attributes on existing conversation
await client.conversations.v1
.conversations(conversationSid)
.update({ attributes: JSON.stringify({ orderId: "12345", status: "resolved" }) });
// Read attributes
const conv = await client.conversations.v1.conversations(conversationSid).fetch();
const attrs = JSON.parse(conv.attributes);
console.log(`Order ID: ${attrs.orderId}`);
```
### Participant Attributes (Metadata)
Store metadata on individual participants (role, name, account info).
**Python**
```python
# Set attributes when adding participant
client.conversations.v1 \
.conversations(conversation.sid) \
.participants \
.create(
messaging_binding_address="+15558675310",
messaging_binding_proxy_address="+15017122661",
attributes='{"name": "John Doe", "role": "customer", "account_id": "A123"}'
)
# Update participant attributes
client.conversations.v1 \
.conversations(conversation.sid) \
.participants(participant_sid) \
.update(attributes='{"role": "vip_customer", "satisfaction": "high"}')
```
**Node.js**
```node
// Set attributes when adding participant
await client.conversations.v1
.conversations(conversationSid)
.participants.create({
messagingBindingAddress: "+15558675310",
messagingBindingProxyAddress: "+15017122661",
attributes: JSON.stringify({ name: "John Doe", role: "customer", accountId: "A123" })
});
// Update participant attributes
await client.conversations.v1
.conversations(conversationSid)
.participants(participantSid)
.update({ attributes: JSON.stringify({ role: "vip_customer", satisfaction: "high" }) });
```
---
## Limits
| Limit | Value |
|-------|-------|
| Participants per conversation | 1,000 |
| Messages per conversation | Unlimited (older messages may be archived) |
| Message retention | Configurable (default: indefinite) |
---
## CANNOT
- **Cannot add SMS participants without a Twilio number** — Number must be assigned to a Conversations (classic) Service
- **Cannot send WhatsApp messages outside the 24-hour window** — Subject to service window rules. See `twilio-whatsapp-send-message`
- **Cannot use chat participants without Access Tokens** — Client-side SDK auth required. See `twilio-iam-auth-setup`
- **Cannot use WhatsApp Groups API** — Deprecated April 2020. Use Conversations (classic) API instead.
- **Conversations v1 (classic) is in maintenance mode** — Consider Conversations v2 API for new projects with enhanced features and scalability.
---
## Next Steps
- **WhatsApp setup and rules:** `twilio-whatsapp-send-message`
- **SMS setup:** `twilio-sms-send-message`
- **Access Tokens for chat clients:** `twilio-iam-auth-setup`
- **Webhook security:** `twilio-webhook-architecture`
© Twilio · MIT · openai/plugins