ββββββββββββββββββββ βββββββββββββββ βββββββ βββ βββββββ
ββββββββββββββββββββ ββββββββββββββββββββββββ βββββββββββ
βββ ββββββ βββ ββββββ βββββββββββββββββ ββββββ ββββ
βββ ββββββ βββ ββββββ βββββββ ββββββββββββββββ βββ
βββ βββββββββββββββββββββββββββ ββββββ βββββββββββββββ
βββ βββββββββββββββββββββββββββ ββββββ βββββ βββββββ
like console.log but it arrives on your phone.
telegram alerts for solo builders β 1 import, 5 functions, zero deps.
// your stripe webhook just broke at 2am.
// you're asleep. teleping is not.
import { teleping } from 'teleping'
// simple β drop anywhere, fire-and-forget
teleping.success('New user', { email: 'alex@startup.com', plan: 'pro' })
teleping.error('Payment failed', { amount: 15, reason: 'card_declined' })
// builder β full context, tap [Copy for Claude] from your phone
teleping.error('Stripe webhook failed')
.data({ userId, amount, event: event.type })
.code(err.stack, 'typescript')
.button('claude') // one tap β error context in clipboard, ready for Claude
.button('cursor') // one tap β opens exact file:line in Cursor
.send()| DIY bot | teleping | |
|---|---|---|
| Setup | write it yourself | npm install teleping |
| Format | ugly plain text | emoji + separators + structured |
| 50 same errors | 50 messages, phone dies | 1 batched message |
| 3AM non-critical | wakes you | quiet hours suppress it |
| Error on phone | stare at it | [Open in Cursor] [Copy for Claude] |
| Every project | rewrite from scratch | 1 import |
You're vibe coding. Cursor, Claude, Vercel, Supabase β everything ships fast. But the moment code hits production, you're blind. You find out about broken payments from users. You find out about signups by checking your dashboard at noon.
teleping makes your app talk to you directly on Telegram. Every signup, payment, error, and metric β on your phone, beautifully formatted, the moment it happens.
shadcn/ui β beautiful components in 1 line
prisma β beautiful database queries in 1 line
teleping β beautiful production alerts in 1 line
npm install teleping- Message @BotFather on Telegram β create a bot β copy the token
- Start a chat with your new bot β get your chat ID
- Add to
.env:
TELEPING_TOKEN=your_bot_token
TELEPING_CHAT=your_chat_idOr run the CLI β it does all of this:
npx teleping init # creates .env with TELEPING_TOKEN + TELEPING_CHAT
npx teleping test # sends a test message to verifyimport { teleping } from 'teleping'
// five functions. that's the whole API.
teleping.log('Server started', { port: 3000 })
teleping.success('New user', { email, plan })
teleping.warn('Rate limit hit', { ip, endpoint })
teleping.error('Payment failed', { error: err.message, userId })
teleping.metric('Monthly revenue', 4500)What lands on your phone:
β
New user
βββββββββββββββββββββ
email alex@startup.com
plan pro
βββββββββββββββββββββ
myapp.com Β· 14:23
sent via teleping
π΄ Payment failed
βββββββββββββββββββββ
error Stripe timeout
userId usr_abc123
βββββββββββββββββββββ
myapp.com Β· 02:14
sent via teleping
[π Open in Cursor] [π€ Copy for Claude]
Call any method with only a label (no data object) to get a MessageBuilder. Chain modifiers, call .send() once. LLM-friendly β describe what you want in a comment and Claude writes the chain.
teleping.error('Stripe webhook failed')
.data({ userId, amount, stripeEvent: event.type }) // key-value context
.code(err.stack, 'typescript') // syntax-highlighted stack
.spoiler('userId') // hide behind tap-to-reveal
.expand() // collapsible data section
.button('claude') // Copy for Claude
.button('cursor') // Open in Cursor
.button('chatgpt') // Copy for ChatGPT
.button('copy-stack') // Copy raw stack
.button('copy-data') // Copy data as JSON
.button('View in Stripe', 'https://dashboard.stripe.com/...')
.copyButton('Copy event ID', event.id)
.send()| Method | Description |
|---|---|
.data(obj) |
Key-value context fields |
.value(n) |
Numeric value (metric builders) |
.code(content, lang?) |
Code block with optional syntax class |
.spoiler(...keys) |
Hide data fields behind Telegram spoiler tag |
.expand() |
Wrap data in collapsible blockquote |
.button(preset) |
Add button by preset name |
.button(text, url) |
URL button shorthand |
.copyButton(text, content) |
Copy-to-clipboard button β no webhook needed |
.send() |
Fire. Required. Call once at the end. |
Button presets:
| Preset | What it does |
|---|---|
'cursor' |
Opens exact file:line from stack trace in Cursor |
'claude' |
One tap copies full error context β paste into Claude |
'chatgpt' |
Same for ChatGPT |
'copy-stack' |
Copies raw stack trace |
'copy-data' |
Copies context data as formatted JSON |
Structured message formats β shadcn-style building blocks.
// Card β any structured event
teleping.card({
title: 'Deploy complete',
subtitle: 'production Β· v2.4.1',
fields: { duration: '48s', tests: '312 passed', coverage: '94%' },
level: 'success',
actions: [{ text: 'View on Vercel', url: 'https://vercel.com/...' }],
})
// Progress bar
teleping.progress('Importing users', { current: 847, total: 1200, unit: 'users' })
// β βββββββββββββββ 847/1200 users (71%)
// Table
teleping.table('Top plans today', [
{ plan: 'pro', signups: 14, revenue: '$420' },
{ plan: 'starter', signups: 38, revenue: '$190' },
])
// Checklist
teleping.checklist('Deploy checklist', [
{ label: 'Migrations run', done: true },
{ label: 'Cache warmed', done: true },
{ label: 'Smoke tests', done: false },
])You're building in Cursor, shipping to Vercel, debugging with Claude. teleping is built for that loop.
Add this to your CLAUDE.md once β Claude will instrument every new route correctly without you explaining it:
## Notifications (teleping)
API: teleping.log / .success / .warn / .error / .metric β fire-and-forget, no await
Builder: teleping.error('label').data({}).code(stack).button('claude').send()
Components: teleping.card() / .progress() / .table() / .checklist()
Config: TELEPING_TOKEN + TELEPING_CHAT in .env
Test: npx teleping testThe production debugging loop:
- Something breaks at 2AM
- Your phone:
π΄ Stripe webhook failedwith full stack - Tap [Copy for Claude] β error context is in clipboard
- Open Claude, paste, get the fix
- Or tap [Open in Cursor] β jumps to exact file and line
// app/api/auth/callback/route.ts
export async function GET(req: Request) {
const { data, error } = await supabase.auth.exchangeCodeForSession(code)
if (error) {
teleping.error('Auth failed', { error: error.message })
return redirect('/login?error=auth')
}
teleping.success('New signup', {
email: data.user.email,
provider: data.user.app_metadata.provider,
})
return redirect('/dashboard')
}// app/api/webhooks/stripe/route.ts
switch (event.type) {
case 'checkout.session.completed':
teleping.success('Payment received', {
amount: `$${session.amount_total / 100}`,
email: session.customer_email,
plan: session.metadata.plan,
})
break
case 'charge.failed':
teleping.error('Payment failed')
.data({ amount: `$${event.data.object.amount / 100}`, reason: event.data.object.failure_message })
.button('claude')
.send()
break
}// middleware.ts or app/api/_error.ts
export function onError(error: Error, req: Request) {
teleping.error(error.message, {
path: new URL(req.url).pathname,
stack: error.stack,
method: req.method,
})
}// app/api/cron/digest/route.ts
export async function GET() {
await teleping.digest()
// β π Digest β 42 events
// β
35 success π΄ 2 errors β οΈ 5 warnings
}50 signups in 5 minutes? One message, not 50:
β
50Γ New signup
βββββββββββββββββββββ
50 events batched
βββββββββββββββββββββ
myapp.com Β· 14:30
sent via teleping
teleping.init({
quietStart: 23, // 11 PM
quietEnd: 7, // 7 AM
timezone: 'America/New_York',
})Non-critical notifications hold until morning. Errors always go through.
Route errors to a dedicated Telegram topic, metrics to a separate chat:
teleping.init({
chatId: '-100main_chat',
routes: {
error: { threadId: '42' }, // β #errors topic
metric: { chatId: '-100metrics_chat' }, // β separate chat
},
})teleping.init({ theme: 'rich' }) // expandable blockquotes, code highlighting
teleping.init({ theme: 'minimal' }) // default β identical to v0.1
teleping.init({ theme: 'compact' }) // short separator, no boldNo TELEPING_TOKEN? One console.warn. All calls become silent no-ops. Safe in tests, CI, and dev.
teleping.init({
token: 'bot_token', // default: process.env.TELEPING_TOKEN
chatId: 'chat_id', // default: process.env.TELEPING_CHAT
app: 'myapp.com', // shown in every message footer
timezone: 'America/New_York', // for quiet hours
quietStart: 23, // hour 0β23
quietEnd: 7,
batchWindowMs: 300_000, // dedup window, default 5 min
theme: 'rich', // 'rich' | 'minimal' | 'compact'
separator: 'βββββββββββββ', // override separator line
footer: 'myapp v2.1 Β· prod', // extra line above "sent via teleping"
emoji: { error: 'π₯', success: 'π' }, // override any level emoji
routes: {
error: { chatId: '-100...', threadId: '42' },
metric: { chatId: '-100...' },
},
buttons: {
error: ['cursor', 'claude'], // add to every error message
default: [],
},
})| Method | When to use | Emoji |
|---|---|---|
teleping.log(label, data?) |
Server events, job complete | βΉοΈ |
teleping.success(label, data?) |
Signup, payment, milestone | β |
teleping.warn(label, data?) |
Rate limit, retry, degraded | |
teleping.error(label, data?) |
Exception, crash β always sends | π΄ |
teleping.metric(label, value) |
Revenue, user count, latency | π |
teleping.digest() |
Send stats summary, reset counters | π |
teleping.init(config) |
Explicit config, overrides env | β |
| Method | Signature |
|---|---|
teleping.card(opts) |
{ title, subtitle?, fields?, level?, actions? } |
teleping.progress(label, opts) |
label, { current, total, unit? } |
teleping.table(title, rows) |
title, { [col]: string | number | boolean }[] |
teleping.checklist(title, items) |
title, { label: string; done: boolean }[] |
import { editMessage } from 'teleping'
// live-update a message (e.g. deploy progress β deploy complete)
await editMessage({ token, chatId, messageId, text: 'Deploy complete β
' })npx teleping init # guided setup β creates .env
npx teleping test # sends a test message to verify config