The owner of TER.A COFFEE came to us with a specific complaint: the physical stamp card wasn't working. Not because customers disliked the loyalty program — they liked it fine — but because the card kept disappearing. Left at home, worn out, occasionally "helped along" by a generous friend with a pen. The reward never reached the people who actually earned it, and there was no way to know how close anyone was to their free coffee.
We shipped a working Telegram bot in 14 days. It now has 393 registered users. Here's what we built, what broke along the way, and what the data looked like when we actually ran the numbers.
Why Telegram, not an app
The short answer: Telegram was already there. The customer base was active in Telegram daily — family groups, news, everything. Asking them to download a separate loyalty app would have meant losing half the potential users at the install screen. With Telegram, there's no install. The bot starts the moment someone scans a QR code at the counter.
For markets where WhatsApp dominates, the same architecture works — the loyalty logic, database, and all the stamping mechanics are independent of the messaging layer. We chose Telegram because that's where this particular audience already was.
How the stamping works
Each customer gets a personal QR code inside the bot. When they want a stamp, the barista scans it — or in practice, the customer just shows their phone and the barista taps the deep-link it generates. The link looks like t.me/bot?start=u-{user_id}, which opens the staff interface directly to that customer's profile. No typing, no searching by name.
On the 7th stamp, the bot sends a "free coffee" notification and resets the counter. The owner keeps the free drink as a separate gesture rather than automating it — they want the barista to hand it over personally. That was a deliberate product decision, not a technical limitation.
The tech stack
Python 3.11, python-telegram-bot v21 (async), PostgreSQL, Docker Compose running on a Hetzner VPS. The database has two tables: users (user_id, cups, username, first_name, last updated) and staff (a whitelist of Telegram IDs). That's it.
The stamp logic itself is about 25 lines of SQL and Python. The core of it:
cur = conn.execute(
"UPDATE users SET cups = cups + 1, updated_at = now() "
"WHERE user_id = %s RETURNING cups",
(user_id,),
)
cups = int(cur.fetchone()["cups"])
if cups >= 7:
conn.execute(
"UPDATE users SET cups = 0 WHERE user_id = %s",
(user_id,),
)
There's a 3-second rate lock per staff–customer pair stored in memory (not in the database — it doesn't need to survive a restart). This prevents a double-tap from counting twice during a busy moment at the counter. We considered Redis for this, but the single-process polling setup made an in-memory dict sufficient.
The Replit problem
The first version ran on Replit. It was the right call at the time — fast to prototype, no server to configure, easy to share a working URL. It held up for the first few hundred users. Then the limitations started showing: Replit's free tier puts containers to sleep after inactivity, which meant the bot took 5–10 seconds to respond to the first message of the day. During a morning coffee rush, a 10-second delay while the barista is waiting is not acceptable. Paid plans helped but didn't fully solve the cold-start behavior.
We migrated to a Hetzner VPS — a €4.51/month instance running Docker Compose with the bot and PostgreSQL as separate services. The migration itself took half a day: export users from Replit DB, import into Postgres, update the env vars, redeploy. The bot has been on polling (not webhooks) since the move, which simplified the infrastructure considerably.
What the data actually showed
Once we moved to Postgres and could run real queries, we pulled a snapshot of the user table. The number that stood out: 315 out of 393 users had no stamp activity in the past month. Active in the past week: 35. Users sitting at 5 or 6 stamps — one or two visits away from a free coffee: 38.
We don't have a benchmark for what "healthy" looks like for a loyalty program of this size, so we're not claiming this is good or bad. What it does tell us is that the 38 near-reward users are a concrete, actionable group. A targeted message to those 38 people — something like "you're one coffee away" — costs essentially nothing to send and has a measurable outcome. That kind of segmented outreach wasn't possible with a stamp card.
The harder question is whether the 315 quiet accounts represent lapsed customers or just people who don't come in often but haven't churned. We don't have visit data from before the bot launched, so there's no baseline to compare against. The owner has a better intuition about this than the database does.
Admin and broadcast tools
The owner can see all users inside the bot — paginated at 50 per page with stamp count and last activity. Broadcasts go out with a confirmation step before sending, which has already prevented at least one accidental mass message during testing. Broadcasts support both text and photos, and the bot auto-generates a bilingual version — the message goes out in Russian followed by a Hebrew translation, separated by a visual divider. That was a specific request from the owner given their customer mix, and it took about a day to wire up with a translation call.
What's on the roadmap
The stamping bot is useful on its own, but most of its value is in what it makes possible next. The customer base is now reachable — 393 Telegram IDs with activity timestamps, compared to zero data from the old card system.
The most obvious next step is triggered messaging: automated outreach to the near-reward segment, win-back messages after extended inactivity, a birthday reward if we start collecting that field. None of this requires new infrastructure — it's a cron job querying the existing database.
After that, pre-order is the one with the most revenue upside: order and pay before arriving, skip the queue. That needs a payment integration and a different conversation with the owner about operations. It's on the list but it's not the next thing.
Building something similar?
We build Telegram bots for restaurants, cafes, and retail — from first commit to production. If you have a specific use case, we're happy to talk through whether it's a good fit.
Get in touch →