Back to blog
google-calendarbookingappointmentsopenclawtutorial

Build an AI Appointment Booking Agent (Google Calendar + OpenClaw)

How to build an AI agent that checks availability, books appointments, and sends confirmations using Google Calendar — ideal for service businesses, coaches, and consultants.

By ClawPort Team

Most booking tools are clunky. Calendly is fine, but there's no AI — it just shows a grid of slots and the user picks one. An OpenClaw booking agent is different: it has a conversation. It can ask what kind of appointment is needed, check availability for the right service type, handle rescheduling, and send a custom confirmation — all in a natural back-and-forth.

This guide builds a full booking agent integrated with Google Calendar.

What You're Building

  • AI agent that understands booking requests naturally ("I need to come in Thursday afternoon")
  • Real-time availability checking against Google Calendar
  • Appointment creation with description and notifications
  • Email confirmation to the customer
  • Rescheduling and cancellation handling

Google Calendar API Setup

  1. Go to console.cloud.google.com
  2. Create a new project or select an existing one
  3. Enable the Google Calendar API
  4. Create a Service Account (recommended for server-to-server auth)
  5. Download the JSON key file
  6. Share your calendar with the service account email (give it "Make changes to events" permission)
GOOGLE_SERVICE_ACCOUNT_KEY='{...json key content...}'
[email protected]

Installing the Google Calendar Client

npm install googleapis

Building the Availability Checker Tool

// tools/check_availability.js
import { google } from 'googleapis';

const credentials = JSON.parse(process.env.GOOGLE_SERVICE_ACCOUNT_KEY);
const CALENDAR_ID = process.env.GOOGLE_CALENDAR_ID;

const auth = new google.auth.GoogleAuth({
  credentials,
  scopes: ['https://www.googleapis.com/auth/calendar'],
});

const calendar = google.calendar({ version: 'v3', auth });

export async function checkAvailability({ date, duration = 60 }) {
  // Parse the requested date
  const targetDate = new Date(date);
  const dayStart = new Date(targetDate);
  dayStart.setHours(9, 0, 0, 0); // 9am
  const dayEnd = new Date(targetDate);
  dayEnd.setHours(18, 0, 0, 0); // 6pm
  
  // Get existing events for that day
  const events = await calendar.events.list({
    calendarId: CALENDAR_ID,
    timeMin: dayStart.toISOString(),
    timeMax: dayEnd.toISOString(),
    singleEvents: true,
    orderBy: 'startTime',
  });
  
  // Calculate available slots (every 30 min, filter out busy times)
  const busyTimes = (events.data.items || []).map(e => ({
    start: new Date(e.start.dateTime),
    end: new Date(e.end.dateTime),
  }));
  
  const slots = [];
  let current = new Date(dayStart);
  
  while (current < dayEnd) {
    const slotEnd = new Date(current.getTime() + duration * 60000);
    
    if (slotEnd > dayEnd) break;
    
    // Check if this slot conflicts with any existing event
    const hasConflict = busyTimes.some(busy => 
      current < busy.end && slotEnd > busy.start
    );
    
    if (!hasConflict) {
      slots.push({
        start: current.toISOString(),
        end: slotEnd.toISOString(),
        display: current.toLocaleTimeString('en-US', { 
          hour: 'numeric', 
          minute: '2-digit',
          hour12: true 
        }),
      });
    }
    
    // Move to next 30-min slot
    current = new Date(current.getTime() + 30 * 60000);
  }
  
  return {
    date: targetDate.toDateString(),
    available_slots: slots,
    duration_minutes: duration,
  };
}

Building the Booking Tool

