Booking Operations Bot

A Telegram back office in 33 workflows — a 74-node deterministic router that tries fifteen explicit intents before ever reaching the AI agent, plus 24 tool workflows and six unattended jobs.

Client
EpsolDev
Category
Automation Engineering
Completed
2026-08-15
Built with
n8n, Telegram Bot API, REST API, WordPress
Topics
API Development, Automation, Backend
Booking Operations Bot

n8n 33 workflows AI agent Running in operations

A Telegram bot that runs a tour-booking back office — read bookings, take payments, chase pending ones, manage admins, reconcile automatically. Thirty-three workflows: one router, two agent revisions, twenty-four tools and six scheduled jobs.

The router: state-machine gates on the left, fifteen action branches fanning right, all converging on one reply


The decision that shapes the whole system

Most chat-operations bots put the language model at the front: every message goes to the agent, the agent picks a tool. That is expensive on every message, non-deterministic on the messages that matter most, and impossible to reason about when it misfires.

This one is inverted. The router tries fifteen explicit intents first, in order. The AI agent is the last branch — the fallback for what the deterministic chain could not classify.

Decide Action -> Action: Add Admin FSM?  -> ...
              -> Action: Admins FSM?     -> ...
              -> Action: Gifts?          -> ...
              -> Action: WP Create?      -> create payment link
              -> Action: WP Sites?       -> ...
              ( eleven more )
              -> Action: AI Agent?       -> Call AI Agent    Build Static Reply
Agent-firstRouter-first (this)
"list bookings"model call, tool call, model callone branch, one fetch
Cost per common commanda full agent turna HTTP request
Behaviour under loadvariesidentical every time
Novel phrasinghandledfalls through to the agent

The predictable 90% is code. The unpredictable 10% is the model. That is the entire architectural bet, and it is why the router is 74 nodes and the agent is 22.


Conversation state is a table, not a memory

Multi-step commands — adding an admin, editing a gift recipient — need to remember where the user is. The router keeps that in a data table, read at the start of every message and written with an explicit delete-then-insert:

Need State Op? -> Delete State -> Need Insert? -> Prep Insert -> Insert State ─┐
               └> Skip State Op ──────────────> Skip Insert ──────────────────┴> State Merge

Delete-then-insert is an idempotent upsert: replaying a message cannot leave two active flows for one user. The three-input merge means every path — wrote state, skipped the insert, skipped the whole operation — rejoins the same trunk, so downstream nodes never branch on "did we touch state?"

A second, identical state block runs after the action, because some actions advance the flow themselves.


Voice is normalised at the edge

On Telegram Message -> Has Voice? ─┬─ yes ─> Transcribe Voice ─> Inject Transcript ─┐
                                   └─ no ───────────────────────────────────────────┴─> Parse Message

Transcription is its own workflow, called as a sub-workflow. By the time anything downstream runs, a voice note and a typed message are the same shape — so not one of the fifteen action branches contains a single line of audio handling.


One reply node

Fifteen action branches, three sub-workflow state machines and the AI agent all converge on a single Reply Merge with fifteen inputs, feeding one Send Reply.

One egress point is a testing decision Every branch is forced to produce the same reply shape. Formatting, escaping and delivery are fixed in one place — and a new action cannot invent its own way of talking to the user.


The agent, when it is reached

The AI agent: a language model, conversation memory, and thirteen sub-workflow tools bound to it

The agent is deliberately small. A chat model, a buffered conversation memory, and thirteen tools — each of which is its own workflow:

bookings_read · bookings_write · bookings_reconcile · payments · sites · forms · admin_dashboard · memory_search · start_cart_handoff · toggle_notify · log_audit_entry · add_admin · admins

Two things worth naming:

Memory is hooked, not just buffered. A Memory Pre-Hook runs before the agent and injects retrieved context; a Memory Post-Hook runs after and persists what happened. The built-in buffer window handles the current conversation; the hooks give it recall across conversations.

A tool is a workflow, not a code block. Every capability the agent has is independently openable, runnable and testable in the editor — and the same workflow backs the router's deterministic branch. bookings_read behaves identically whether a regex or a model decided to call it.

An earlier revision of the agent graph, kept alongside the current one

Both revisions stay deployed. The router names the version it calls, so rolling back is a branch change, not a redeploy.

One agent tool in isolation: the admin dashboard reader


The jobs nobody triggers

Eight workflows run with nobody typing anything — six on a schedule or an event, two callable on demand:

Scheduled payment reconciliation

JobWhat it does
AutoReconcilematches incoming payments to pending bookings
BulkReconcilePendingthe same, run in bulk on demand
NotifyOnPaymentpushes a notification the moment a payment lands
PendingReminderchases bookings that were never paid
ExcursionReminderreminds ahead of the booked date
DailySummaryone operational digest per day
HealthCheckproves the stack is reachable
ErrorHandlercatches every unhandled failure

The daily operational summary

Reconciliation is the one that earns its keep. Matching payments to bookings by hand is the task that silently rots — this runs on a schedule and the operator only hears about the exceptions.

The shared error handler every workflow in the set reports into


Stack

n8n Telegram Bot API OpenRouter LangChain JavaScript REST API WordPress

Every write path goes through the booking platform's own API — the bot holds no database of its own, so a booking changed from the bot and one changed from the admin are the same write.

All projects