Skip to content
Learn Agentic AI
Learn Agentic AI11 min read12 views

Calendar Event Agents: Pre-Meeting Prep, Post-Meeting Summaries, and Follow-Ups

Build an AI calendar agent that prepares meeting briefs, generates post-meeting summaries with action items, and sends automated follow-up emails using Google Calendar webhooks.

Why Calendar Events Drive Valuable Agent Workflows

Meetings consume 15-25% of the average knowledge worker's week, yet most people walk into meetings unprepared and walk out without clear action items. Calendar events are natural trigger points for AI agents because each event has a known start time, end time, attendee list, and often a description that signals the meeting's purpose.

A calendar event agent can deliver three high-value workflows: pre-meeting preparation (gathering context about attendees and topics 30 minutes before), post-meeting summarization (processing notes or transcripts after the meeting ends), and follow-up automation (sending action items and thank-you messages to attendees).

Calendar Webhook Setup

Google Calendar supports push notifications that alert your endpoint when events are created, updated, or deleted. Register a watch on the user's calendar to start receiving notifications.

flowchart LR
    CLIENT(["Client SDK"])
    GW["API Gateway<br/>auth plus rate limit"]
    APP["FastAPI app<br/>handlers and DI"]
    VAL["Pydantic validation"]
    SVC["Service layer<br/>business logic"]
    DB[(Database)]
    QUEUE[(Background queue)]
    OBS[(Tracing)]
    CLIENT --> GW --> APP --> VAL --> SVC
    SVC --> DB
    SVC --> QUEUE
    SVC --> OBS
    SVC --> CLIENT
    style GW fill:#4f46e5,stroke:#4338ca,color:#fff
    style APP fill:#f59e0b,stroke:#d97706,color:#1f2937
    style DB fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
import os
import httpx
from fastapi import FastAPI, Request, BackgroundTasks
from datetime import datetime, timedelta
from openai import AsyncOpenAI

app = FastAPI()
llm = AsyncOpenAI()

GOOGLE_CALENDAR_API = "https://www.googleapis.com/calendar/v3"

async def register_calendar_watch(calendar_id: str, access_token: str):
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            f"{GOOGLE_CALENDAR_API}/calendars/{calendar_id}/events/watch",
            headers={"Authorization": f"Bearer {access_token}"},
            json={
                "id": f"watch-{calendar_id}",
                "type": "web_hook",
                "address": "https://your-agent.com/calendar/webhook",
                "expiration": int(
                    (datetime.utcnow() + timedelta(days=7)).timestamp() * 1000
                ),
            },
        )
        return resp.json()

@app.post("/calendar/webhook")
async def calendar_webhook(request: Request, background_tasks: BackgroundTasks):
    channel_id = request.headers.get("X-Goog-Channel-ID", "")
    resource_state = request.headers.get("X-Goog-Resource-State", "")

    if resource_state == "sync":
        return {"status": "sync_acknowledged"}

    background_tasks.add_task(handle_calendar_change, channel_id)
    return {"status": "accepted"}

Google sends a lightweight notification that something changed, not the full event data. Your handler must fetch the updated events separately.

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →

Fetching Changed Events

Use the sync token pattern to efficiently fetch only events that changed since your last check.

sync_tokens: dict[str, str] = {}

async def handle_calendar_change(channel_id: str):
    calendar_id = get_calendar_for_channel(channel_id)
    access_token = await get_access_token(calendar_id)

    params = {"singleEvents": True, "orderBy": "startTime"}
    token = sync_tokens.get(calendar_id)
    if token:
        params["syncToken"] = token
    else:
        params["timeMin"] = datetime.utcnow().isoformat() + "Z"
        params["timeMax"] = (
            datetime.utcnow() + timedelta(days=7)
        ).isoformat() + "Z"

    async with httpx.AsyncClient() as client:
        resp = await client.get(
            f"{GOOGLE_CALENDAR_API}/calendars/{calendar_id}/events",
            headers={"Authorization": f"Bearer {access_token}"},
            params=params,
        )
        data = resp.json()

    sync_tokens[calendar_id] = data.get("nextSyncToken", "")

    for event in data.get("items", []):
        await process_calendar_event(event, calendar_id)

Pre-Meeting Preparation Agent

Schedule a prep task that fires 30 minutes before each meeting. The agent gathers context about attendees and topics, then sends a brief.

from apscheduler.schedulers.asyncio import AsyncIOScheduler

scheduler = AsyncIOScheduler()

async def process_calendar_event(event: dict, calendar_id: str):
    if event.get("status") == "cancelled":
        scheduler.remove_job(f"prep-{event['id']}", jobstore="default")
        return

    start_str = event.get("start", {}).get("dateTime")
    if not start_str:
        return

    start_time = datetime.fromisoformat(start_str)
    prep_time = start_time - timedelta(minutes=30)

    if prep_time > datetime.now(start_time.tzinfo):
        scheduler.add_job(
            generate_meeting_prep,
            "date",
            run_date=prep_time,
            args=[event, calendar_id],
            id=f"prep-{event['id']}",
            replace_existing=True,
        )

