Small beauty salon ·
A salon’s income and expense tracking, done by Telegram message
An expense or income is written to the bot in your own words: “rent 20000 for October.” The bot shows how it understood the message and records it after a button tap. The yearly summary opens on a phone from a link, and the familiar Excel file is put together at the tap of a button.

The task
A small salon kept its income and expenses in one Excel spreadsheet: date, category, amount, a total at the bottom, and profit by month and two charts off to the side. The spreadsheet itself is clear. The trouble is elsewhere: to enter an expense, you have to open it. And an expense happens on the go, when all you have at hand is a phone.
So entries appeared later, whenever someone got around to the spreadsheet. By then the day of the purchase could no longer be recalled, so the first of the month went into the spreadsheet. And some expenses were left without even a name: the spreadsheet has rows labeled “Expense with no explanation.” The money went out, but what it was spent on is not in the records.
What was needed was tracking that does not require sitting down at a computer: you record it right where you chat and forget about it. It had to come with no new program to learn, and two people had to be able to make entries. And the familiar spreadsheet with totals and charts had to stay.
What it delivered
An expense or income is recorded with a Telegram message, in your own words and on the day it happened, not at the end of the month from memory. A yearly summary with profit by month and charts opens on a phone at the tap of a button. The familiar Excel file is still there: the bot puts it together from the same numbers.
Where I started
The 2026 spreadsheet is all there was before the bot. It shows where tracking by hand loses accuracy.
The salon’s yearly totals are not shown in this case study. The screenshots of the summary page use demo data: the numbers are made up, while the page is built by the same code that runs for the salon. The screenshots of the chat with the bot are real, from the working bot.
| Metric | How many |
|---|---|
| Transactions in the spreadsheet for the year | 109 |
| Of those, dated the first of the month instead of the actual day | 105 |
| Purchases in rubles where the amount and exchange rate are written into the category name | 5 |
| Spellings of one and the same category | 2 |
The first of the month in place of the day is not carelessness but a consequence of the method: the spreadsheet gets filled in when someone gets around to it. A spreadsheet like that shows how much went out in a month but not when, and by then it is hard to check an entry against a receipt.
The same goes for purchases in rubles. The column held the amount after conversion, while the rubles and the exchange rate were written out in words in the category name: you can read them, but you cannot calculate with them.
What I did
One transaction, one message
You write to the bot the way you would say it to a person: “rent 20000 for October,” “income 4800 today,” “coffee 1200 yesterday.” There are no forms and no required word order. From the message the bot pulls the date, the type (income or expense), the category, and the amount.
If no date is given, today’s date is used. If only a month is given, the bot marks that there is no exact day and does not make one up. Several transactions can be sent in one message, one line each.

It shows first, then records
The bot answers every message with its reading of it: what it understood, for what date, in what category, and for what amount. Under that reading are two buttons, “Record” («Записать») and “Cancel” («Отмена»). A transaction goes into the records only after “Record.”

If the text does not make clear whether this is income or an expense, the bot does not guess; it asks with two buttons. If it could not make sense of the message, it says so and records nothing.
AI reads, a program does the math
AI parses the free-form text, and that is where its job ends. It returns the date, type, category, and amount as the person wrote them. It is not trusted to add, convert currency, or calculate percentages: all the arithmetic is done by an ordinary program that always gives the same answer on the same data.
For income and expense tracking, this is the key decision. A mistake in understanding is visible on the screen before anything is recorded. A mistake in addition would be visible to no one.
One category, one name
Along with the message, the AI gets the list of categories already in the records. So a name written in lowercase, with a capital letter, or in Latin letters goes into one and the same category, not three different ones.
In the old spreadsheet, one category was written two ways; during the migration they were merged into one.
Rubles convert on their own
The salon pays for some purchases in rubles but keeps its records in another currency. It is enough to write “July in rubles purchase 30000”: the bot takes the Bank of Russia exchange rate and converts. For a month already closed, it uses the average rate for the month; for the current month, the rate on the day of the transaction. The entry keeps the rubles, the rate, and the result, so you can see where the amount came from.
If the Bank of Russia does not respond, no rate is filled in by guesswork: the transaction is not recorded, the message is saved, and a “Retry” button appears under it.
The summary opens from a link that lasts 15 minutes
When the “Statistics” button is tapped, the bot sends a link. It opens in the phone’s browser, with no app, password, or sign-up, and works for 15 minutes. After that the page stops opening, and you get a new link.

