Small Biz Workflows
FORM SBW-008 · REV B · 5-FILE KITBack Office

Free Booking Agent

A conversational AI that books, reschedules, and cancels appointments straight into your Google Calendar from one chat endpoint, self-hosted on Cloudflare's free tier, so you stop paying monthly for a booking tool you don't even own.

TIME TO VALUE
1 afternoon (chat + calendar) · 1 evening (live on your channel)
RUNNING COST
$0 hosting (Cloudflare free tier) + roughly $1 to $5 LLM usage
STACK
Cloudflare Workers + Hono (the single /chat endpoint) · Google Calendar REST API via raw fetch (no SDK) · An LLM with tool calling (Gemini 1.5 Flash or GPT-4o-mini) · Cloudflare KV or in-memory store for the conversation
Get the kit free ↓Free Account · Field-TestedREAD THE FULL BUILD BELOW · KIT FREE WITH EMAIL

PROVENANCE: Rebuilt as an owned, step-by-step agent from the MIT-licensed SMB Forge AI Scheduler architecture and Google Calendar REST patterns. Generalized for any small business, no vendor lock-in, no secrets, adapt freely. The kit's INSTALL.md is a one-shot Claude Code build: unzip, paste one prompt, answer ~5 questions.

TL;DR

A customer messages "can I get in Thursday afternoon?" and a small AI answers in plain English, checks your real Google Calendar for open slots, books the one they pick, and sends the confirmation. No app, no monthly fee. It runs on Cloudflare's free tier and writes straight into the calendar you already use. You own all of it.

The problem this solves

A booking tool is the one piece of software almost every service business rents forever. You pay $20 to $50 a month, your availability lives inside someone else's dashboard, and the customer still has to leave the conversation, open a form, pick from a stiff grid, and hope they got the right one.

The thing is, "check my calendar and book a time" is a small, well-understood job. An LLM that can call three functions does it conversationally, and the three functions are just three Google Calendar API calls. This file builds that agent as code you own: one chat endpoint, your calendar, your rules, $0 a month to host.

The full picture

Every box below is one small piece. Build them left to right.

                            THE BOOKING LOOP
═══════════════════════════════════════════════════════════════════════
                                                  ┌──────────────────┐
 ┌──────────────┐  "Thursday  ┌────────────────┐  │  list_free_slots │
 │   CUSTOMER   │   at 2?"    │  /chat ENDPOINT │  │  freeBusy query  │
 │  messages    │ ──────────▶ │  (Hono on a    │  └────────┬─────────┘
 │  you         │             │   CF Worker)   │           │ open windows
 └──────┬───────┘             └───────┬────────┘           ▼
        │                             │ message     ┌──────────────────┐
        │ confirmation                ▼             │  GOOGLE CALENDAR │
        │ text                ┌────────────────┐    │  (your real one) │
        ▼                     │    LLM BRAIN   │    └────────┬─────────┘
 ┌──────────────┐             │ picks the tool │             │
 │   REPLY      │ ◀────────── │ to call, then  │ ◀───────────┘
 │ "Booked you  │   answer    │ writes a reply │   event id +
 │  Thu 2:00pm" │             └───────┬────────┘   confirm link
 └──────────────┘                     │
                          ┌───────────┴───────────┐
                          ▼                       ▼
                 ┌──────────────────┐   ┌──────────────────┐
                 │ book_appointment │   │ cancel_appointment│
                 │ create event     │   │ delete event      │
                 └──────────────────┘   └──────────────────┘
═══════════════════════════════════════════════════════════════════════

The LLM never touches your calendar directly. It only decides which of the three tools to call; each tool is one plain Google Calendar REST request. That separation is what keeps it safe and cheap.

What you need

Piece What it does Pick
Chat endpoint Receives the message, runs the loop One Cloudflare Worker with Hono, one /chat route
LLM with tool calling Decides which tool to call, writes the reply Gemini 1.5 Flash (cheapest) or GPT-4o-mini
Calendar API Reads free slots, creates and deletes events Google Calendar REST API, called with raw fetch
Session store Remembers the conversation between messages Cloudflare KV, or in-memory to start
OAuth refresh token Lets the Worker act on your calendar One-time Google sign-in, stored as a Worker secret

You do not need the googleapis package. Every calendar call is a single fetch(), which is exactly what runs cleanly on a Worker.

