Introduction
Telegram bots are a powerful tool for workflow automation. However, processing heavy tasks directly in bot handlers blocks the event loop and leads to response delays. The solution is to separate the async bot (aiogram) from background workers (RQ + Valkey).
System Architecture
The complete request processing cycle works as follows: user sends a message → bot receives it via aiogram → bot enqueues a task in RQ via Valkey → RQ worker processes the task → worker sends the result back via Telegram API, linked to the original message.
Bot: Receiving and Enqueueing
from aiogram import Bot, Dispatcher, types
from rq import Queue
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
task_queue = Queue(‘telegram’, connection=redis_conn)
@dp.message()
async def handle_message(message: types.Message):
task_queue.enqueue(
‘workers.send_text_task’,
chat_id=message.chat.id,
text=message.text,
reply_to=message.message_id
)
await message.reply(“⏳ Processing request…”)
Worker: Processing and Responding
def send_text_task(chat_id, text, reply_to):
result = process_heavy_task(text)
_run_async_task(
_reply_message(chat_id, result, reply_to)
)
async def _reply_message(chat_id, text, reply_to):
bot = Bot(token=BOT_TOKEN)
await bot.send_message(
chat_id=chat_id, text=text,
reply_to_message_id=reply_to
)
await bot.session.close()
ValkeyPersistence for State Storage
To track task statuses, we use Valkey (Redis-compatible). FinishedJobRegistry allows the bot to periodically check completed tasks and notify users of results. The worker stores results with TTL to prevent memory bloat.
reply_to_message_id for Threading
An important detail is preserving the original message_id and passing it to reply_to_message_id when responding. This creates a natural thread in the chat and helps the user link the response to their request, especially when multiple requests are being processed in parallel.
Containerization
The bot and workers run in separate containers within a single Podman pod. Valkey also runs in the pod, providing communication via localhost. This simplifies deployment and ensures all components share the same network.
Conclusion
Separating a Telegram bot into a frontend (aiogram) and backend (RQ workers) ensures bot responsiveness and processing scalability. Valkey serves as both message broker and state store.