The gist
Messaging apps aren't just chats anymore. They're full business platforms: sales, support, onboarding, teamwork. There's one problem: all of it takes live people, who get tired, get sick and go on vacation. AI changes the equation. In this lesson we build smart bots for Telegram, Slack, WhatsApp and Microsoft Teams: a customer writes → Claude analyzes it → replies in seconds → if it's complicated → escalates to a person.
Key concepts
- Telegram Bot API: the official interface for bots; python-telegram-bot is the standard in Python
- Webhook vs Polling: two ways to receive messages; webhooks are faster and cheaper in production
- Slack Bolt SDK: the official SDK: events, shortcuts and slash commands in one place
- WhatsApp Business API: three routes: Twilio (reliable), 360dialog (specialized), Meta Cloud API (direct)
- Microsoft Teams: notifications through a webhook in the Workflows app, and Microsoft's official Teams MCP (preview)
- ManyChat: a no-code builder for direct message flows (Instagram, WhatsApp, Messenger, Telegram and more)
- Unified Messaging Hub: one entry point for all channels; Claude triages and routes
- Escalation logic: when the bot answers on its own and when it hands off to a live person
Theory
Which messaging app fits which business
| App | Audience | Best for | Difficulty |
|---|---|---|---|
| Telegram | International communities, tech, creators | Communities, content, leads, sales | Low |
| Slack | Teams, SaaS, B2B | Internal processes, notifications, DevOps | Medium |
| Latin America, Europe, worldwide | Customer support, small business | Medium | |
| Teams | Corporate B2B | Enterprise clients, Office 365 integration | High |
The rule for choosing: go where your audience already is. Don't create a new channel; plug into an existing one.
A Telegram bot: from zero to a smart assistant
Step 1: Create the bot
Send /newbot to @BotFather → enter a name and a username → get a token. This token = control over the bot.
Step 2: Basic integration with Claude
import os
import logging
from telegram import Update
from telegram.ext import (
Application, CommandHandler, MessageHandler,
filters, ContextTypes
)
import anthropic
from dotenv import load_dotenv
load_dotenv()
claude = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
# Conversation history kept in memory
# In production, replace with Redis or SQLite
conversation_history: dict[int, list] = {}
SYSTEM_PROMPT = """You are the AI assistant for Acme Realty, a real estate agency.
You answer questions about real estate in Ecuador: prices, neighborhoods, the buying process.
Tone: a friendly expert, not a salesperson.
If a question needs a consultation with an agent, say so clearly and offer to take their contact info.
Keep it short: 1–3 sentences for simple questions, up to 5 paragraphs for complex ones.
Language: English."""
async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""The /start command: greet a new user."""
user = update.effective_user
await update.message.reply_text(
f"Hi, {user.first_name}! 👋\n\n"
"I can help with questions about real estate in Cuenca, Ecuador. "
"Ask about prices, neighborhoods or the buying process, and I'll answer right away.\n\n"
"If you'd like a personal consultation, just say so."
)
async def cmd_contact(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""The /contact command: collect contact details."""
await update.message.reply_text(
"Leave your email or phone number, "
"and an agent will get back to you within 2 hours during business hours."
)
# In a real bot, a ConversationHandler goes here to receive and save the contact
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""The main handler for incoming messages."""
user_id = update.effective_user.id
user_text = update.message.text
# Start the history if this is the first message
if user_id not in conversation_history:
conversation_history[user_id] = []
# Add the user's message
conversation_history[user_id].append({
"role": "user",
"content": user_text
})
# Limit the history to the last 10 messages
if len(conversation_history[user_id]) > 10:
conversation_history[user_id] = conversation_history[user_id][-10:]
# Show "typing..." for a good user experience
await context.bot.send_chat_action(
chat_id=update.effective_chat.id,
action="typing"
)
# Request to Claude (current models: the "What's current" page)
response = claude.messages.create(
model="claude-sonnet-5-5",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=conversation_history[user_id]
)
assistant_reply = "".join(b.text for b in response.content if b.type == "text")
# Save the reply to the history
conversation_history[user_id].append({
"role": "assistant",
"content": assistant_reply
})
await update.message.reply_text(assistant_reply)
async def handle_error(update: object, context: ContextTypes.DEFAULT_TYPE):
"""Log errors."""
logging.error("Exception while handling update:", exc_info=context.error)
def main():
app = Application.builder().token(BOT_TOKEN).build()
app.add_handler(CommandHandler("start", cmd_start))
app.add_handler(CommandHandler("contact", cmd_contact))
app.add_handler(MessageHandler(
filters.TEXT & ~filters.COMMAND, handle_message
))
app.add_error_handler(handle_error)
print("Telegram bot running (polling mode)...")
app.run_polling(allowed_updates=Update.ALL_TYPES)
if __name__ == "__main__":
main()Telegram: four main bot patterns
1. FAQ bot: answers standard questions about the product, prices, shipping. The system prompt contains the whole knowledge base. It takes on the routine questions and lifts a noticeable share of the load off the support team.
2. Lead qualifier: asks a new user a series of questions (budget, timeline, goals), gauges how "warm" the lead is, and either offers a meeting or sends them into a nurture sequence.
3. Booking bot: integrates with Calendly through the API, offers open time slots and creates the booking right in the chat. The customer never leaves Telegram.
4. Content publisher: Claude generates a post on a schedule, and the bot publishes it to a channel. Planning a week of content takes 20 minutes, once, on Monday.
Slack: automating team processes
Slack with the Bolt SDK isn't just a chat. It's a platform for smart team bots.
Typical scenarios:
- Standup summary: the bot collects survey answers, Claude formats a summary → posts it to a channel
- PR notifications: when a Pull Request is opened, Claude reads the diff and writes a short summary of the changes
- Tasks by command:
/task Build a landing page→ Claude creates a task in Notion/Linear → returns the link - @mention replies: the bot "listens" to channels and answers when someone @mentions it
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import anthropic
import os
app = App(token=os.environ["SLACK_BOT_TOKEN"])
claude = anthropic.Anthropic()
@app.event("app_mention")
def handle_mention(event, say):
"""Replies when someone addresses the bot with an @mention."""
user_text = event["text"]
# Strip the bot's @mention from the text
clean_text = user_text.split(">", 1)[-1].strip() if ">" in user_text else user_text
response = claude.messages.create(
model="claude-sonnet-5-5",
max_tokens=512,
system="You are the development team's assistant. Keep answers short and specific.",
messages=[{"role": "user", "content": clean_text}]
)
say(f"<@{event['user']}> {''.join(b.text for b in response.content if b.type == 'text')}")
@app.command("/standup")
def handle_standup(ack, say, command):
"""
/standup: generates a standup form.
The user fills it in, and the bot collects the answers and makes a summary.
"""
ack()
say({
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Standup for {command['user_name']}*\n"
"Answer three questions:"
}
},
{
"type": "input",
"block_id": "done",
"element": {
"type": "plain_text_input",
"action_id": "done_input",
"placeholder": {"type": "plain_text", "text": "What did you do yesterday?"}
},
"label": {"type": "plain_text", "text": "Done"}
}
]
})
@app.command("/summarize")
def handle_summarize(ack, say, command):
"""
/summarize [text]: summarizes a long message.
Handy for long PR descriptions or documents in channels.
"""
ack()
text_to_summarize = command.get("text", "")
if not text_to_summarize:
say("Add the text after the command: `/summarize [your text]`")
return
response = claude.messages.create(
model="claude-haiku-4-5",
max_tokens=300,
messages=[{
"role": "user",
"content": f"Summarize this text in 3–5 bullet points:\n\n{text_to_summarize}"
}]
)
say(f"📋 *Summary:*\n{''.join(b.text for b in response.content if b.type == 'text')}")
if __name__ == "__main__":
handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
print("Slack bot running...")
handler.start()WhatsApp Business API: the biggest reach
WhatsApp is one of the most widely used messaging apps in the world, with billions of users. For businesses in Latin America and Europe, it's often the only channel customers prefer.
Mind Meta's rules: since January 15, 2026, Meta hasn't allowed general-purpose AI assistants (like ChatGPT) on the WhatsApp Business Platform, with exceptions for a few countries. A bot that answers your own business's customers is a different case, but check WhatsApp Business's current policies and what your provider allows before you launch.
Three ways to connect:
| Provider | Pros | Cons | Price |
|---|---|---|---|
| Twilio | Reliable, excellent documentation | You pay both the provider and Meta | Twilio's rates + Meta's fees |
| 360dialog | Specializes in WhatsApp | Less documentation | 360dialog's rates + Meta's fees |
| Meta Cloud API | Direct access, no provider markup | Complicated business verification | Meta's fees only |
Since July 1, 2025, Meta charges for every delivered template message (it depends on the message category and the recipient's country); replies to a customer within the customer service window are free. See current rates on Meta's page and with your provider.
Code with Twilio + Claude:
from flask import Flask, request
from twilio.twiml.messaging_response import MessagingResponse
from twilio.rest import Client
import anthropic
import os
app = Flask(__name__)
twilio_client = Client(
os.environ["TWILIO_ACCOUNT_SID"],
os.environ["TWILIO_AUTH_TOKEN"]
)
claude = anthropic.Anthropic()
# Conversation history (in production: Redis with a 24h TTL)
wa_conversations: dict[str, list] = {}
WA_SYSTEM = """You are a company's AI assistant on WhatsApp.
Keep replies short (WhatsApp isn't email).
If a question needs a person, say: 'I'm passing you to a specialist, expect a reply within an hour.'
Be friendly but professional."""
@app.route("/webhook/whatsapp", methods=["POST"])
def whatsapp_webhook():
"""Handles incoming WhatsApp messages."""
from_number = request.form.get("From", "")
body = request.form.get("Body", "").strip()
if not body:
return str(MessagingResponse())
# Conversation history
if from_number not in wa_conversations:
wa_conversations[from_number] = []
wa_conversations[from_number].append(
{"role": "user", "content": body}
)
# Limit the context to the last 8 messages
history = wa_conversations[from_number][-8:]
# Claude generates a reply
response = claude.messages.create(
model="claude-sonnet-5-5",
max_tokens=512,
system=WA_SYSTEM,
messages=history
)
reply = "".join(b.text for b in response.content if b.type == "text")
wa_conversations[from_number].append(
{"role": "assistant", "content": reply}
)
# If Claude hands off to a person, notify someone inside the company
if "specialist" in reply.lower() or "passing you" in reply.lower():
notify_manager(from_number, body, reply)
# Reply to the user
resp = MessagingResponse()
resp.message(reply)
return str(resp)
def notify_manager(customer_number: str, question: str, bot_reply: str):
"""Notify a manager on Telegram when the bot can't help."""
import requests
telegram_token = os.environ.get("TELEGRAM_BOT_TOKEN")
manager_chat = os.environ.get("MANAGER_TELEGRAM_CHAT_ID")
if not telegram_token or not manager_chat:
return
message = (
f"🔔 *Escalation from WhatsApp*\n\n"
f"Customer: `{customer_number}`\n"
f"Question: {question}\n\n"
f"Bot replied: {bot_reply[:200]}..."
)
requests.post(
f"https://api.telegram.org/bot{telegram_token}/sendMessage",
json={
"chat_id": manager_chat,
"text": message,
"parse_mode": "Markdown"
}
)
if __name__ == "__main__":
app.run(port=5001, debug=False)Microsoft Teams: the corporate channel
If your clients are corporations, they work in Teams. Two scenarios:
Scenario 1: Webhook notifications to a Teams channel
The simplest route is the Workflows app in Teams (it runs on Power Automate): it gives you an address you can send a request with a message to. Microsoft is retiring the old Incoming Webhooks built on Office 365 connectors, so set up new notifications through Workflows.
import requests
import json
import os
TEAMS_WEBHOOK_URL = os.environ["TEAMS_WEBHOOK_URL"]
def send_teams_notification(title: str, message: str,
color: str = "0078D4",
facts: list[dict] = None) -> bool:
"""
Sends a notification card to a Teams channel through the webhook address from Workflows.
Workflows accepts both MessageCard and Adaptive Card: check the format in a test channel.
color: hex color of the side stripe (0078D4 = Microsoft blue)
facts: a list of {"name": "...", "value": "..."} for a data table
"""
card = {
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
"themeColor": color,
"summary": title,
"sections": [{
"activityTitle": title,
"activityText": message,
}]
}
if facts:
card["sections"][0]["facts"] = facts
response = requests.post(
TEAMS_WEBHOOK_URL,
headers={"Content-Type": "application/json"},
data=json.dumps(card)
)
return response.ok # any successful response (200, 202)
# Example: a new deal notification
def notify_deal_closed(client_name: str, amount: float,
property_address: str) -> None:
"""A notification to the sales channel in Teams when a deal closes."""
send_teams_notification(
title="🎉 New deal closed!",
message=f"An agent signed a contract with a client.",
color="00B050", # green
facts=[
{"name": "Client", "value": client_name},
{"name": "Amount", "value": f"${amount:,.0f}"},
{"name": "Property", "value": property_address},
]
)How to set up a webhook in Teams through Workflows:
- In Teams → Channel → ⋯ (the three dots) → Workflows
- Pick the "Send webhook alerts to a channel" template
- Set the parameters and save
- Copy the webhook address → save it to an environment variable
Scenario 2: Microsoft's Teams MCP
Microsoft has an official MCP server for Teams (part of Microsoft Agent 365, in preview). It lets an agent read chats and channels, post messages and reply in threads. It's a hosted server, not a package you install with npx: an organization admin sets up the connection (registering an app in Microsoft Entra and granting permissions), and tool names may change during the preview. For current tools and the connection setup, see Microsoft's documentation. Check the same documentation for which MCP clients are supported.
When to use which:
- Webhook through Workflows: notifications from business processes (CRM, deploys, deals). Fast, no app registration.
- Teams MCP: an interactive agent that reads and replies in channels. Requires registering an app in Entra and admin permissions.
ManyChat: when you don't need code
For business owners who don't program, there's ManyChat. A visual builder for direct message flows: Instagram, Messenger, WhatsApp, TikTok, Telegram.
What ManyChat can do:
- Visual message sequences with conditions (if/else, no code)
- Integration with Google Sheets, Zapier, Make
- Audience segmentation by tags
- A/B testing of messages
- Broadcasts for mass messaging
ManyChat + Claude through Zapier: ManyChat's own AI features depend on your plan, and connecting Claude specifically is usually easier through Zapier: ManyChat → Webhook → Zapier → Claude API → reply back to ManyChat. A bit more involved, but no code.
Unified Messaging Hub: one system for every channel
from flask import Flask, request, jsonify
import anthropic
import json
import os
app = Flask(__name__)
claude = anthropic.Anthropic()
ROUTING_SYSTEM = """You are a router for customer messages. Analyze the request and return JSON:
{
"intent": "faq|lead|complaint|consultation|other",
"urgency": "low|medium|high",
"language": "en|es|ru",
"should_escalate": true|false,
"suggested_response": "a short answer if intent=faq"
}"""
def route_message(message: str, channel: str, user_id: str) -> dict:
"""
Analyzes an incoming message and decides where it goes.
channel: telegram|whatsapp|slack|teams
"""
response = claude.messages.create(
model="claude-haiku-4-5", # Haiku for routing: fast and cheap; check that the model is still available
max_tokens=256,
system=ROUTING_SYSTEM,
messages=[{
"role": "user",
"content": f"Channel: {channel}\nMessage: {message}"
}]
)
try:
return json.loads("".join(b.text for b in response.content if b.type == "text"))
except json.JSONDecodeError:
return {
"intent": "other",
"urgency": "medium",
"language": "en",
"should_escalate": False,
"suggested_response": ""
}
@app.route("/hub/incoming", methods=["POST"])
def unified_incoming():
"""
A single entry point for messages from all channels.
Each source (Telegram bot, WhatsApp webhook, Slack event)
sends its message here in a standard format.
"""
data = request.json
channel = data.get("channel", "unknown")
user_id = data.get("user_id", "")
message = data.get("message", "")
# 1. Routing with Claude Haiku (cheap and fast)
routing = route_message(message, channel, user_id)
# 2. Handling logic by intent
if routing["should_escalate"]:
# Hand off to a live person
escalate_to_manager(channel, user_id, message, routing)
reply = "I'm passing your question to a specialist. We'll reply within 1 hour."
elif routing["intent"] == "faq" and routing["suggested_response"]:
# Use the ready answer from routing
reply = routing["suggested_response"]
else:
# A full answer from Claude Sonnet
response = claude.messages.create(
model="claude-sonnet-5-5",
max_tokens=512,
system="You are a friendly assistant. Reply in the customer's language.",
messages=[{"role": "user", "content": message}]
)
reply = "".join(b.text for b in response.content if b.type == "text")
return jsonify({
"reply": reply,
"routing": routing,
"channel": channel
})
def escalate_to_manager(channel: str, user_id: str,
message: str, routing: dict) -> None:
"""Notifies a manager on Telegram."""
bot_token = os.environ.get("TELEGRAM_BOT_TOKEN")
manager_chat = os.environ.get("MANAGER_TELEGRAM_CHAT_ID")
if not bot_token or not manager_chat:
return
import requests
requests.post(
f"https://api.telegram.org/bot{bot_token}/sendMessage",
json={
"chat_id": manager_chat,
"text": (
f"🔔 *Escalation*\n"
f"Channel: {channel} | User: `{user_id}`\n"
f"Urgency: {routing.get('urgency', '?')}\n"
f"Message: {message[:300]}"
),
"parse_mode": "Markdown"
}
)
if __name__ == "__main__":
app.run(port=5002, debug=False)Making money: selling bots to clients
This is a separate service. Small businesses need bots but don't know how to build them.
Typical packages and what goes into the price:
| Package | What's included | What goes into the price |
|---|---|---|
| Basic FAQ bot | Telegram bot, 20–50 questions in the knowledge base | hours for setup and filling the knowledge base; maintenance: prompt edits and monitoring |
| Lead qualifier | Telegram + CRM integration | hours for the CRM integration; maintenance: checking that leads come through |
| WhatsApp bot | WhatsApp Business API + Claude | setup and business verification; maintenance + Meta's and the provider's fees |
| Full system | All channels + CRM + Unified Hub + analytics | total hours across all channels + hosting and token costs |
Prices depend on your market, your niche and how much time you put in. Income in this niche isn't guaranteed: work out your costs and the demand in your market using the lessons Packaging your offer and Pricing and monetization.
Practice
Assignment: a Telegram AI assistant + a Teams webhook in 5 steps
Step 1: Create a Telegram bot (10 min)
- @BotFather →
/newbot→ name → username → token - Create the project:
mkdir ai-messenger-bot && cd ai-messenger-bot
python -m venv venv && source venv/bin/activate
pip install python-telegram-bot anthropic python-dotenv flask requests- Create
.env:
TELEGRAM_BOT_TOKEN=your_token
ANTHROPIC_API_KEY=sk-ant-...
TEAMS_WEBHOOK_URL=https://... # we'll add this in step 3
MANAGER_TELEGRAM_CHAT_ID=your_chat_idStep 2: Adapt the code to your niche (10 min)
Copy handle_message() from the theory section. Change SYSTEM_PROMPT: describe your business or your client's niche. Run it:
python bot.pyFind the bot in Telegram by its username → send /start → ask 7–10 questions as if you were a customer. Judge the quality, and refine the system prompt if needed.
Step 3: Set up a webhook in Teams (5 min)
- In your Teams (or ask your client): Channel → ⋯ → Workflows → the "Send webhook alerts to a channel" template
- Name it "AI Assistant Bot" and save
- Copy the webhook address → add it to
.envasTEAMS_WEBHOOK_URL - Run a test:
from dotenv import load_dotenv
load_dotenv()
notify_deal_closed("John Miller", 85000, "45 Calle Larga, Cuenca")Check the Teams channel: a card should appear.
Step 4: Add escalation to Telegram (5 min)
Copy the notify_manager() function from the WhatsApp section. Build it into the Telegram bot: when a user writes "I want to talk to a person" or "call me," Claude returns an escalation flag and you notify yourself.
Step 5: Document it as a case study (5 min)
- A screenshot of a conversation with the bot
- A screenshot of the Teams notification
- A description: which business it's for, what problem it solves
- A cost breakdown for the client (setup + maintenance)
- Add it to your portfolio
Bonus assignment:
Add a /contact command to the Telegram bot. When a user calls it, the bot asks for an email/phone number, saves it to a Google Sheet with the gspread library and notifies you on Telegram. That's your first lead capture tool.
Tools and resources
- python-telegram-bot: the standard for Telegram bots in Python, free
- Slack Bolt SDK: the official SDK for Slack apps, free
- Twilio WhatsApp: WhatsApp Business API, prices on Twilio's site
- 360dialog: a specialized WhatsApp API, pricing on the site
- Meta Cloud API: direct access to WhatsApp, complicated verification, Meta's pricing
- ManyChat: a no-code builder for direct message flows; current pricing and terms on the site
- Teams: webhooks through Workflows: notifications in Teams without a separate bot
- ngrok: a public URL for development, has a free plan
- Redis / Upstash: storing conversation history, has a free tier
Key takeaways
A messaging channel without an AI bot is like a website without a contact form: the customer shows up, nobody's there, the customer leaves. A bot catches inquiries that would otherwise be lost.
A well-configured Telegram bot takes on the routine questions and frees up your staff for the hard ones. The token cost of a typical conversation is a small fraction of what a person's working time costs, but calculate it for your own volume. For current prices and versions, see What's current.
You can sell bots to clients as a product, not just as one-off work: the core is reusable, and you swap the system prompt and branding for each niche. How much it brings in depends on your market and niche; there are no guarantees.
Further reading
→ No-code AI: v0, Webflow AI and Framer for business owners
The mark stays in this browser only and is never sent anywhere. My progress