Conversational Cross-Channel Booking Agent
A booking agent that keeps one shared inbox across WhatsApp, Instagram, Outlook, and Google instead of four separate ones.
Problem Statement
What was being solved
A solo service-business owner (a salon owner, a clinician, a consultant who delivers the service and runs the business) gets booking requests from four places that don't talk to each other: WhatsApp, Instagram DMs, email, and calendar invites. Each channel holds its own version of what's booked. Nothing reconciles them, so there's no single answer to what's confirmed, what's pending, or what got promised twice.
Why it matters
A solo operator handling 50-plus booking messages a day across four channels loses two-plus hours daily to manual triage and scheduling, time that would otherwise go to clients. Double bookings, missed DMs, and switching between four apps aren't small annoyances for someone with no staff to absorb them. They're lost revenue.
Why it's non-trivial
Each platform has its own data model, its own webhook guarantees, and its own OAuth flow. A conversation state that holds across all four, one that also handles duplicate messages, retried webhooks, and matching the same customer across platforms, sits as much in distributed systems territory as it does in AI.
Why Naive Fails
Each bot only knows the bookings it made itself. Reconciling that into one calendar is a manual step, and it's the first thing to slip once things get busy.
One FastAPI gateway and one LangGraph agent write every booking to a single Cloud SQL store. There's no second calendar to reconcile.
No idempotency check anywhere. WhatsApp, Instagram, and Outlook all retry webhook delivery, so the same event can trigger a second booking attempt.
Idempotency keys (message_id plus platform) are checked against Memorystore before a Cloud Task runs, so duplicate deliveries get rejected before they reach the booking flow.
Customer identity is scoped per platform, with no matching against phone or email. A customer who DMs on Instagram and later messages on WhatsApp becomes two unrelated records.
Not solved in v1. This is the redesign flagged as highest priority: build identity resolution before any bot logic, not after (see "Do Differently").
Architecture
Stack
LangGraph
Booking confirmation needs to loop: propose, decline, re-check, propose again. StateGraph supports that natively and keeps conversation state explicit across tool calls.
Gemini
The model behind the agent's tool-calling loop, wrapped in a small LangChain-compatible class instead of called directly, so the graph can swap models without touching node logic.
MCP (Model Context Protocol)
Appointment and slot-availability tools run as their own MCP servers rather than living as inline LangChain tool functions, so the booking logic stays addressable on its own.
Langfuse
Traces every tool call inside a booking loop. Without it, a bad booking is just a support ticket with no way to see which tool ran or why.
FastAPI
Handles webhook traffic from four platforms concurrently. A synchronous framework would queue these one after another and risk webhook timeouts.
SQLAlchemy (async) + Alembic
Async ORM for conversation state, customers, and appointments, with Alembic tracking schema changes as the booking model grew.
Cloud Run
Scales to zero between bursts of booking traffic and scales out during a busy DM window, without servers to manage directly.
Cloud Tasks
Separates webhook receipt from the actual booking work. The webhook needs an immediate 200; the agent needs 8 to 15 seconds. Cloud Tasks queues the job and calls Cloud Run asynchronously.
Cloud SQL
Relational storage for conversations, customers, and appointments, reached through the Cloud SQL IAM connector instead of a static password.
Memorystore for Redis
Sub-millisecond reads for idempotency keys and short-lived session state, managed and Redis-compatible so the client code doesn't change.
Cloud Scheduler
Runs the media-refresh job on a fixed cadence, catching CDN links before they expire without a cron daemon to babysit.
Google Cloud Storage
Permanent home for media whose platform CDN links expire, refreshed by the Cloud Scheduler job.
WhatsApp Business Cloud API
The primary channel for the target market. The Cloud API skips the server management that On-Premises would require.
Microsoft Graph (Outlook)
Corporate clients live on Outlook. Graph API gives calendar and email access under one OAuth flow instead of two separate integrations.
Firebase Cloud Messaging
Push notifications for booking confirmations on both iOS and Android from one integration.
How
- 01
LangGraph's cyclic StateGraph instead of a linear chain
Booking needs confirmation loops: the agent proposes a slot, the customer declines, the agent checks availability again. A linear chain can't re-enter a node once it's run. LangGraph's StateGraph allows that explicitly, so the confirmation node just routes back to the availability node on a rejection.
- 02
Cloud Tasks instead of a self-managed queue
A webhook has to return HTTP 200 within about 3 seconds or the platform retries it. The LangGraph agent can take 8 to 15 seconds to finish a booking loop. Cloud Tasks separates the two: the webhook returns immediately, and the booking work runs asynchronously on Cloud Run, with no broker or worker fleet to run ourselves.
- 03
Google Cloud Storage instead of platform CDN links
Instagram's media URLs expire in 24 to 48 hours; WhatsApp's expire after being accessed once. GCS holds the files permanently, and Cloud Scheduler runs a periodic job that refreshes profile pictures before their source CDN link expires.
- 04
Per-platform idempotency keys in Memorystore
Every major messaging platform can and will deliver the same webhook event more than once. Keys built from the message ID and platform type get checked against Memorystore before a Cloud Task is allowed to run, so a duplicate delivery never turns into a duplicate booking.
Cloud Tasks and Cloud Run push that operational weight onto GCP instead of a self-run broker and worker pool. GCS costs more per request than self-hosting object storage, but it sidesteps the expiring-URL problem and the re-fetching that comes with it. Memorystore is one more managed line item, but it means not running Redis ourselves. LangGraph's cyclic state model adds serialization overhead a simple prompt loop wouldn't need — worth it because the confirmation flow genuinely has to re-enter a node.
Metrics / Outcomes
Results
What I would measure
Booking completion rate, from intent to confirmed slot. Webhook delivery latency, from platform send to Cloud Task execution. How often the system actually catches a calendar conflict before it becomes a double booking.
Solving a similar problem?
I'm open to conversations about production AI systems — agentic workflows, RAG pipelines, or messy integration problems like this one.