// tools/book_appointment.js
export async function bookAppointment({ 
  startTime, 
  endTime, 
  customerName,
  customerEmail,
  serviceType,
  notes = ''
}) {
  const event = {
    summary: `${serviceType} — ${customerName}`,
    description: `Customer: ${customerName}\nEmail: ${customerEmail}\nNotes: ${notes}`,
    start: {
      dateTime: startTime,
      timeZone: 'America/New_York', // set your timezone
    },
    end: {
      dateTime: endTime,
      timeZone: 'America/New_York',
    },
    attendees: [
      { email: customerEmail },
    ],
    reminders: {
      useDefault: false,
      overrides: [
        { method: 'email', minutes: 24 * 60 }, // 24h before
        { method: 'popup', minutes: 30 },       // 30 min before
      ],
    },
  };
  
  const response = await calendar.events.insert({
    calendarId: CALENDAR_ID,
    resource: event,
    sendUpdates: 'all', // sends email to attendees
  });
  
  return {
    event_id: response.data.id,
    event_link: response.data.htmlLink,
    start: response.data.start.dateTime,
    end: response.data.end.dateTime,
    confirmation_sent: true,
  };
}

The SOUL.md

# BookingBot — [Business Name]

You help clients book appointments for [Business Name].

## Services Available

- **Initial Consultation** (30 min, free) — For new clients
- **Standard Appointment** (60 min, $150) — Regular service
- **Extended Session** (90 min, $200) — Complex cases

## Booking Flow

1. Ask what type of appointment they need
2. Ask for their preferred date and time range ("morning", "afternoon", 
   "Thursday", etc.)
3. Use check_availability to find open slots for that date
4. Present up to 3 available time slots
5. Confirm their choice
6. Ask for their name and email
7. Use book_appointment to create the event
8. Confirm the booking with the date, time, and what to expect

## Important Rules

- Only offer slots during business hours (Mon-Fri, 9am-6pm)
- Allow at least 24 hours advance notice for bookings
- If no slots are available on requested date, suggest the next 
  available day (check availability for that day)
- Always confirm the timezone with out-of-state clients

## Rescheduling

If someone needs to reschedule:
1. Ask for their current appointment details (name + original date)
2. Find a new slot using check_availability
3. Cancel the old event and create a new one
4. Confirm both changes

## After Booking

Let them know:
- They'll receive a calendar invitation at their email
- A reminder will be sent 24 hours before
- To cancel or reschedule, they can reach us at [phone/email]

Handling Natural Language Dates

The agent will receive things like "Thursday afternoon" or "sometime next week in the morning." Your LLM can interpret these, but make sure your SOUL.md instructs it to resolve ambiguity:

## Date Handling

When a date is ambiguous:
- "Thursday" → assume the nearest upcoming Thursday
- "Next week" → Monday through Friday of next week
- "This weekend" → inform them you're only available weekdays

Always confirm the specific date before checking availability:
"Let me check Thursday March 14th — is that the Thursday you had in mind?"

Integration with Email Confirmations

Google Calendar sends invitations automatically when you include attendees in the event creation, but you can also send a custom email via Resend or SendGrid:

import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);

async function sendBookingConfirmation({ customerEmail, customerName, startTime, serviceType }) {
  await resend.emails.send({
    from: '[email protected]',
    to: customerEmail,
    subject: `Confirmed: Your ${serviceType} appointment`,
    html: `
      <h2>Booking Confirmed!</h2>
      <p>Hi ${customerName},</p>
      <p>Your <strong>${serviceType}</strong> is confirmed for 
      <strong>${new Date(startTime).toLocaleString()}</strong>.</p>
      <p>A calendar invitation has also been sent to this email.</p>
      <p>Questions? Reply to this email or call [phone].</p>
    `
  });
}

Self-Hosting This Setup

Running this yourself means managing:

  • Google service account credentials (rotation, security)
  • HTTPS endpoint for the agent
  • Process management and uptime
  • Timezone handling across environments

The service account JSON key needs to be available as an environment variable without being committed to version control. On a VPS, this typically means an .env file or secrets manager setup.

Deploy on ClawPort

ClawPort handles environment variable storage securely — you paste your Google service account JSON once in the dashboard settings, and it's encrypted at rest. No dotenv files on a server somewhere.

For a booking agent like this, the combination of ClawPort ($10/month) + Google Calendar (free) + Resend ($0-20/month depending on volume) gives you a fully functional booking system for under $30/month — compared to dedicated booking software at $50-100/month.

Seven-day free trial at clawport.io.

Ready to deploy your AI agent?

Get started with ClawPort in 60 seconds. No credit card required.

Get Started Free