A small Telegram bot turned out to be a nine-month exercise not in programming, but in holding a system together while almost everything around it changed.
At the end of October I rather accidentally took on a Telegram bot for one project. The task did not look large.
It had to connect Telegram to an existing backend: authorise the user, save their settings, fetch the relevant data and show the result back in the bot.
By 30 October a test instance with authorisation was already running, while the backend team opened up the missing API methods in parallel. I was receiving the webhook on my own server for the time being.
It felt like all that remained was to finish the scenario.
The bot was accepted only on 24 July of the following year.
The bot was not complicated
This is an important part of the story. Technically the project never struck me as anything especially frightening.
By March, when I had to restore the development context for the umpteenth time, I wrote roughly the same thing: if we assemble a centralised API and understand the current task, the bot closes out fine.
But by then I could already remember three managers and four backend developers who had passed through the project.
And here the real problem gradually became visible. It was not in the code.
The requirements existed in several places at once
Part of the scenario lived in Miro.
Some of it was discussed in Telegram.
Some of it sat in Swagger.
Some documents existed, but no longer quite matched that version of the project.
In March the team was literally searching for the old specification and access to the diagram, in order to reconstruct the logic the bot had been assembled from in the first place. Meanwhile the scenario had managed to change during development.
By the end of May the situation looked almost comic. I was asked to hand over the bot and the final requirements document.
There was no document.
I answered roughly like this: here is the bot, here is the diagram, tasks were mostly recorded in chat, in principle everything should work.
A phrase best spoken in production only when you have good insurance.
What had actually come together by then
Despite all the surrounding fog, the bot itself gradually turned into a perfectly normal standalone service.
The stack in the final version was roughly this:
Python 3.12FastAPIaiogramRedishttpxTelegram Webhooks
In production it assumed a reverse proxy, HTTPS and a separate systemd process. The service kept user state in Redis and talked to the backend over an HTTP API.
Through the backend the bot could handle authorisation and SMS, user settings, selections, creating and updating profile data, and fetching results for chosen parameters.
By that point, in other words, this was no longer /start → two buttons → thank you.
But the architecture was still the most predictable part of the project.
The nastiest bugs lived between systems
One typical problem looked like this:
- the bot said it had found matching results;
- it showed them;
- the “see all” button opened the website;
- on the website there was a different set, or nothing at all.
On the Telegram side everything could look correct. On the API side you could also get a 200.
The problem was that different backend methods and the website page applied filters slightly differently.
This is a good example of how the content of my work was changing. I was “fixing the bot” less and less often. Instead I had to trace the path of the data:
Telegram → bot service → API → user state → filtering → frontend
And find the point where two parts of the system stopped agreeing with each other.
Then the project almost ended
On the first of July a few more small remarks arrived. One problem with link generation I fixed fairly quickly.
A few minutes later the manager wrote: the client likes everything. The next day payment was already being discussed.
A rather precise summary of the whole story appeared in the chat:
We basically rebuilt the entire bot together.
To some degree that was true. Only it was not being rebuilt because the first version had been completely wrong. The system around it was changing.
“Final edits”
Naturally, the first of July did not turn out to be the finale.
Then the client wanted to proofread every text and button. I exported them into a separate file. It came to more than twenty kilobytes of text, to which the manager replied: “a whole dissertation”.
Authorisation checks continued.
Links changed.
The SMS logic was refined.
Menu items were reordered.
On one release the transitions broke entirely, and practically any action started sending the user to the site’s home page.
At that moment one more excellent characterisation of the project appeared in the chat: “we have no project documentation for the bot whatsoever”.
So we carried on working from the system’s actual behaviour.
24 July
At 14:30 a message arrived: the client has accepted the bot. From here on, only warranty fixes if any are needed.
A project begun in October as a fairly small Telegram bot had finally ended. Almost nine months later.
What I took from it
You could look at this story and decide the bot was developed for nine months. That would not be quite right. The core code could have been written considerably faster.
The project existed for nine months. And a project consisted of more than code.
It consisted of a changing backend, new requirements, several generations of participants, lost context, differing ideas about filtering, client edits, old diagrams and new releases.
At some point my work stopped being about developing the next feature and became about assembling one working system out of all that, again and again.
That is probably why I now believe far less in the phrase “it’s a simple bot”.
A simple bot can be simple. The system it has to be embedded in almost never is.
In October I thought I was writing a Telegram bot. By July I understood that all along I had been holding together the coherence between parts of a system that kept moving.
And somehow that turned out to be the main work.