The gist
A lecture is an architect showing you the blueprint of a house. A lab is you and a crew laying bricks day by day, and by Sunday the lights are on and the first tenant has moved in.
This lesson isn't theory. It's a step-by-step build of a real AI product, day by day through the week. Not "how people build Telegram bots in theory," but "here you are on Monday, here's what you do hour by hour, and on Sunday you have a bot deployed to production, your first client connected, and Stripe ready to send an invoice every month."
After the main lessons of the course, you know how it works in theory. Now you prove to yourself that you can ship a product end-to-end in a week.
Important: the amounts in this lesson (for example, $500 a month) are examples for the math, not an income forecast and not a pricing recommendation. Whether you find a client and how much they pay depends on your niche, your market and your work: the course doesn't guarantee results.
🎯 End state Day 7
By Sunday evening you have:
- ✅ A Telegram bot deployed on Cloudflare Workers, answering 24/7
- ✅ Your first client connected, with billing set up through Stripe (you agree on the amount together; $500/month in this lesson is an example)
- ✅ A connected tool (the Stripe API or email, depending on the niche)
- ✅ State management with Cloudflare KV (the bot remembers context)
- ✅ Rate limiting + cost guardrails (so the API doesn't bankrupt you)
- ✅ An audit log of every interaction
- ✅ Call monitoring (this lesson uses Helicone; see the note in Day 7)
- ✅ An onboarding template for clients #2-10
Economics: there's a sample calculation in the "Real cost breakdown" section at the end of the lesson. The numbers there are illustrative: run the math with your own plans and your own price.
📋 Pre-requisites
For this lab to make sense, you need:
- ✓ A paid Claude plan with Claude Code (Pro or Max; current prices: What's current)
- ✓ An API key from the Claude Console: the API is billed separately from the subscription
- ✓ Claude Code installed and working (see the lesson Installation and setup)
- ✓ A Cloudflare account (the free tier is enough)
- ✓ A Stripe account (for billing)
- ✓ A Telegram account
- ✓ The main course lessons completed (you understand CLAUDE.md, hooks and deploy basics)
- ✓ About 30 hours over 7 days (4-5 hours a day)
- ✓ Willingness to call people you don't know (Day 1 is discovery calls)
DAY 1 (5 hours): Niche + first customer interviews
Goal for the day: by evening you know a specific niche + a specific pain point, and 2-3 real potential clients have agreed to a call.
Hour 1: Pick a niche with the 3-question method
Don't pick "small business." That's not a niche, it's a universe. A niche is the intersection of:
- Which group of people do you know better than most? (real estate agents in Ecuador, dentists in Chicago, fitness trainers in Miami)
- Which of them hurt so much that they already pay for weak solutions? (for example, Calendly + Zoom + Notion + a freelance assistant)
- What part of that pain can a Telegram bot with an LLM inside solve? (lead qualification, appointment booking, FAQ auto-replies, follow-up sequences)
Write down your answers. Where the three answers overlap = your niche.
Hour 2: 20 potential clients
Open Google Sheets. Columns: name, company, contact, source, status.
20 rows. Sources: LinkedIn (search by niche), Instagram, local business groups and chats, referrals from people you know.
NO mass scraping. 20 real names, each one with a reason you can explain: "why this particular person is a fit."
Hour 3: Cold outreach to 5 contacts
Message template (adapt it to your niche):
Hi [Name]! I'm building an AI assistant for [niche]: it answers clients' common questions in Telegram, qualifies leads and books meetings. No canned scripts. I'm not selling anything, I'm researching the problem. Could you spare 15 minutes on a call to tell me how you handle clients right now? What drives you crazy? As a thank-you for your time, I'll send you a short industry summary (3 pages, 3 specific ideas for saving hours every week). What works for you, tomorrow or the day after?
Send 5 DMs, on different platforms. NO copy-paste: rewrite the first 2 lines for each person.
Hours 4-5: 2-3 discovery calls
Some of the 5 will reply; if nobody does, change the message or the niche. The call is 15 minutes. Script:
1. (2 min) Tell me about yourself and your business. 2. (5 min) How do you handle clients right now after first contact? Who answers messages? How many hours a day does it take? 3. (3 min) What are the 3 questions clients ask MOST OFTEN? 4. (2 min) If you had an assistant answering those 3 questions in Telegram 24/7, how much time would that save you, in hours per week? 5. (3 min) If a tool like that cost [your price] a month, would that be expensive, fair or cheap? Why?
Write it down word for word. Don't "optimize" it in your head.
Output Day 1
At the end of the day, in a Google Doc:
- Niche (one line)
- 3 client pain points (word-for-word quotes)
- 3 common client questions the bot will handle
- Reaction to your price (ready / too expensive / fair)
- The name of the first person willing to be a test client
If you don't have a first test client, don't start Day 2. Go back to Hour 3.
DAY 2 (5 hours): MVP design + CLAUDE.md + scaffold
Goal for the day: 3 happy paths designed + CLAUDE.md written + Worker scaffold created + bot registered with BotFather.
Hour 1: Conversation flows (3 happy paths)
On paper or in Miro, draw 3 conversations the bot has to handle end-to-end.
Example for a real estate agent in Ecuador:
Flow 1: Qualified lead
Client: Hi, I'm looking for an apartment in Cuenca under $80K
Bot: Hi! I can help you find one. 3 questions:
1. How many bedrooms do you need?
2. Does the neighborhood matter, or are you flexible?
3. When are you planning to move?
Client: 2 bedrooms, downtown, in 3 months
Bot: Got it. You're a great fit, so I'm passing you to our agent.
They'll call you today before 6 PM. Please confirm your phone number.Flow 2: FAQ auto-reply
Client: Do I need a visa to buy an apartment?
Bot: Foreigners can buy property in Ecuador without a visa.
You only need a passport + a RUC (you get it in 1 day).
If you'd like to discuss it with an agent, just type "call".The bot's answer in this example is illustrative: the bot takes legal facts only from an FAQ the client has checked.
Flow 3: Not our client
Client: Can you get me a mortgage?
Bot: We work with direct purchases, not mortgages.
For a mortgage I can recommend [partner]. Want their contact?Why do this now: on Day 3 you won't be inventing "what the bot should say." You'll only be building what's on the drawing.
Hour 2: CLAUDE.md system prompt
Create the project folder:
mkdir telegram-bot-prod && cd telegram-bot-prod
git initCLAUDE.md (this will be the system prompt for the LLM):
# Real estate assistant in Cuenca, Ecuador
## Role
You are the first point of contact for clients of a real estate agency in Cuenca.
You answer in Telegram. Your goal is to qualify the lead and hand them
off to a person, or to answer FAQ questions.
## Style
- Brief, friendly, polite and professional
- No filler, no emoji on every line
- If you don't know the answer, DO NOT make it up; say "I'll check with the agent"
## 3 happy paths
[Paste the flows from Hour 1 here]
## What you NEVER do
- Never promise prices without the agent's approval
- Never state legal facts ("100% legal")
- Never send the client's personal data anywhere outside
- Never handle mortgage topics (refer to the partner)
## When to hand off to a person
- The client is ready to view an apartment within a month
- The client asks a question that isn't in the FAQ
- The client writes "I want a call" / "call me"
## Tools
- save_lead(name, phone, budget, bedrooms) — saves to KV
- notify_realtor(lead_id) — sends a notification to the agentThis is the bot's product constitution. Change it and the behavior changes.
Hours 3-4: Cloudflare Worker scaffold
Setup:
npm create cloudflare@latest -- telegram-bot
# the wizard will ask for a template (Hello World) and a language (TypeScript); the command flags change,
# check the Cloudflare docs for the current version
cd telegram-bot
npm install @anthropic-ai/sdksrc/index.ts, a minimal scaffold:
import Anthropic from "@anthropic-ai/sdk";
export interface Env {
ANTHROPIC_API_KEY: string;
TELEGRAM_BOT_TOKEN: string;
TELEGRAM_WEBHOOK_SECRET: string;
BOT_KV: KVNamespace;
SYSTEM_PROMPT: string;
}
interface TelegramUpdate {
update_id: number;
message?: {
message_id: number;
from: { id: number; first_name: string; username?: string };
chat: { id: number };
text?: string;
};
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Webhook secret verification
const secret = request.headers.get("X-Telegram-Bot-Api-Secret-Token");
if (secret !== env.TELEGRAM_WEBHOOK_SECRET) {
return new Response("Forbidden", { status: 403 });
}
if (request.method !== "POST") {
return new Response("Method Not Allowed", { status: 405 });
}
const update: TelegramUpdate = await request.json();
if (!update.message?.text) {
return new Response("OK"); // ignore non-text
}
const chatId = update.message.chat.id;
const userText = update.message.text;
// TODO Day 3: real LLM call here
const reply = `Echo: ${userText}`;
await sendTelegramMessage(env, chatId, reply);
return new Response("OK");
},
};
async function sendTelegramMessage(env: Env, chatId: number, text: string) {
const url = `https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/sendMessage`;
await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ chat_id: chatId, text }),
});
}wrangler.toml:
name = "telegram-bot"
main = "src/index.ts"
compatibility_date = "2026-10-01"
[[kv_namespaces]]
binding = "BOT_KV"
id = "your-kv-namespace-id" # we'll create it laterCloudflare creates new projects with a wrangler.jsonc file; the wrangler.toml format is also supported. If the wizard created wrangler.jsonc, move the same fields there.
Set your secrets (NEVER commit them):
wrangler secret put ANTHROPIC_API_KEY
wrangler secret put TELEGRAM_BOT_TOKEN
wrangler secret put TELEGRAM_WEBHOOK_SECRET # generate with openssl rand -hex 32Hour 5: Telegram bot via BotFather
In Telegram → @BotFather → /newbot → name + username → you get a token.
Connect the webhook (after the first wrangler deploy):
WEBHOOK_URL="https://telegram-bot.your-subdomain.workers.dev"
SECRET="<same as in wrangler secret>"
curl -X POST "https://api.telegram.org/bot${BOT_TOKEN}/setWebhook" \
-H "Content-Type: application/json" \
-d "{\"url\":\"${WEBHOOK_URL}\",\"secret_token\":\"${SECRET}\"}"Send the bot "hello" in Telegram. It should reply "Echo: hello". If it works, move on to Day 3.
DAY 3 (5 hours): Core conversation handling
Goal for the day: the bot gives meaningful answers through the LLM, errors don't crash it, and every interaction goes into the audit log.
Hours 1-3: Webhook + LLM + response
Replace // TODO Day 3 with a real call:
import Anthropic from "@anthropic-ai/sdk";
async function generateReply(
env: Env,
userText: string,
conversationHistory: Array<{ role: "user" | "assistant"; content: string }>,
): Promise<string> {
const client = new Anthropic({ apiKey: env.ANTHROPIC_API_KEY });
const response = await client.messages.create({
model: "claude-sonnet-5-5", // move this to an environment variable; current models: "What's current"
max_tokens: 500,
system: env.SYSTEM_PROMPT, // loaded from KV or wrangler vars
messages: [
...conversationHistory,
{ role: "user", content: userText },
],
});
// Extract the text from the response
const textBlock = response.content.find((b) => b.type === "text");
return textBlock && textBlock.type === "text"
? textBlock.text
: "Sorry, I didn't understand. Could you rephrase that?";
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const secret = request.headers.get("X-Telegram-Bot-Api-Secret-Token");
if (secret !== env.TELEGRAM_WEBHOOK_SECRET) {
return new Response("Forbidden", { status: 403 });
}
if (request.method !== "POST") {
return new Response("Method Not Allowed", { status: 405 });
}
const update: TelegramUpdate = await request.json();
if (!update.message?.text) return new Response("OK");
const chatId = update.message.chat.id;
const userText = update.message.text;
try {
// Load the conversation history from KV (we'll extend this on Day 4)
const historyKey = `chat:${chatId}:history`;
const historyRaw = await env.BOT_KV.get(historyKey);
const history: Array<{ role: "user" | "assistant"; content: string }> =
historyRaw ? JSON.parse(historyRaw) : [];
// Generate the reply
const reply = await generateReply(env, userText, history);
// Update the history (last 10 messages)
const updatedHistory = [
...history,
{ role: "user" as const, content: userText },
{ role: "assistant" as const, content: reply },
].slice(-20);
await env.BOT_KV.put(historyKey, JSON.stringify(updatedHistory), {
expirationTtl: 60 * 60 * 24 * 7, // 7 days
});
// Audit log
await logEvent(env, {
ts: new Date().toISOString(),
chat_id: chatId,
user_text: userText,
bot_reply: reply,
tokens: 0, // TODO fill in from the response
});
await sendTelegramMessage(env, chatId, reply);
} catch (e) {
console.error("Bot error:", e);
await sendTelegramMessage(
env,
chatId,
"Sorry, there's a temporary problem. Please try again in a minute.",
);
}
return new Response("OK");
},
};Hours 4-5: Error handling + audit log
logEvent writes to KV or an external service:
interface AuditEntry {
ts: string;
chat_id: number;
user_text: string;
bot_reply: string;
tokens: number;
}
async function logEvent(env: Env, entry: AuditEntry): Promise<void> {
const key = `audit:${entry.ts}:${entry.chat_id}`;
await env.BOT_KV.put(key, JSON.stringify(entry), {
expirationTtl: 60 * 60 * 24 * 90, // 90 days retention
});
}Defensive patterns:
// Limit on the length of the user's message
if (userText.length > 1000) {
await sendTelegramMessage(
env,
chatId,
"Your message is too long. Please shorten it to 1,000 characters.",
);
return new Response("OK");
}
// Timeout for the LLM call
const replyPromise = generateReply(env, userText, history);
const timeoutPromise = new Promise<string>((_, reject) =>
setTimeout(() => reject(new Error("LLM timeout")), 15000),
);
const reply = await Promise.race([replyPromise, timeoutPromise]);Deploy:
wrangler deploySend the bot 5 different messages. Every one should get a meaningful reply. The conversation history is saved (try "do you remember what I told you earlier?").
DAY 4 (5 hours): Connect tools (MCP integration + state)
Goal for the day: the bot does more than talk: it actually works with tools (Stripe, email or your use case's API).
Hours 1-2: Tool integration
For the real estate agent in Ecuador, we need to: save the lead + notify the agent in Telegram.
Extend CLAUDE.md (the system prompt) with tools:
const tools: Anthropic.Tool[] = [
{
name: "save_lead",
description: "Saves a qualified lead to the database",
input_schema: {
type: "object",
properties: {
name: { type: "string", description: "Client's name" },
phone: { type: "string", description: "Phone number with country code" },
budget_usd: { type: "number", description: "Budget in USD" },
bedrooms: { type: "number", description: "Number of bedrooms" },
timeline_months: { type: "number", description: "Time until the move" },
},
required: ["name", "phone", "budget_usd", "bedrooms"],
},
},
{
name: "notify_realtor",
description: "Sends the agent a notification about a new lead",
input_schema: {
type: "object",
properties: {
lead_id: { type: "string" },
urgency: { type: "string", enum: ["high", "medium", "low"] },
},
required: ["lead_id", "urgency"],
},
},
];Tool execution loop:
async function generateReplyWithTools(
env: Env,
userText: string,
history: Array<Anthropic.MessageParam>,
): Promise<string> {
const client = new Anthropic({ apiKey: env.ANTHROPIC_API_KEY });
const messages: Array<Anthropic.MessageParam> = [
...history,
{ role: "user", content: userText },
];
// Loop max 5 times to avoid infinite tool calls
for (let i = 0; i < 5; i++) {
const response = await client.messages.create({
model: "claude-sonnet-5-5", // move this to an environment variable; current models: "What's current"
max_tokens: 1000,
system: env.SYSTEM_PROMPT,
tools,
messages,
});
if (response.stop_reason === "end_turn") {
const textBlock = response.content.find((b) => b.type === "text");
return textBlock && textBlock.type === "text" ? textBlock.text : "";
}
if (response.stop_reason === "tool_use") {
messages.push({ role: "assistant", content: response.content });
const toolResults: Array<Anthropic.ToolResultBlockParam> = [];
for (const block of response.content) {
if (block.type === "tool_use") {
const result = await executeTool(env, block.name, block.input);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: JSON.stringify(result),
});
}
}
messages.push({ role: "user", content: toolResults });
continue;
}
break;
}
return "Sorry, I couldn't process your request. Please try again.";
}
async function executeTool(
env: Env,
name: string,
input: unknown,
): Promise<unknown> {
if (name === "save_lead") {
const lead = input as {
name: string;
phone: string;
budget_usd: number;
bedrooms: number;
};
const leadId = `lead_${Date.now()}`;
await env.BOT_KV.put(`leads:${leadId}`, JSON.stringify(lead));
return { lead_id: leadId, status: "saved" };
}
if (name === "notify_realtor") {
const { lead_id, urgency } = input as { lead_id: string; urgency: string };
// Real notification via Telegram to the agent's chat
const realtorChatId = Number(env.REALTOR_CHAT_ID);
await sendTelegramMessage(
env,
realtorChatId,
`New lead (${urgency}): ${lead_id}`,
);
return { status: "notified" };
}
return { error: `Unknown tool: ${name}` };
}Hours 3-4: State management (KV pattern)
We already did part of this on Day 3. Now we extend it: we store not only the history but also a client profile:
interface ClientProfile {
chat_id: number;
first_seen: string;
name?: string;
phone?: string;
budget_usd?: number;
bedrooms?: number;
status: "new" | "qualifying" | "qualified" | "passed_to_realtor";
last_interaction: string;
}
async function loadProfile(env: Env, chatId: number): Promise<ClientProfile> {
const raw = await env.BOT_KV.get(`profile:${chatId}`);
if (raw) return JSON.parse(raw);
return {
chat_id: chatId,
first_seen: new Date().toISOString(),
status: "new",
last_interaction: new Date().toISOString(),
};
}
async function saveProfile(env: Env, profile: ClientProfile): Promise<void> {
profile.last_interaction = new Date().toISOString();
await env.BOT_KV.put(`profile:${profile.chat_id}`, JSON.stringify(profile), {
expirationTtl: 60 * 60 * 24 * 365, // one year
});
}Pass the profile into the system prompt so the bot remembers context between sessions:
const profile = await loadProfile(env, chatId);
const systemWithContext = `${env.SYSTEM_PROMPT}
## Current client profile
${JSON.stringify(profile, null, 2)}
Use this information so you don't ask questions you already know
the answer to. Update the profile through tools.`;Hour 5: Testing harness
Create tests/conversation.test.ts:
import { describe, test, expect } from "vitest";
describe("Bot conversation flows", () => {
test("Flow 1: qualified lead gets saved", async () => {
const responses = await simulateConversation([
"Hi, I'm looking for an apartment in Cuenca under $80K",
"2 bedrooms, downtown, in 3 months",
"My name is [client], reach me through the bot",
]);
expect(responses).toContain("got it");
// Check that the lead is saved in KV
const leads = await env.BOT_KV.list({ prefix: "leads:" });
expect(leads.keys.length).toBeGreaterThan(0);
});
test("Flow 2: FAQ is answered without handing off to the agent", async () => {
const reply = await simulateConversation([
"Do I need a visa to buy an apartment?",
]);
expect(reply[0]).toMatch(/visa.*not needed|without a visa/i);
});
test("Flow 3: mortgage → referral to partner", async () => {
const reply = await simulateConversation(["Can you get me a mortgage?"]);
expect(reply[0]).toMatch(/mortgage|partner/i);
});
});Run: npx vitest. All 3 tests should be green.
DAY 5 (4 hours): Polish + safety + production deploy
Goal for the day: the bot won't bankrupt you, won't fall over under load, and is deployed to production.
Hour 1: Rate limiting per user
Don't let one client burn through your API key:
async function checkRateLimit(env: Env, chatId: number): Promise<boolean> {
const key = `ratelimit:${chatId}`;
const countRaw = await env.BOT_KV.get(key);
const count = countRaw ? parseInt(countRaw) : 0;
if (count >= 30) return false; // max 30 messages per minute
await env.BOT_KV.put(key, String(count + 1), { expirationTtl: 60 });
return true;
}
// In the fetch handler:
if (!(await checkRateLimit(env, chatId))) {
await sendTelegramMessage(
env,
chatId,
"Too many messages. Please wait a minute.",
);
return new Response("OK");
}Hour 2: Cost guardrails
A daily cost cap so that a random bug doesn't burn extra money overnight:
async function checkDailyCost(env: Env): Promise<boolean> {
const today = new Date().toISOString().split("T")[0];
const key = `cost:${today}`;
const costRaw = await env.BOT_KV.get(key);
const costCents = costRaw ? parseInt(costRaw) : 0;
return costCents < 1000; // $10/day hard cap
}
async function recordCost(env: Env, tokens: number): Promise<void> {
const today = new Date().toISOString().split("T")[0];
const key = `cost:${today}`;
const costRaw = await env.BOT_KV.get(key);
const currentCents = costRaw ? parseInt(costRaw) : 0;
// A rough, deliberately high estimate: 1 cent per 100 tokens. It's a safety fuse;
// current token prices are on the "What's current" page
const addCents = Math.ceil(tokens / 100);
await env.BOT_KV.put(key, String(currentCents + addCents), {
expirationTtl: 60 * 60 * 48,
});
}
// In fetch:
if (!(await checkDailyCost(env))) {
await sendTelegramMessage(
env,
chatId,
"The service is temporarily overloaded. It will be back tomorrow.",
);
return new Response("OK");
}Hour 3: Helpful error messages
Templates for common errors (instead of a stack trace):
const ERROR_MESSAGES = {
timeout:
"I'm taking too long to think. Try asking a simpler question or try again in a minute.",
rate_limit:
"Too many messages in a row. Give me a minute and I'll answer.",
api_down:
"We're having technical problems. The agent has been notified and will call you personally.",
unknown_topic:
"I didn't quite understand your question. I'm passing it to the agent, who will call you within an hour.",
};Hour 4: Production deploy
# Check the secrets in production
wrangler secret list
# Deploy
wrangler deploy
# Verify webhook
curl "https://api.telegram.org/bot${BOT_TOKEN}/getWebhookInfo"In the Cloudflare dashboard → Workers → your bot → Logs (real-time). You should see incoming requests.
Smoke tests in production:
- Full Flow 1 (qualified lead)
- Flow 2 (FAQ)
- Flow 3 (not our client)
- Edge case: a long message (>1,000 characters)
- Edge case: 35 messages in a minute (rate limit kicks in)
All passed? On to Day 6.
DAY 6 (5 hours): First customer onboarding
Goal for the day: a real client starts using the bot for their business. By the end of the day, the first leads come in through the bot.
Hours 1-2: Customer onboarding flow
Create an onboarding checklist (in Notion or Markdown):
# Client onboarding checklist ## Before the call (we do this) - [ ] A dedicated Telegram bot created for the client (its own token) - [ ] CLAUDE.md adapted to the client's business - [ ] Company name, tone of voice - [ ] 3 happy paths from their real conversations - [ ] FAQ from their knowledge base (5-10 common questions) - [ ] Tools configured (if lead saving is needed, where to write) - [ ] The agent's notification chat obtained and tested ## On the call (30 minutes) - [ ] Show the bot in action (3 happy paths live) - [ ] Explain how to edit the FAQ (access to CLAUDE.md) - [ ] Explain how to view leads (KV or Google Sheet sync) - [ ] Show how to add you to the chat if the bot gets stuck - [ ] Discuss billing: the amount and payment method through Stripe, invoice by email - [ ] Agree on a trial period (7 days) or the first payment right away ## After the call - [ ] Send the Stripe invoice - [ ] Connect the production webhook to their Telegram channel - [ ] Set up monitoring on their traffic (Helicone tag by client_id) - [ ] Send a follow-up after 24 hours: "How was the first day?"
Hours 3-4: 30-min onboarding call
Structure:
0-5 min: small talk + context 5-15 min: live demo (3 happy paths) 15-25 min: tailoring to their business (live edits to CLAUDE.md) 25-30 min: billing + next steps
The main rule: the client must send the bot its first message themselves during the call and get a reply. That's the "wow" moment that sells.
Hour 5: Handle first-day live issues
What will happen most often:
- The bot gave the wrong answer to a specific question → edit CLAUDE.md (the FAQ section)
- A tool didn't work right → edit the tool description / parameters
- The client wants another happy path → add it
Commit every fix:
git commit -m "fix(bot): add FAQ about the agent's commission"
wrangler deployThe feedback loop is minutes, not days. That's what live ops means.
Output Day 6
- The bot is in production for the client
- 2-5 leads have gone through the bot (at least in test mode)
- The client has seen real value and is ready to pay
DAY 7 (3 hours): Pricing + sustainability
Goal for the day: money received, monitoring set up, a plan for week 2.
Hour 1: Stripe invoice
# Through the Stripe CLI or the dashboard
stripe customers create \
--name="Client name" \
--email="<client_email>"
# Invoice
stripe invoices create \
--customer=cus_XXX \
--collection_method=send_invoice \
--days_until_due=7
# The amount below is an example (50000 cents = $500); use your own price
stripe invoice_items create \
--customer=cus_XXX \
--invoice=in_XXX \
--description="Telegram AI bot — 1 month" \
--amount=50000 \
--currency=usd
stripe invoices send --id=in_XXXOr simpler: in the Stripe Dashboard → Invoices → Create. 5 minutes by hand.
For recurring billing (Month 2+): create a Subscription at the price you agreed on, with autopay through the Stripe Customer Portal.
Hour 2: Call monitoring (Helicone)
This lesson uses Helicone for monitoring: a proxy between your code and the API that shows every call and what it cost. Important: since March 2026 Helicone has been part of Mintlify and is in maintenance mode (fixes and support for new models, no new features). Check the service's website for the terms of the free tier. Alternatives: Langfuse, Portkey or the usage section in the Claude Console.
// Replace the Anthropic SDK init:
const client = new Anthropic({
apiKey: env.ANTHROPIC_API_KEY,
baseURL: "https://anthropic.helicone.ai", // proxy through Helicone
defaultHeaders: {
"Helicone-Auth": `Bearer ${env.HELICONE_API_KEY}`,
"Helicone-Property-Client": "client_001", // tag per client
},
});Sign up at helicone.ai → copy the key → wrangler secret put HELICONE_API_KEY. You see every call: cost, latency, error rate. No dashboard of your own needed.
Hour 3: Retrospective + week 2 plan
Open a notebook and answer honestly:
- What worked? (for example: cold DMs work if the first line is personal)
- What DIDN'T work? (for example: 2 of 5 potential clients said no at the pricing stage; too expensive for them)
- What did I learn about the niche? (for example: they already pay for Calendly and a freelance assistant, so the price has to be justified with extra capabilities)
- One next client: who, and how?
Set up a Week 2 spreadsheet:
Day 8: 10 outreach DMs (use what worked in Week 1) Day 9: 3 discovery calls Day 10: 1 onboarding call Day 11: support for the current client + fixes based on feedback Day 12: 2 onboarding calls (if 2 leads are ready) Day 13: invoice 2 new clients Day 14: Week 2 retro
Week 2 goal: widen the funnel: more conversations and more onboardings. Set the specific numbers yourself based on the results of week one.
💰 Real cost breakdown
The numbers below are the author's estimates, to show how the costs are structured. This is not a price list and not an income forecast. Current token and plan prices: What's current.
| Period | Your costs | Source |
|---|---|---|
| Day 1-2 (research + design) | $0 | time only |
| Day 3 (LLM testing) | small | Anthropic API smoke tests |
| Day 4 (tool dev) | small | tool execution tests |
| Day 5 (load testing) | small | rate limit + cost cap checks |
| Day 6 (live with client) | small | real conversations |
| Day 7 (Stripe + monitoring) | $0 | free tiers of the services |
| Total setup | tens of dollars | depends on plans and number of tests |
| Month 1 ops (1 client) | LLM + Cloudflare | Workers Free: 100,000 requests per day; paid plan from $5 a month (as of October 2026) |
| Revenue | depends on your agreement | you set the price and terms |
What eats into the economics as you grow:
- More requests than the free Workers plan allows → a paid Cloudflare plan
- Token usage grows → prompt caching (repeated context costs noticeably less) and the Batch API for non-urgent tasks
- Support takes more than a couple of hours a week → a virtual assistant or automation
As the number of clients grows, so does support. Work out the economics per client in advance: price minus tokens, infrastructure and your time.
⚠️ Common pitfalls
Day 1: the niche is too broad
❌ "An AI bot for small businesses" ✅ "An AI bot for real estate agents in Cuenca who sell to foreigners"
Broad niche = generic outreach = almost no replies.
Day 3: skipping error handling
If on Day 3 you go "no try/catch for now, I'll add it later," then on Day 6 the bot will crash during the live call in front of the client. In front of the client. That's not a story you want to tell.
Day 6: forgot Stripe ToS / refund policy
Without a written refund policy, the first unhappy client will file a chargeback and Stripe may restrict your account. The simplest version: "Pro-rated refund within the first 14 days, no refunds after that, you can cancel the subscription at any time." Refund rules depend on the country; this is not legal advice.
Day 7: didn't set up monitoring
Two weeks in, one tool execution starts failing on an edge case. You find out from the client: "it's not working for us." With monitoring (Helicone, for example), you find out yourself, not from the client.
Any day: vendor lock-in
If you hardcode the model name in 15 places, switching to Haiku or Gemini for cost optimization will take a day of work. A single MODEL_NAME constant in env vars solves the problem in 30 seconds.
✅ What you have at end Day 7
Concretely. Not "potential." Not "experience." Real artifacts:
- A working Telegram bot in Cloudflare Workers production: answers 24/7, deployed, monitored
- First client connected: payment terms agreed
- Stripe billing: invoice sent, recurring set up (if the client agreed to pay)
- Audit log: every interaction stored for 90 days (the basis for audits)
- Cost monitoring: a monitoring dashboard where you see tokens and cost per client
- Rate limiting + cost cap: the system won't go broke from a bug or abuse
- Customer onboarding template: for clients #2-10 (no longer from scratch)
- 3 verified happy paths: they really work for a real business
- Cold outreach playbook: what to write, on which channel, and the response rate you got
- Retrospective: what works, what doesn't, the Week 2 plan
📈 Scaling plan (Week 2-4, briefly)
Week 2 (widen the funnel):
- 30 cold DMs using the Week 1 templates that worked
- 6-8 discovery calls
- 2-3 onboardings
- Cost: 25 hours of your time
Week 3-4 (templatize):
- Templatize onboarding (auto-setup of a new bot in 30 minutes)
- A virtual assistant for building lists and handling first-touch messages
- Customer success: weekly check-in emails (automated)
- Cost: 20 hours / week
Month 2:
- Referrals may start coming in
- You can revisit the price for new clients, if the niche allows it
- Think about "white label": a bot under the client's brand (premium tier)
Where NOT to scale:
- Don't build "an AI bot for everything": you lose your niche
- Don't hire salespeople early: it's too soon
- Don't build your own dashboard: off-the-shelf monitoring will be enough for a long time
🔗 Cross-references
- Telegram bots with the Claude API: Telegram bot theory (what a webhook is, BotFather, polling vs. webhook)
- Hooks: automatic rules and Hooks LIVE: Claude Code hooks (for your local dev workflow)
- Deployment: Cloudflare Workers: deploy basics (Cloudflare Workers, secrets, wrangler)
- First clients: first clients and cold outreach methodology
- Multi-agent orchestration: if you want several bots with different roles
📚 Sources
- Cloudflare Workers Telegram Tutorial — https://developers.cloudflare.com/workers/tutorials/
- Telegram Bot API — https://core.telegram.org/bots/api
- Anthropic SDK for TypeScript — https://github.com/anthropics/anthropic-sdk-typescript
- Stripe Subscriptions — https://docs.stripe.com/billing/subscriptions/overview
- Helicone (LLM monitoring) — https://www.helicone.ai/
- Cloudflare KV — https://developers.cloudflare.com/kv/
Key takeaways
A lab isn't a lesson. It's 7 days of real work where the result is a working bot in production and a first client you've agreed on terms with. If on Sunday evening all you have is "potential," the lab didn't work out: start Week 2 as if it were Week 1.
Customer first, code second. Day 1 goes to discovery calls, not code. Without a validated pain point, even the most beautiful bot is useless to everyone. Without a paid contract, the product doesn't exist yet.
A bot's economics aren't AI magic. They're the right niche + the right price + the right guardrails. The price is justified when it saves the client time that's worth more than your service; work that out with the numbers of a specific client.
Next lesson
The mark stays in this browser only and is never sent anywhere. My progress