AI-powered support chat with human escalation via Telegram.
Claude handles first-line support. You handle escalations from your phone.
No Intercom. No Crisp. No $50/mo. Just Claude + a Telegram bot you already have.
Customer types → Claude answers with context + tools
│
Can't resolve? ─→ Escalation via Telegram
│
You reply on your phone
│
Customer sees it in real-time (SSE)
│
Send /done → AI takes over again
- AI first-line support — Claude answers with live context you inject (user info, product state, etc.)
- Custom tools — give Claude actions it can take (create ticket, trigger retry, etc.)
- Telegram escalation — when AI can't resolve it, you get a Telegram notification with the last 5 turns of context
- Bidirectional bridge — reply from Telegram, customer sees it instantly via SSE
/donehandoff — one command returns the session back to AI- Zero vendor lock-in — your Postgres, your Redis, your bot
- Next.js 15 (App Router)
- Claude (Anthropic SDK —
claude-sonnet-4-6by default) - Drizzle ORM + PostgreSQL
- ioredis for the Telegram → SSE bridge
- Telegram Bot API
git clone https://github.com/MarekFish93/huginn.git
cd huginn
npm installcp .env.example .env.localFill in .env.local:
| Variable | Where to get it |
|---|---|
DATABASE_URL |
Any Postgres — Supabase, Neon, Railway, or self-hosted |
REDIS_URL |
Any Redis — Upstash, Railway, or self-hosted |
ANTHROPIC_API_KEY |
console.anthropic.com |
TELEGRAM_BOT_TOKEN |
Create a bot via @BotFather |
TELEGRAM_CHAT_ID |
Your chat ID — get it from @userinfobot |
TELEGRAM_WEBHOOK_SECRET |
Random string — openssl rand -hex 32 |
NEXT_PUBLIC_APP_URL |
Your deployment URL (no trailing slash) |
npm run db:generate
npm run db:migratenpm run webhook:registerThis tells Telegram where to send incoming messages. Run once after each domain change.
npm run devOpen http://localhost:3000 and try the demo.
Edit src/lib/chat/system-prompt.ts. The context object is whatever you pass from your frontend — user info, subscription state, product context, anything.
// In your frontend:
<ChatWidget
sessionId={sessionId}
userName={user.name}
context={{
productName: 'Acme SaaS',
planName: user.plan,
accountAge: user.createdAt,
}}
/>Edit src/lib/chat/tools.ts. Two parts:
- Add a tool definition to
SUPPORT_TOOLS(this is what Claude sees) - Add a handler to
executeTool()(this is what runs when Claude calls it)
// Example: let Claude check order status
export const SUPPORT_TOOLS: Tool[] = [
{
name: 'getOrderStatus',
description: 'Look up the status of a customer order.',
input_schema: {
type: 'object',
properties: { orderId: { type: 'string' } },
required: ['orderId'],
},
},
]
export async function executeTool(name: string, input: Record<string, unknown>, ctx: ToolContext) {
if (name === 'getOrderStatus') {
const order = await db.query.orders.findFirst({ where: eq(orders.id, input.orderId as string) })
return order ? `Order status: ${order.status}` : 'Order not found.'
}
}You don't need to use the demo UI. The only required pieces are:
src/lib/escalation.ts— Telegram send + sentinel detectionsrc/lib/chat/system-prompt.ts— system prompt buildersrc/lib/chat/tools.ts— tool definitions + executorsrc/app/api/chat/[id]/route.ts— POST (AI loop) + GET (SSE)src/app/api/webhooks/telegram/route.ts— Telegram webhook
Copy these into your Next.js app, set the env vars, run the migration, register the webhook, done.
The chat UI and AI responses work out of the box with npm run dev. However, Telegram webhooks require a public HTTPS URL — Telegram cannot reach localhost.
To test the full escalation flow locally, expose your dev server with a tunnel:
# Option A — ngrok (free tier works)
ngrok http 3000
# Copy the https://xxxx.ngrok.io URL → set as NEXT_PUBLIC_APP_URL in .env.local
# Then re-run: npm run webhook:register
# Option B — Cloudflare Tunnel (free, no account needed for quick tests)
npx cloudflared tunnel --url http://localhost:3000Everything except Telegram replies can be tested without a tunnel.
Works on any Node.js host. Redis must be reachable from your server (SSE requires a persistent connection — Vercel Edge is not supported).
Recommended hosts: Railway, Render, Fly.io, or a VPS.
Do I need Claude specifically?
The escalation pattern is model-agnostic. Swap claude-sonnet-4-6 for any model you prefer in src/app/api/chat/[id]/route.ts.
Can I use a group chat instead of a personal Telegram chat?
Yes — set TELEGRAM_CHAT_ID to a group ID. The bot must be a member with message permissions.
What if Telegram is down?
Escalation fails gracefully — the customer is told the team will follow up. The session is not marked as escalated so AI continues to handle it.
Is this production-ready?
The core pattern is battle-tested. What you need to add for production: authentication on the chat route, rate limiting, and error monitoring.
MIT — use it, fork it, build on it.
Built by Jarnhaus.