production

a practical checklist for running yaebal bots under real traffic: retries, rate limits, concurrency, webhooks, shutdown, tests, and observability.

the baseline stack

most production bots need three guards: retry transient telegram failures, throttle outgoing calls to avoid 429s, and rate-limit abusive users before expensive handlers run.

bot.ts
import { createBot } from "yaebal";
import { autoRetry } from "@yaebal/again";
import { throttle } from "@yaebal/throttle";
import { ratelimiter } from "@yaebal/ratelimiter";

const bot = createBot(process.env.BOT_TOKEN!)
  .install(throttle({ globalPerSec: 30, perChatPerSec: 1, perGroupPerMin: 20 }))
  .install(autoRetry({ maxRetries: 5, maxDelayMs: 10_000, retryAfterPaddingMs: 250 }))
  .install(ratelimiter({ limit: 5, windowMs: 1000 }));
piecewhy
@yaebal/againawait structured retry_after waits and transient 5xx errors
@yaebal/throttleschedule outgoing API calls through global/private/group buckets
@yaebal/ratelimiterdrop spammy incoming updates before they hit business logic

polling at scale

plain bot.start() processes updates sequentially. use @yaebal/runner when one slow handler should not block unrelated chats. keep per-chat ordering enabled if handlers read and write session state.

runner.ts
import { run, chatKey } from "@yaebal/runner";

const handle = run(bot, {
  concurrency: 50,
  sequentializeBy: chatKey,
  allowedUpdates: ["message", "callback_query"],
  onError: (error, update) => {
    console.error("update failed", update?.update_id, error);
  },
});

process.once("SIGTERM", () => void handle.stop());
ordering is a correctness feature. two updates from the same chat running at the same time can lose session writes. chatKey serializes each chat while unrelated chats still run in parallel.

webhooks for serverless and edge

for cloudflare workers, deno deploy, vercel edge, and similar runtimes, use a fetch-style webhook. the handler validates the secret token in constant time and rejects oversized bodies.

worker.ts
import { createBot, webhook } from "yaebal";

const bot = createBot(env.BOT_TOKEN);
bot.command("start", (ctx) => ctx.reply("running"));

export default {
  fetch: webhook(bot, { secretToken: env.WEBHOOK_SECRET }),
};

register the webhook during deploy with @yaebal/web or the raw bot api. keep the secret token in your host's secret store, not in source.

graceful shutdown

on polling deployments, catch SIGTERM and let the bot stop cleanly. with runner, handle.stop() stops polling and drains in-flight updates. with plain polling, bot.stop() stops the loop.

shutdown.ts
let stopping = false;

process.once("SIGTERM", async () => {
  if (stopping) return;
  stopping = true;
  await bot.stop();
});

observability

use api hooks for request-level logs/metrics and bot.onError for handler failures. do not log tokens, file download urls, raw secrets, or full user payloads in production logs.

observability.ts
bot.api.before((method, params) => {
  console.log("telegram ->", method);
  return params;
});

bot.api.after((method) => {
  console.log("telegram <-", method, "ok");
});

bot.onError((error, ctx) => {
  console.error("handler failed", ctx.update.update_id, error);
});

broadcasts

for large audiences, use @yaebal/broadcast with persistent storage, bounded retry and an explicit rate limit. keep skipped recipients for cleanup, and remove users that blocked the bot when telegram returns 403.

state and storage

stateproduction advice
sessionsuse persistent storage for anything that must survive restarts; memory storage is dev-only
scenes/conversationsprefer durable state for long flows; short prompt-style flows can be in-memory
media cachecache file ids so repeated sends do not re-upload the same bytes

pre-release checklist

  • run pnpm typecheck and your bot tests.
  • exercise critical flows with @yaebal/test.
  • set allowedUpdates explicitly for the update kinds you use.
  • use webhook secret tokens in production.
  • keep one polling process per token, or use webhooks.
  • set a broadcast rate limit and storage adapter before large outbound flows.
  • document how to rotate the bot token and webhook secret.