AI Agent for Calendar & Scheduling: Automate Meeting Booking, Prep & Time Management
You spend 4.8 hours per week just scheduling meetings. Add prep time, and it's a full day lost. An AI calendar agent handles scheduling, creates pre-meeting briefs, protects your focus time, and optimizes your week.
What's Inside
The Scheduling Time Sink
Scheduling a single meeting takes an average of 8 back-and-forth emails. For someone with 10 meetings per week, that's 80 emails — just for scheduling. Add timezone conversions, room bookings, rescheduling, and conflicts, and you're looking at a full workday lost to calendar management every week.
Tools like Calendly help, but they only solve the inbound booking problem. They don't:
- Read your email and auto-suggest times when someone asks "Can we meet?"
- Prepare a briefing document before each meeting
- Protect your deep work blocks from being overwritten
- Optimize your day to batch similar meetings together
- Reschedule intelligently when conflicts arise
- Summarize what happened and create follow-up tasks after meetings
That's what a calendar agent does. It works across your email, calendar, and CRM to manage your time like a world-class executive assistant.
6 Calendar Agent Capabilities
Natural Language Scheduling
"Schedule a 30-min call with Sarah next week, preferably morning." The agent checks both calendars (via FreeBusy), suggests 3 slots, drafts the invite email, and books it when confirmed. Handles timezone conversion automatically.
Email-Triggered Booking
When someone emails "Can we find time to discuss the proposal?", the agent detects the scheduling intent, checks your availability, and drafts a reply with 3 suggested times — all before you open the email.
Pre-Meeting Briefings
15 minutes before each meeting, the agent sends you a brief: attendee backgrounds (from CRM/LinkedIn), last interaction notes, relevant docs, agenda if available, and suggested talking points.
Focus Time Protection
Block 2-3 hour deep work windows. When someone tries to book during focus time, the agent redirects to alternative slots. Learns your energy patterns — never schedules important meetings during your low-energy periods.
Smart Rescheduling
When a conflict arises, the agent doesn't just alert you. It proposes the optimal reschedule: finds a slot that works for all participants, considers travel time between in-person meetings, respects priority levels.
Week Optimization
Every Sunday evening, the agent reviews your upcoming week: batches similar meetings together, ensures buffer time between calls, moves low-priority meetings to free up your peak hours, and sends a weekly overview.
Build: Auto-Scheduling Agent
import Anthropic from "@anthropic-ai/sdk";
import { google } from "googleapis";
const anthropic = new Anthropic();
const calendar = google.calendar({ version: "v3", auth: oAuth2Client });
async function findAvailableSlots(config) {
const { duration = 30, daysAhead = 7, preferredTimes = "morning",
excludeWeekends = true } = config;
const now = new Date();
const end = new Date(now.getTime() + daysAhead * 86400000);
// Get busy times
const { data } = await calendar.freebusy.query({
requestBody: {
timeMin: now.toISOString(),
timeMax: end.toISOString(),
items: [{ id: "primary" }],
},
});
const busySlots = data.calendars.primary.busy;
// Generate available slots
const slots = [];
const current = new Date(now);
current.setHours(8, 0, 0, 0); // Start at 8 AM
if (current < now) current.setDate(current.getDate() + 1);
while (current < end) {
const dayOfWeek = current.getDay();
if (excludeWeekends && (dayOfWeek === 0 || dayOfWeek === 6)) {
current.setDate(current.getDate() + 1);
current.setHours(8, 0, 0, 0);
continue;
}
const slotEnd = new Date(current.getTime() + duration * 60000);
const hour = current.getHours();
// Check working hours (8-18)
if (hour >= 18) {
current.setDate(current.getDate() + 1);
current.setHours(8, 0, 0, 0);
continue;
}
// Check if slot conflicts with busy times
const isBusy = busySlots.some(busy => {
const busyStart = new Date(busy.start);
const busyEnd = new Date(busy.end);
return current < busyEnd && slotEnd > busyStart;
});
if (!isBusy) {
slots.push({
start: new Date(current),
end: new Date(slotEnd),
period: hour < 12 ? "morning" : hour < 17 ? "afternoon" : "evening",
});
}
current.setMinutes(current.getMinutes() + 30); // 30-min increments
}
// Sort by preference
const preferenceOrder = {
morning: preferredTimes === "morning" ? 0 : 1,
afternoon: preferredTimes === "afternoon" ? 0 : 1,
evening: 2,
};
return slots
.sort((a, b) => preferenceOrder[a.period] - preferenceOrder[b.period])
.slice(0, 5);
}
async function scheduleFromEmail(emailText, senderEmail) {
// Parse scheduling intent
const response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 300,
messages: [{
role: "user",
content: `Extract scheduling details from this email. Return JSON:
{
"wants_meeting": true/false,
"duration_minutes": number (default 30),
"preferred_time": "morning|afternoon|anytime|specific",
"specific_dates": ["any dates mentioned"],
"topic": "what the meeting is about",
"urgency": "this_week|next_week|flexible"
}
Email: ${emailText}`
}],
});
const intent = JSON.parse(response.content[0].text);
if (!intent.wants_meeting) return null;
// Find available slots
const slots = await findAvailableSlots({
duration: intent.duration_minutes,
preferredTimes: intent.preferred_time,
daysAhead: intent.urgency === "this_week" ? 5 : 14,
});
// Format slots for email reply
const formattedSlots = slots.slice(0, 3).map(s => {
const opts = { weekday: "long", month: "short", day: "numeric",
hour: "2-digit", minute: "2-digit" };
return `• ${s.start.toLocaleDateString("en-US", opts)} at ${
s.start.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" })}`;
}).join("\n");
// Draft reply
const reply = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 200,
messages: [{
role: "user",
content: `Draft a brief scheduling reply for a ${intent.duration_minutes}-min meeting about "${intent.topic}".
Available times:
${formattedSlots}
Keep it casual and brief. Just offer the times and ask which works.`
}],
});
return {
draftReply: reply.content[0].text,
suggestedSlots: slots.slice(0, 3),
meetingTopic: intent.topic,
};
}
// Create the calendar event once a slot is confirmed
async function createEvent(slot, details) {
const event = await calendar.events.insert({
calendarId: "primary",
requestBody: {
summary: details.topic,
start: { dateTime: slot.start.toISOString() },
end: { dateTime: slot.end.toISOString() },
attendees: details.attendees.map(e => ({ email: e })),
conferenceData: {
createRequest: {
requestId: `meeting-${Date.now()}`,
conferenceSolutionKey: { type: "hangoutsMeet" },
},
},
reminders: {
useDefault: false,
overrides: [{ method: "popup", minutes: 15 }],
},
},
conferenceDataVersion: 1,
});
return event.data;
}
Build: Meeting Prep Agent
async function generateMeetingBrief(eventId) {
const { data: event } = await calendar.events.get({
calendarId: "primary",
eventId,
});
const attendees = event.attendees?.filter(a => !a.self) || [];
const meetingTime = new Date(event.start.dateTime);
// Gather context for each attendee
const attendeeInfo = await Promise.all(
attendees.map(async (a) => {
const crmData = await getCRMContact(a.email);
const pastEmails = await searchEmails(a.email, 5);
return {
email: a.email,
name: a.displayName || a.email,
crm: crmData,
recentEmails: pastEmails.map(e => e.subject).join(", "),
};
})
);
// Get related documents
const docs = await searchDocs(event.summary);
const response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 800,
messages: [{
role: "user",
content: `Create a pre-meeting brief.
Meeting: ${event.summary}
Time: ${meetingTime.toLocaleString()}
Duration: ${(new Date(event.end.dateTime) - meetingTime) / 60000} min
Description: ${event.description || "None"}
Attendees:
${attendeeInfo.map(a =>
`- ${a.name} (${a.email})
CRM: ${a.crm ? `${a.crm.company}, ${a.crm.title}` : "Not in CRM"}
Recent emails: ${a.recentEmails || "None"}`
).join("\n")}
Related docs: ${docs.map(d => d.title).join(", ") || "None found"}
Generate a concise brief with:
1. **Attendee context** (who they are, relationship history)
2. **Meeting purpose** (best guess from title + context)
3. **Key topics to prepare** (3-5 bullet points)
4. **Questions to ask** (2-3 strategic questions)
5. **Relevant history** (past discussions, open items)
Keep it scannable — this will be read 5 minutes before the meeting.`
}],
});
return response.content[0].text;
}
// Send briefs 15 minutes before each meeting
async function sendDailyBriefs() {
const now = new Date();
const endOfDay = new Date(now);
endOfDay.setHours(23, 59, 59);
const { data } = await calendar.events.list({
calendarId: "primary",
timeMin: now.toISOString(),
timeMax: endOfDay.toISOString(),
singleEvents: true,
orderBy: "startTime",
});
for (const event of data.items || []) {
if (!event.attendees || event.attendees.length < 2) continue;
const meetingTime = new Date(event.start.dateTime);
const briefTime = new Date(meetingTime.getTime() - 15 * 60000);
// Schedule brief delivery
const delay = briefTime.getTime() - Date.now();
if (delay > 0 && delay < 86400000) {
setTimeout(async () => {
const brief = await generateMeetingBrief(event.id);
await sendSlackDM(brief); // Or email, or Notion page
}, delay);
}
}
}
Build: Focus Time Protector
async function protectFocusTime(config) {
const { focusBlocks = [
{ day: 1, start: "09:00", end: "12:00", label: "Deep Work" },
{ day: 3, start: "09:00", end: "12:00", label: "Deep Work" },
{ day: 5, start: "09:00", end: "11:00", label: "Deep Work" },
] } = config;
// Create recurring focus blocks for the next 4 weeks
for (const block of focusBlocks) {
const nextDate = getNextDayOfWeek(block.day);
for (let week = 0; week < 4; week++) {
const date = new Date(nextDate);
date.setDate(date.getDate() + week * 7);
const [startH, startM] = block.start.split(":").map(Number);
const [endH, endM] = block.end.split(":").map(Number);
const start = new Date(date);
start.setHours(startH, startM, 0);
const end = new Date(date);
end.setHours(endH, endM, 0);
// Check if block already exists
const existing = await calendar.events.list({
calendarId: "primary",
timeMin: start.toISOString(),
timeMax: end.toISOString(),
q: block.label,
});
if (existing.data.items?.length > 0) continue;
await calendar.events.insert({
calendarId: "primary",
requestBody: {
summary: `🔒 ${block.label}`,
start: { dateTime: start.toISOString() },
end: { dateTime: end.toISOString() },
transparency: "opaque", // Shows as "busy"
colorId: "11", // Red
reminders: { useDefault: false },
description: "Protected focus time. AI agent will redirect " +
"scheduling requests to other available slots.",
},
});
}
}
}
// When someone tries to book during focus time
async function handleFocusTimeConflict(requestedSlot, requester) {
// Find alternative slots outside focus blocks
const alternatives = await findAvailableSlots({
duration: requestedSlot.duration,
daysAhead: 7,
});
// Filter out focus time slots
const nonFocusSlots = alternatives.filter(slot => {
const hour = slot.start.getHours();
const day = slot.start.getDay();
// Check against focus blocks
return !isFocusTime(day, hour);
});
return {
message: `That time is blocked for deep work. Here are alternatives:`,
alternatives: nonFocusSlots.slice(0, 3),
};
}
Build: Week Optimizer
async function optimizeWeek() {
const weekStart = getNextMonday();
const weekEnd = new Date(weekStart);
weekEnd.setDate(weekEnd.getDate() + 5);
const { data } = await calendar.events.list({
calendarId: "primary",
timeMin: weekStart.toISOString(),
timeMax: weekEnd.toISOString(),
singleEvents: true,
orderBy: "startTime",
});
const events = data.items || [];
const response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1000,
messages: [{
role: "user",
content: `Analyze and optimize this week's calendar.
Events:
${events.map(e => {
const start = new Date(e.start.dateTime || e.start.date);
const end = new Date(e.end.dateTime || e.end.date);
const duration = (end - start) / 60000;
return `${start.toLocaleDateString("en-US", { weekday: "short" })} ` +
`${start.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" })} ` +
`(${duration}min): ${e.summary} [${e.attendees?.length || 0} attendees]`;
}).join("\n")}
Analyze:
1. **Meeting load**: Total hours in meetings vs available work time
2. **Fragmentation**: Are there enough uninterrupted blocks for deep work?
3. **Batching opportunities**: Similar meetings that could be grouped
4. **Buffer time**: Are there breaks between back-to-back meetings?
5. **Energy management**: Are high-stakes meetings at peak energy times?
Provide:
- Week score (1-10) with explanation
- 3 specific optimization suggestions
- Meetings that could be shortened or made async
- Recommended schedule adjustments`
}],
});
return response.content[0].text;
}
Take back your calendar
The AI Employee Playbook includes the complete calendar agent with auto-scheduling, meeting prep, and focus time protection — plus 12 other agent templates.
Get the Playbook — €29Tool Comparison: 7 Calendar AI Tools
| Tool | Best For | Price | AI Features |
|---|---|---|---|
| Calendly | Inbound meeting booking | Free / $10/mo | ⚠️ Basic |
| Reclaim.ai | Focus time protection, smart scheduling | Free / $10/mo | ✅ Good |
| Clockwise | Team calendar optimization | Free / $7/mo | ✅ Good |
| Motion | AI task + calendar management | $19/mo | ✅ Strong |
| x.ai (Scheduler AI) | Email-based scheduling assistant | $8/mo | ✅ Good |
| Cal.com | Open-source Calendly alternative | Free / $12/mo | ⚠️ Basic |
| Custom Agent (this guide) | Full control, multi-tool integration | ~$3-10/mo (API) | ✅ Full |
Our recommendation: Use Reclaim.ai for focus time protection — it's excellent out of the box. Use Calendly for inbound booking. Build a custom agent when you need email-triggered scheduling, CRM-integrated meeting prep, or week optimization that considers your energy and priorities.
Build Your Calendar Agent in 30 Minutes
Set Up Google Calendar API
Enable Google Calendar API in Cloud Console. Use the same OAuth credentials from the Gmail setup (if you have one). Add the calendar scope. The Calendar API is simpler than Gmail — fewer permissions needed.
Build Slot Finder
Copy the findAvailableSlots function above. Test it: does it correctly exclude your existing meetings? Does it respect your preferred times? Add timezone handling with luxon or date-fns-tz.
Add Email Integration
Connect to the Gmail agent (from our Gmail guide). When an email contains scheduling intent, trigger the calendar agent to find slots and draft a reply. This is where the magic happens — zero manual scheduling.
Set Up Meeting Briefs
Schedule the brief generator to run every morning. It checks today's calendar and sends briefs 15 minutes before each meeting. Start with basic attendee lookup — add CRM integration later.
5 Mistakes That Wreck Your Schedule
Auto-booking without confirmation
Never let the agent book meetings without your approval. Suggest times and draft invites, but wait for you to confirm. One auto-booked meeting with the wrong person or at the wrong time creates a mess.
Ignoring buffer time
Back-to-back meetings are exhausting. Always add 15-minute buffers between meetings. Your agent should treat buffer time as "busy" when finding slots. This alone improves meeting quality dramatically.
Not handling timezones properly
If you suggest "Tuesday at 2 PM" without specifying the timezone, you'll get double-booked when the other person is in a different zone. Always include timezone in suggested times. Use the attendee's timezone when possible.
Treating all meetings equally
A 1:1 with your CEO and a vendor demo are not the same. Your agent should prioritize: client meetings > internal strategy > 1:1s > info sessions > vendor demos. High-priority meetings get peak hours.
No "meeting-free" boundaries
Without hard limits, your calendar will fill up completely. Set rules: no meetings before 9 AM, no meetings after 5 PM, minimum 2 hours of focus time per day, one meeting-free day per week. Your agent enforces these like a bouncer.
Get your time back
The AI Employee Playbook includes the complete calendar agent with auto-scheduling, prep briefs, and week optimization — plus 12 other production-ready agents.
Get the Playbook — €29What's Next
Start with the slot finder and email integration — that's where the biggest time savings are. Then add meeting prep briefs (your meetings will be noticeably more productive). Finally, set up focus time protection and week optimization.
The goal isn't to have fewer meetings. It's to make scheduling invisible — so fast and effortless that you never think about it. Your AI agent handles the logistics. You show up prepared and focused.
Want the complete agent-building system?
The AI Employee Playbook covers the 3-file framework, memory systems, autonomy rules, and real production examples.
Get the Playbook — €29