The page shows the totals for the year: income, expenses, profit, and profit margin. Below are a table by month with the average profit since the start of the year, two charts on which a month with a loss is marked in red, and a list of all transactions.
A month that is still under way, with expenses already recorded but no revenue yet, is left out of the profit and the average; otherwise every month would start with a “loss.” The page says so directly.
The salon’s name is not on the page. At an expired link or any other address, the server shows nothing, and the page is closed to search engines.
And one detail that is not obvious: the link is not single-use, though that would be stricter. Telegram itself opens the links it is sent in order to show a preview, so it would use up a single-use link before the person did.

Excel stayed
When the “Export xlsx” button is tapped, the bot sends a file in the familiar form: transactions, the total, profit, profit margin, a table by month, and two charts.

The totals in the file and on the page cannot differ: both come from one calculation. The cells hold numbers, not formulas, so the file opens the same way in Excel and in a preview on a phone.
A mistake is visible and can be removed
The “Recent transactions” button («Последние операции») shows the ten most recent entries, each with a delete option that asks for confirmation. A deleted transaction disappears from the summary and from the export, but it is not erased from the database; it is marked.
There is deliberately no “Edit” button: correcting an entry means deleting it and writing it again, with the same kind of message.


The old spreadsheet moved over in full
The year’s records were not started from scratch: on its first run, the bot moved all 109 transactions over from the spreadsheet. The migration counts as successful only if four control totals match afterward: the number of transactions, total income, total expenses, and profit. All four matched.
During the migration, the purchases in rubles were split into rubles, rate, and result. At the same time, the five such transactions were recalculated from scratch at the Bank of Russia rate, and the result matched the spreadsheet to the last unit.
This turned up a small thing of the kind that is never small where money is concerned: with the rate rounded to three decimal places, one transaction was off by two units. So the bot calculates with the unrounded rate and rounds only the result.
The server goes down, and no messages are lost
The bot lives on a small server of its own, not in a data center: the power or the internet can go out. When that happens, messages wait inside Telegram itself and are processed in order when the bot comes back.
The transaction date is taken from the time the message was sent, not the time it was processed: an expense written on the evening of the thirtieth will not slide to the first. The “Record” and “Cancel” buttons under old messages still work after a restart, and a message delivered a second time is not recorded twice. Once back, the bot says in one message that it was unavailable and how many messages it processed.
There is an honest limit to this: Telegram keeps such messages for no more than a day. If the server was silent longer, the earlier ones are lost. The bot warns about this in its help message and advises checking “Recent transactions” after a long downtime.
A copy of the records, every night
All the records are one file on the server. Once a day, at night, the bot sends a copy of it on Telegram to those who keep the records, if anything changed during the day. If the server breaks, the latest copy stays in the chat.
The bot does not answer strangers
Only people on the list can enter transactions and view the summary; right now that is two people. The bot does not answer anyone else at all, neither messages nor buttons, and an outsider does not see the command menu.
In groups the bot stays silent too, so that the amounts are not seen by people who should not see them. Neither message text nor amounts get into the logs on the server.
Does your income and expense tracking also live in a spreadsheet you never get around to?
Message meWhy this case study has no numbers
The bot has been running since October 2, 2026, which is less than a week as of the day of publication. In that time, it is not possible to say honestly either “it saves this many hours” or “tracking got this much more accurate.”
What is known for certain is what was checked before launch and on the first day.
| What was checked | Result |
|---|---|
| Migration of the old spreadsheet | 109 transactions, all four control totals matched |
| Message parsing by the real AI | Eight test messages out of eight parsed correctly |
| Response time | About five seconds per message |
| Downtime and restart | An automated check: three messages from the queue recorded in order, with their sending dates and no repeats |
| Interruption in the middle of recording | A separate code check: no duplicate entry and no database corruption |
And here is how the result will be measured. For every transaction, the bot marks whether it was recorded through the bot or moved over from the spreadsheet, and whether it names an exact day. In the old spreadsheet, 4 transactions out of 109 had the actual day. In a month or two, the same share among new entries will show whether the records are now being kept on time.
The numbers about the old spreadsheet come from the spreadsheet itself and from the migration check. Those about parsing and response time come from the project’s work log, the entry of October 2, 2026. The screenshots of the summary page were taken on demo data; the chat screenshots, in the working bot on October 7, 2026. The source code is not published: its tests contain the salon’s real numbers.
What this gives the business going forward
Does anyone need training?
No. Writing to the bot is the same as writing to a person on Telegram: what was bought and for how much. There are no forms and no required word order, and if the bot got it wrong, that is visible before anything is recorded.
What if the AI gets it wrong?
It records nothing by itself: first it shows its reading of the message, and a transaction goes into the records only after a button tap. And it is not trusted with the math at all: amounts, the exchange rate, and percentages are calculated by a program.
Who will see the numbers?
Only the people on the list. The bot does not answer anyone else, the summary link lasts 15 minutes, the business name is not on the page, and the page is closed to search engines.
What costs money after launch?
The server the bot lives on and the AI that parses the messages. Telegram and the Bank of Russia exchange rate are free.
What of this carries over to another business?
Almost everything except the categories themselves. You write it in your own words, check it, and confirm; messages are not lost during downtime; a copy every night; a summary from a link and a file for Excel: that is the framework. What changes is what exactly gets recorded: instead of income and expenses, it can be orders, shifts, vehicle mileage, or stock on hand.
How it works technically
Python 3.14, aiogram 3, aiohttp, openpyxl, SQLite in WAL mode with synchronous=FULL.
Parsing is done by the Claude Sonnet model, with the response strictly following
a JSON schema: date, type, category, amount, currency. The request carries today’s
date and the list of existing categories. The exchange rate comes from the Bank of
Russia XML with a cache in the database: for a closed month, the average over the days
that have a rate; for the current month, the rate on the date or the nearest earlier one.
Updates come by long polling without dropping the queue, strictly one at a time; marks
of processed update_id values are kept for a week. A message is saved in the database
before the model is called, one batch per Telegram message
(UNIQUE (chat_id, message_id)); writing the entries and changing the batch status
happen in a single database transaction, so confirmation is idempotent. The reply with
buttons is retried until it goes through: until it is sent, the next update is not taken.
The summary page is a single HTML file with no scripts and no external resources; the
charts are inline SVG, with a light and a dark theme. The link token is 32 random bytes
and lives in memory for 15 minutes. Without a valid token, the server returns an empty
404 for everything except robots.txt, including requests that are not HTTP. Headers
forbid indexing, caching, and embedding; the access log is turned off so that tokens
do not end up in it. The server is exposed to the outside through Tailscale Funnel.
A systemd service with autostart; the home directory is read-only for it. The database copy is taken with the SQLite backup mechanism, not by reading the file while it is in use. More than three hundred automated tests; they need neither a network nor secrets: the Bank of Russia responses are recorded in advance. Before release, there was a separate code review, and four defects in reply delivery and the web server were fixed as a result.
Who did the work
It’s just me behind jw-dev.pro, not a studio or an agency. That means you talk directly to the person doing the work: no manager, no brief relayed through third parties, and no passing the project to another contractor partway through.
My time zone is UTC+7, the same as Bangkok and Hanoi. I build websites, Telegram bots, and automation: a task rarely ends with a page, and it usually has to be connected to what you already have running.
As time goes on, the task sounds like “build a website” less and less often. One person needs a price to be changed in one place, not on twenty pages. Another needs the channel to run without them. A third needs the whole price list to sit inside Telegram, where their customers already go every day. That’s why the list of what I do is open-ended: if your task isn’t on it, that doesn’t necessarily mean it can’t be solved.
I write case studies so that they can be checked: what was there, what was done, and how it was measured. If there is analytics data, I give the numbers and the period they cover; if it is too early to measure, I say so. You won’t find “increased sales” here: that is a promise, not a result.
Need something similar?
Whether it’s a website, a bot, or something that is now done by hand, tell me what’s going on, and I’ll tell you whether it can be solved or not.
This kind of work is the service “Automation services for small business.”
Your request was not sent
Your text is not lost: go back and try again. Or contact me directly: on Telegram, on WhatsApp, or by email.
If it’s easier, contact me directly: on Telegram, on WhatsApp, or by email.
Not sure what’s wrong with your website? Check it for free on Telegram: @jw_site_check_bot.
Who owns everything afterward and what happens if I disappear: answers on the home page.