async def generate_meeting_prep(event: dict, calendar_id: str):
    attendees = [a["email"] for a in event.get("attendees", [])]
    attendee_context = await gather_attendee_context(attendees)

    prompt = f"""Prepare a brief meeting prep document.

Meeting: {event.get('summary', 'No title')}
Time: {event['start']['dateTime']}
Description: {event.get('description', 'No description')}
Attendees: {', '.join(attendees)}

Attendee context:
{attendee_context}

Generate:
1. Meeting purpose (1-2 sentences based on title and description)
2. Key attendee info (role, recent interactions, relevant context)
3. Suggested talking points (3-5 bullet points)
4. Questions to prepare for"""

    response = await llm.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}],
    )
    prep_doc = response.choices[0].message.content

    owner_email = get_calendar_owner_email(calendar_id)
    await send_email(
        to=owner_email,
        subject=f"Meeting Prep: {event.get('summary', 'Upcoming Meeting')}",
        body=prep_doc,
    )

Post-Meeting Summary Generation

After a meeting ends, process notes or transcripts to generate a structured summary with action items.

async def generate_post_meeting_summary(
    event: dict, transcript: str | None = None, notes: str | None = None
):
    content = transcript or notes or "No transcript or notes available"

    prompt = f"""Generate a structured meeting summary.

Meeting: {event.get('summary', 'No title')}
Attendees: {[a['email'] for a in event.get('attendees', [])]}
Content: {content[:6000]}

Format the summary as:
## Key Decisions
- List each decision made

## Action Items
- [Owner] Description (Due: date if mentioned)

## Discussion Highlights
- Key points discussed

## Open Questions
- Unresolved items requiring follow-up"""

    response = await llm.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}],
    )
    return response.choices[0].message.content

Automated Follow-Up Emails

Send personalized follow-ups to each attendee with their specific action items highlighted.

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

async def send_follow_ups(event: dict, summary: str):
    action_items = extract_action_items(summary)
    attendees = event.get("attendees", [])

    for attendee in attendees:
        email = attendee["email"]
        their_items = [
            item for item in action_items
            if email in item.get("owner", "").lower()
        ]

        prompt = f"""Write a brief follow-up email for {email} after this meeting.

Meeting: {event.get('summary')}
Full summary: {summary}
Their action items: {their_items}

Keep it under 150 words. Be professional and specific about their tasks."""

        response = await llm.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": prompt}],
        )
        await send_email(
            to=email,
            subject=f"Follow-up: {event.get('summary', 'Meeting')}",
            body=response.choices[0].message.content,
        )

FAQ

How do I get meeting transcripts automatically?

Integrate with a transcription service like Fireflies.ai, Otter.ai, or Google Meet's built-in recording. These services provide webhook callbacks when transcripts are ready. Link the transcript to the calendar event using the event ID or time window matching.

How far in advance should the prep agent run?

Thirty minutes works well for most meetings. For important client calls or board meetings, extend this to 2-4 hours to allow time for manual review and additions. Make the lead time configurable per calendar or meeting type.

What if a meeting is rescheduled?

The calendar webhook fires on updates too. When the event start time changes, cancel the existing prep job and schedule a new one at the updated time. The replace_existing=True parameter in APScheduler handles this automatically.


#CalendarAutomation #AIAgents #MeetingProductivity #GoogleCalendar #FastAPI #AgenticAI #LearnAI #AIEngineering

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like

AI Strategy

AI Agent M&A Activity 2026: Aircall–Vogent, Meta–PlayAI, OpenAI's Six Deals

Q1 2026 saw a record acquisition wave: Aircall bought Vogent (May), Meta acquired Manus and PlayAI, OpenAI closed six deals. The voice AI consolidation phase has begun.

Agentic AI

LangGraph Checkpointers in Production: Durable, Resumable Agents with Eval Replay

Use LangGraph's checkpointer to make agents resumable across crashes and human-in-the-loop pauses, then replay any checkpoint into your eval pipeline.

Agentic AI

LangGraph State-Machine Architecture: A Principal-Engineer Deep Dive (2026)

How LangGraph's StateGraph, channels, and reducers actually work — with a working multi-step agent, eval hooks at every node, and the patterns that survive production.

Agentic AI

Multi-Agent Handoffs with the OpenAI Agents SDK: The Pattern That Actually Scales (2026)

Handoffs done right — when one agent should hand control to another, how to preserve context, and how to evaluate the handoff decision itself.

Agentic AI

Building Your First Agent with the OpenAI Agents SDK in 2026: A Hands-On Walkthrough

Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.

Agentic AI

LangGraph Supervisor Pattern: Orchestrating Multi-Agent Teams in 2026

The supervisor pattern in LangGraph for coordinating specialist agents, with full code, an eval pipeline that scores routing accuracy, and the failure modes to watch for.