Phase 1, The chat endpoint that talks back (build this first)

   message ──▶ /chat ──▶ LLM (no tools yet) ──▶ plain reply ──▶ back to sender
  1. Make one Cloudflare Worker with Hono and a single POST /chat route. It takes { "message": "...", "senderId": "..." } and returns { "reply": "...", "status": "active" }.
  2. Wire one LLM call. Send the user message, get text back, return it. No calendar yet. Prove the round trip works end to end before adding any tools.
  3. Pick your engine with one env var. LLM_PROVIDER=gemini or openai. Toggling later should never touch the rest of the code.
  4. Add the session store. Key by senderId, keep the last few turns so "yeah, 2pm works" still makes sense after "what do you have Thursday?".

You know Phase 1 works when: you curl the endpoint, say hello, and get a sane sentence back in under two seconds.

Phase 2, The three calendar tools (this is the product)

Now give the LLM exactly three tools. Each one is a single REST call. Define them in the model's tool/function schema, then run the standard tool-calling loop: model asks to call a tool, you run the real API call, you feed the result back, model replies.

  USER MESSAGE
        │
        ▼
  ┌──────────────────────────────────────────────────────────────┐
  │ LLM picks ONE tool                                            │
  ├──────────────────────┬────────────────────┬──────────────────┤
  │ list_free_slots      │ book_appointment   │ cancel_appointment│
  │ POST .../freeBusy    │ POST .../events    │ DELETE .../events/│
  │ returns open windows │ returns event id   │ {eventId}         │
  └──────────┬───────────┴─────────┬──────────┴────────┬─────────┘
             ▼                     ▼                    ▼
       "I have 1, 2,        "Booked. Thursday    "Done, that slot is
        and 4pm open"        2:00pm is set."      open again."

The three real endpoints, confirmed against the Google Calendar REST API:

Tool Calendar API call Returns
list_free_slots POST https://www.googleapis.com/calendar/v3/freeBusy busy blocks, you invert to free windows inside business hours
book_appointment POST https://www.googleapis.com/calendar/v3/calendars/primary/events the new event id and a confirmation link
cancel_appointment DELETE https://www.googleapis.com/calendar/v3/calendars/primary/events/{eventId} empty on success

The booking guardrails (non-negotiable):

Phase 3, Connect it to where customers actually message

The /chat endpoint is channel-agnostic on purpose. The same JSON in and out works for any front door.

   web chat widget ─┐
   SMS (Twilio)     ─┤
   WhatsApp         ─┼──▶  POST /chat  ──▶  same booking loop
   Telegram         ─┘

Start with the simplest one you have, a chat widget on your site or an SMS number, and point its inbound webhook at /chat. Nothing about the agent changes.

How the calendar access works (the one-time OAuth part)

You sign in to Google once and store a refresh token. After that the Worker handles itself.

  YOU (once)                          THE WORKER (every booking)
  ──────────                          ──────────────────────────
  sign in with Google      ──▶        has the refresh token (a secret)
  approve calendar access  ──▶        trades it for a 1-hour access token
  copy the refresh token   ──▶        caches that token until it expires
  save it as a secret      ──▶        makes the calendar call, books it

The refresh token is a secret. It goes in wrangler secret put, never in code, never in git, never in this file.

Data model, what to remember

You barely need a database. One small record per conversation is plenty.

 booking_sessions
 ┌────────────────┬──────────────────────────────────────────┐
 │ sender_id      │ who is chatting (phone, chat id)         │
 │ history        │ the last few turns, for context          │
 │ last_offered   │ slots the agent quoted (prevents drift)  │
 │ booked_event   │ Google Calendar event id, once confirmed │
 │ updated_at     │ timestamp, for cleanup                   │
 └────────────────┴──────────────────────────────────────────┘

Cloudflare KV holds this fine. The real, permanent record of the appointment lives where it should: in your Google Calendar.

Cost, why this is genuinely $0/mo

Line item Cost
Cloudflare Workers $0 on the free tier (100k requests/day)
Cloudflare KV $0 on the free tier
Google Calendar API $0, it is free to use
LLM (Gemini 1.5 Flash) about $0.07 per 1M input tokens, so a few dollars a month at real volume

A booking conversation is small. Most owners spend a dollar or two a month on the LLM and nothing else. Compare that to $20 to $50 every month, forever, for a tool you rent.

Week-one checklist

Troubleshooting

Provenance

The architecture here, a single /chat Worker, an LLM tool-calling loop, and SDK-free Google Calendar REST calls, is adapted from the MIT-licensed SMB Forge AI Scheduler: github.com/linmichael123/smbforge-agentic-workflows. This kit is our own step-by-step build of that idea, written so you can stand it up and own it, not a copy of any product.

MEMBERS · FREE · THE KIT

Sign in free to unlock the Free Booking Agent kit

One free membership unlocks this kit, the whole library, and every new workflow the week it ships, plus your members dashboard. One tap with Google or a magic link. No password.

Sign in / Join free

Already a member? This unlocks the moment you sign in.