The grammY framework has become one of the most popular tools for developing Telegram-бот s on Node.js thanks to its excellent architecture, plugin support, and high performance. However, writing a basic "Hello World" is very different from creating a fault-tolerant service ready for real loads and business data processing.
In this article, we will examine building a bot architecture for lead generation. We will cover secure initialization via переменные окружения, setting up step-by-step conversations, switching between Long Polling and Webhooks based on Express, as well as handling Telegram Bot API limits correctly (error 429).
1. Project Initialization and Secure Configuration
Never store your bot token in the source code. To manage configuration in Node.js, the dotenv package is used, while the token itself is moved to a .env file. Make sure that the .env file is added to .gitignore.
To create a full-featured bot, we will need the grammy, @grammyjs/conversations (for managing conversation scenarios), and @grammyjs/auto-retry (for handling API rate limits) packages.
require('dotenv').config();
const { Bot, session } = require('grammy');
const botToken = process.env.BOT_TOKEN;
if (!botToken) {
throw new Error('Ошибка: Переменная окружения BOT_TOKEN не задана!');
}
const bot = new Bot(botToken);2. Organizing Step-by-Step Conversations for Lead Generation
A typical task for a commercial bot is to collect contact details from a user (name, phone number, request details) and transfer them to a CRM. In grammY, the officially supported conversations plugin is used for this purpose. It allows you to write asynchronous conversation code as if it were a linear script, without turning the logic into a chain of scattered callback functions.
At the same time, it is important to provide input validation and the ability to cancel the script at any step using global commands (for example, /cancel).
const { conversations, createConversation } = require('@grammyjs/conversations');
// Подключаем сессии (необходимы для работы conversations)
bot.use(session({ initial: () => ({}) }));
/**
* Функция-сценарий пошагового сбора лида
*/
async function collectLeadConversation(conversation, ctx) {
await ctx.reply('Давайте оформим заявку! Как к вам обращаться?');
// Ожидаем текстовый ответ от пользователя
const nameContext = await conversation.waitFor('message:text');
const userName = nameContext.message.text.trim();
if (userName.startsWith('/')) {
await ctx.reply('Заполнение заявки отменено.');
return;
}
await ctx.reply(`Приятно познакомиться, ${userName}! Укажите ваш номер телефона:`);
// Ожидаем ответ с контактом или текстом
const phoneContext = await conversation.waitFor(['message:contact', 'message:text']);
let phoneNumber = '';
if (phoneContext.message.contact) {
phoneNumber = phoneContext.message.contact.phone_number;
} else {
phoneNumber = phoneContext.message.text.trim();
}
// Генерация уникального идентификатора лида
const leadId = Math.random().toString(36).substring(2, 9);
console.log('Новая заявка сохранена:', {
leadId,
telegramId: ctx.from.id,
name: userName,
phone: phoneNumber,
createdAt: new Date().toISOString()
});
await ctx.reply(`Спасибо, ${userName}! Ваша заявка №${leadId} успешно принята. Менеджер свяжется с вами.`);
}
// Регистрируем диалог в плагине
bot.use(createConversation(collectLeadConversation));
bot.use(conversations());
// Команда для запуска сценария
bot.command('lead', async (ctx) => {
await ctx.conversation.enter('collectLeadConversation');
});
bot.command('start', async (ctx) => {
await ctx.reply('Привет! Нажмите /lead, чтобы оставить заявку на консультацию.');
});3. Webhook vs. Long Polling: Production Server Configuration
During the development stage, it is more convenient to use Long Polling (bot.start()) — it does not require a public IP address or an SSL certificate. However, under high load in production, Webhook is the standard solution. Обработка вебхуков allows you to deploy the bot in a Serverless infrastructure or scale the backend behind a load balancer.
To secure the webhook, you must use X-Telegram-Bot-Api-Secret-Token headers passed when registering the URL via the setWebhook method.
const express = require('express');
const { webhookCallback } = require('grammy');
const app = express();
app.use(express.json());
const PORT = process.env.PORT || 3000;
const SECRET_TOKEN = process.env.WEBHOOK_SECRET || 'crypto-safe-secret-token';
if (process.env.NODE_ENV === 'production') {
// Продакшн: используем Express и Webhook с проверкой Secret Token
app.post(`/webhook/${botToken}`, (req, res, next) => {
const incomingToken = req.headers['x-telegram-bot-api-secret-token'];
if (incomingToken !== SECRET_TOKEN) {
return res.status(403).send('Forbidden: invalid secret token');
}
return next();
}, webhookCallback(bot, 'express'));
app.listen(PORT, () => {
console.log(`Webhook сервер запущен на порту ${PORT}`);
});
} else {
// Разработка: запуск Long Polling
bot.start({
onStart: (botInfo) => console.log(`Бот @${botInfo.username} запущен локально через Long Polling`),
});
}4. Dealing with Telegram API Rate Limits (Error 429 / Retry-After)
Telegram Bot API imposes strict rate limits: no more than 30 messages per second in total and no more than 1 message per second to the same chat. During broadcast messaging or a sudden influx of users, the API returns an error with HTTP code 429 Too Many Requests and a retry_after parameter.
If you ignore this status and continue sending requests, Telegram may temporarily or permanently block the bot token. In the grammY ecosystem, the @grammyjs/auto-retry plugin is used to handle this issue automatically. It intercepts 429 errors and automatically delays retrying the request for the number of seconds specified by the Telegram server.
const { autoRetry } = require('@grammyjs/auto-retry');
// Подключаем auto-retry в API-клиент бота
bot.api.config.use(
autoRetry({
maxRetryAttempts: 3, // Максимальное количество повторных попыток
maxDelaySeconds: 60, // Максимальное время ожидания в секундах
})
);
// Пример безопасной отправки уведомления
async function sendNotification(chatId, text) {
try {
await bot.api.sendMessage(chatId, text, { parse_mode: 'HTML' });
} catch (error) {
console.error(`Не удалось доставить сообщение для ${chatId}:`, error.message);
}
}5. Architectural Nuances and Launch Checklist
- Идемпотентность вебхуков: Telegram guarantees update delivery, but during network failures, it may send the same
update_idmultiple times. Store processedupdate_idvalues in Redis or a database with a TTL to prevent duplicates. - Graceful Shutdown: When restarting the application, stop Long Polling or close database connections to avoid losing current session states.
- Data Type Validation: When receiving messages in scenario steps, always check their type. A user might send a sticker, video, or file instead of the expected text.
If you need professional development of complex bots or интеграция Telegram solutions integrated with your corporate system, contact the specialists at BotCreator.