Work / Agents and bots

Booking bot for solo professionals: the client writes at night, the bot has already booked them for tomorrow

What it was

A solo professional works with their hands and does not hold a phone. Clients write in the evening and at night, asking the same things: how much, is Saturday free, where are you. Bookings live in a notebook or in the head, hence two clients for the same hour and forgotten visits. Hiring an administrator does not pay for one person, and the closest competitor costs $79-219 a month.

What we did

We built a service where the professional creates a bot in @BotFather following an illustrated guide and pastes the token. One process on the server runs every professional's bot at once. The client picks a service, day and hour with buttons, sees only free slots, gets a reminder a day ahead. The professional has a dashboard: bookings, services, a broadcast to clients, a referral link, payment by card or crypto. Six languages. After one day we removed AI answers to free text: the model could name a price the professional does not have.

What the client got

The bot runs: at the time of the check the service had been up 2 months and 20 days without a single restart. 49 checks in tests, each failed on the old code first. In the database 5 professionals, one of them a test account, 9 bookings. Zero payments in its whole history, three professionals keep using it for free after their trial month ended. Why they do not run their bookings through the bot, we have not investigated.

ClientOwn subscription product for solo professionals: barbers, nail artists, cosmetologists, photographers, tutors
My roleIdea, product, code, copy in six languages, illustrated guide, landing page, server
StatusRunning, zero paying users so far
Result2 mo 20 d the service ran without a single restart at the time of the check · Open
Rejected: the first version, where a model answered clients' free-text questions. It could name a price or a time the professional does not have. Reconstruction, not a screenshot
Rejected: the first version, where a model answered clients' free-text questions. It could name a price or a time the professional does not have. Reconstruction, not a screenshot
Accepted: booking by buttons only, from the real schedule: service, day, hour, confirmation. There are no model calls in the code
Accepted: booking by buttons only, from the real schedule: service, day, hour, confirmation. There are no model calls in the code

The task

A solo barber or nail artist cannot answer a client who writes at 23:00 asking “any time tomorrow?”. The answer comes in the morning, when the client has already booked somewhere else. Bookings live in a notebook or in the head, hence two clients for the same hour and forgotten visits. Hiring an administrator does not pay for one person.

This is our own product, not a commission. We wanted to check whether solo professionals in Ukraine would buy a simple booking tool for $10 a month. From our market research, AI Beauty Bot costs $79-219 a month, Starta.one starts at 6 euro but is complicated, beautyTG on GitHub is free but cannot be set up without a developer. A solo professional in Telegram, in Ukrainian and Russian, for $10 looked like an almost empty spot.

Why, and what the idea was

The idea: the professional does not wait for us. They create a bot in @BotFather following a step-by-step illustrated guide, paste the token into the main bot, and the server picks their bot up immediately, with no restart. One process on the server holds an unlimited number of other people’s bots. The client in the professional’s bot never types free text: service, day, hour, all with buttons, and only free slots are shown.

The main decision we revised: in the first version Claude Haiku 4.5 answered clients’ free-text questions. After one day we removed it completely. The bot could say things about the professional’s prices and services that were not true, and for booking, accuracy matters more than a “smart” conversation. We removed the whole path, not just the button: leave a free-text handler in place and the risk stays. Price: $20 at first, then I lowered it to $10 as a starting price for Ukraine, for everyone including those already connected, with the first month free. Database: SQLite instead of PostgreSQL, simpler at the start, enough for roughly 500 professionals by our estimate.

How it went

Stage What happened
Day 1, evening Concept, competitor research, an MVP plan for 1-2 weeks
Day 2 26 commits in a day: registration, services, calendar, referral program, CryptoCloud and LiqPay payments, trial protection, three critical bugs in the multi-bot scheme. In the evening, AI answers to free text removed
Day 3 MVP on the server. Manual subscription grants with the /activate command, to sell without waiting for LiqPay to be connected
Day 5 The professional’s dashboard in six languages, BotFather guide pictures in six languages, the server password removed from deploy.py
Day 6 Days off, broadcast to clients, support. Price lowered from $20 to $10 for everyone. Project documentation
2.5 months later A check on request: the bot is alive. The double-booking hole closed, the first tests in the project
The next day The payment intake hole closed, log rotation, a professional’s bot with someone else’s webhook switched off

46 commits in total: 5 days of active development and two targeted sessions later on.

Tools and method

Python 3.11, aiogram 3.7, SQLAlchemy 2 with aiosqlite, APScheduler for reminders, an aiohttp server for payment postbacks. About 4,700 lines, the biggest files: professional registration 2,125 lines, translations 986, the client bot 813.

Many bots in one process: one shared aiogram dispatcher and one polling call with all bots at once. When a professional connects or leaves, the polling task restarts with the new list under an asyncio.Lock so that two simultaneous connections do not clobber each other. Database: SQLite, one file, tables for professionals, clients, bookings and a separate trial history table keyed by Telegram ID. Server: a VPS, a systemd service with autostart and auto-restart, deployment by git push plus a script, SSH key login.

Money and keys: secrets only in .env, outside git. A payment postback is accepted only with a signature (JWT HS256), the signature is bound to a specific invoice, LiqPay verifies its own signature separately. The price is set in one place in the code plus the texts in translations.

Result

The main bot for professionals: language choice, registration (specialty, services with prices, working hours, days off, address with a format hint), the BotFather guide with pictures, token entry. Dashboard: my bookings, settings, editing and deleting services, a broadcast to all your clients, a referral link (bring a professional, get a month free), support, payment by card via LiqPay or crypto via CryptoCloud. The new-booking notification has a “Message the client” button.

The professional’s bot for clients: language, service, day (a quick pick of 7 days or a custom date), hour. Only free slots are shown. A booking can be cancelled with a reason, a contact can be left. The reminder arrives a day before the visit. Languages: Ukrainian, Russian, English, Polish, Spanish, German. The landing page ai-bot.com.ua/masters in Ukrainian and English, with a demo conversation, the plan and FAQ. The bot itself: t.me/masterbooking_main_bot.

What we measured. Uptime: the service ran 2 months and 20 days without a restart, 180 MB of memory, 195 minutes of CPU in total, the queue of unprocessed Telegram messages empty. Tests: three files, 49 checks: 19 on taken slots, including a race between two simultaneous bookings, 13 on the real booking function, 17 on the payment postback signature. Every test failed on the old code first.

Honestly about the weak side. The product works technically, but there are no sales. In the database 5 professionals (one a test account), 9 bookings, and no new bookings after the first days. Zero payments in its whole history, three professionals’ trial month ended 21-55 days ago and they keep using it for free. Why the connected professionals stopped running bookings through the bot, we have not investigated, and without that answer fixing payments is premature. Five open defects at the last check, among them: nobody gets switched off for non-payment, the postback travels over HTTP without TLS, the client’s booking steps are still hardcoded in Russian while the professional’s dashboard is translated into six languages.

Mistakes and what we took from them

The professional's bot silently did not work.The second and later bots received no messages: no errors, just silence. We caught it by the unprocessed-message counter in Telegram, which never went down for the second bot. In aiogram you cannot start polling on one dispatcher several times as separate tasks. "No errors in the log" does not mean "it works": look at an external sign.
The payment secret read as empty.load_dotenv() was called after imports that had already read the environment, so every signature check failed with 401 even with a correct .env. Loading the environment is now the first line. The same day we learned the provider signs the postback with JWT, not HMAC as we had assumed.
An endless free month.The /reset command deleted the professional entirely, so one could re-register for a new trial every month. Trial history moved to a separate table keyed by Telegram ID, and it survives a reset.
The landing page promised what the code did not do.The page said "no double bookings" three times, while the free-slot function never looked at the bookings table: a taken hour was offered again. Closed in three layers: taken hours are subtracted from the list, a check before booking, a partial unique index in the database for confirmed bookings only. A separate lesson: create_all() does not create a new index on an existing table, it has to be applied with a separate command, otherwise the protection exists in the code but not in the database.
Anyone could grant a subscription.The signature check had "no signature, so skip" with a link to the provider's official example, and the postback port was open to the internet. A request without a signature gave a professional 30 days. Checked with a live request before and after the fix (now 401). The provider's official example is no reason to let an unsigned request through when the port faces the outside.
A 1.1 GB log.One professional connected someone else's platform with a webhook to his bot, our process got a conflict every 5 seconds and wrote it to the log, 6.8 MB a day. The log was compressed to 49 MB, rotation set up, that bot's token switched off. Along the way we caught our own mistake: in the morning we decided the bot had no logs at all because we were looking at the wrong file.
The server password in the code.deploy.py contained the root password. Removed, SSH key login, the password on the server changed.

Tools

Python 3.11aiogram 3.7SQLAlchemy 2SQLiteAPScheduleraiohttpPyJWTLiqPayCryptoCloudsystemdlogrotateGitHubClaude Code