Skip to content
Blog / Developer Resources

How to Give Your AI Agent a Calendar

Binoy Perera

A step-by-step guide to giving an AI agent its own calendar with AgentMail: create the inbox, book meetings, answer invitations and act when meetings start, end or move.

  • calendar
  • ai-agents
  • webhooks
  • scheduling
  • tutorial

TL;DR

  • Every AgentMail inbox comes with its own calendar, so giving an agent a calendar starts with creating an inbox.
  • The agent can book meetings, answer invitations and get a webhook when a meeting changes, starts or ends. A recurring event can also replace a cron job.
  • Give the agent free/busy access to your own calendar, not read and write access.

An agent that books meetings needs somewhere to keep them. It has to know what it agreed to, notice when the other side moves a meeting, and do something when a meeting starts or ends. A calendar does all three.

The usual workarounds are giving the agent access to your own calendar, or setting up a Google account for each agent. The first hands the agent your whole schedule. The second costs a paid Workspace seat per agent. Google's push notifications also only tell you that something on a calendar changed. Nothing fires when a meeting starts, so you still run a scheduler.

This guide uses AgentMail, where every inbox comes with a calendar. The examples are in Python, and the TypeScript SDK has the same methods.

Why does an agent need its own calendar?

An agent needs its own calendar because the meetings it books belong to other people too, and they can move or cancel them at any time. A calendar keeps the agent's copy of each meeting in step with those changes, and tells the agent when a meeting moves, starts or ends.

A reminder or a cron timer belongs to the agent, and nobody else can change it. A meeting belongs to everyone on it. Once an agent books meetings and then acts on them, by sending a quote, a call link or a follow-up, it needs to hear about it when someone else moves one.

Our in-house sales agent, Jordan, ran into exactly this. It books demos with prospects and sends each one a custom quote 30 minutes before the call. Recently, one prospect moved their call to Friday. Jordan had set itself a timer when the demo was booked, nothing told the timer the meeting had moved, and the quote went out 30 minutes before a call that wasn't happening anymore.

Step 1: How do you give your agent a calendar?

Log into the AgentMail console and create an inbox for the agent, or create one with the SDK. The calendar is created with the inbox, at the same address, and there is nothing to turn on. In the console it sits in the inbox's Calendar tab.

Python

from agentmail import AgentMailfrom agentmail.inboxes import CreateInboxRequestclient = AgentMail()  # reads AGENTMAIL_API_KEYinbox = client.inboxes.create(request=CreateInboxRequest(username="jordan"))inbox_id = inbox.inbox_id  # jordan@agentmail.to

If your agent already has an AgentMail inbox, it already has a calendar, so skip to step 2.

Step 2: How do you set the calendar's time zone?

A new calendar starts in UTC. Events created without a time zone use the calendar's default, so set it to the zone your agent works in.

Python

client.inboxes.calendar.update(inbox_id, timezone="America/New_York")

Event times are wall-clock times in an IANA time zone, the way people write meetings. A weekly 9:00 meeting stays at 9:00 local time across daylight saving changes.

Step 3: Which webhooks should the agent subscribe to?

Subscribe to the six calendar events, plus message.received so the agent sees invitation emails as they arrive.

Python

client.webhooks.create(    url="https://your-agent.example.com/webhooks/agentmail",    event_types=[        "message.received",        "calendar.event.created",        "calendar.event.updated",        "calendar.event.deleted",        "calendar.event.responded",        "calendar.event.starting",        "calendar.event.ending",    ],    inbox_ids=[inbox_id],    client_id="jordan-calendar-webhook",)
EventSent when
calendar.event.createdThe agent creates an event, or an invitation it received lands on its calendar
calendar.event.updatedAn event changes. For one-off and recurring events, the payload includes previous, the old values of what changed
calendar.event.deletedAn event is deleted
calendar.event.respondedThe agent answers an invitation, or someone answers one the agent sent
calendar.event.starting and calendar.event.endingAn event starts or ends. Usually within a minute, never early, and not for an event cancelled before it starts

Webhooks are delivered at least once, so deduplicate on event_id.

How does the agent book a meeting?

Create an event with attendees and send_invites=True. The inbox becomes the organizer, and each attendee gets a standard invitation that opens in Google Calendar, Outlook or Apple Calendar.

Python

created = client.inboxes.calendar.create_event(    inbox_id,    title="AgentMail demo with Acme",    start="2026-11-12T14:00:00",    end="2026-11-12T14:30:00",    timezone="America/New_York",    location="https://meet.example.com/acme-demo",    attendees=[{"email": "jane@acme.com", "name": "Jane Doe"}],    send_invites=True,    client_id="demo-acme-2026-11-12",    metadata={"crm_deal_id": "D-1042"},)

client_id makes the request safe to retry. A retry with the same client_id returns the original event and doesn't send the invites again. metadata is for your own records. It comes back in every webhook for the event and is never sent to attendees, so your handler knows which deal a meeting belongs to without a lookup.

When Jane accepts, her calendar app emails a reply to the inbox, her status on the event changes to accepted, and the agent gets calendar.event.responded.

If the meeting has to move, update the event and pass send_invites=True again. Jane gets an updated invitation, and the start and end webhooks move to the new time. Send start and end together, even if only one changed.

Python

client.inboxes.calendar.update_event(    inbox_id,    created.event.event_id,    start="2026-11-13T14:00:00",    end="2026-11-13T14:30:00",    send_invites=True,)

To call it off, delete the event with send_invites=True and every attendee gets a cancellation. Without send_invites, a change stays on the agent's calendar and attendees keep the old version.

Invitations count toward the inbox's normal send limits, one per attendee.

How does the agent answer invitations?

When someone invites the agent's address to a meeting, the event goes on its calendar automatically. The message.received webhook for that email waits until the event is on the calendar, so the message already carries calendar_event_id, and the agent can answer straight from the webhook.

Python

def on_message_received(payload):    message = payload["message"]    event_id = message.get("calendar_event_id")    if event_id and organizer_is_trusted(message):        client.inboxes.calendar.respond_to_event(            inbox_id,            event_id,            status="accepted",  # or "declined", "tentative"        )

organizer_is_trusted is yours to write. Most agents should accept invitations from people they already work with and leave the rest for a human.

To catch anything the agent hasn't answered, check the agenda for events still marked needs_action:

Python

agenda = client.inboxes.calendar.get_agenda(inbox_id)waiting = [e for e in agenda.events if e.response_status == "needs_action"]

An invitation the agent received belongs to whoever sent it. The agent can edit its own copy, for example to add metadata, but those edits aren't sent to anyone. To move the meeting, the agent has to email the organizer and ask.

How does the agent act when a meeting starts, ends or moves?

Handle the start and end webhooks for anything that should happen at the meeting itself, like sending the call link or a follow-up.

Python

def on_calendar_event(payload):    event = payload["calendar_event"]    kind = payload["event_type"]    if kind == "calendar.event.starting":        send_call_link(event)    elif kind == "calendar.event.ending":        draft_follow_up(event)    elif kind == "calendar.event.updated" and "start" in payload.get("previous", {}):        reschedule_pre_meeting_work(event["event_id"], event["start_at"])

The last branch is the fix for Jordan. Start webhooks never fire early, so anything that has to happen before a meeting, like a quote 30 minutes ahead, still needs its own timer. Key that timer to the event, and when calendar.event.updated arrives with a changed start, move the timer to the new start_at. Changes to a single date of a recurring meeting arrive without previous, so for those, compare start_at with the time you stored.

Can the calendar run the agent's recurring jobs?

An agent that does something every week usually runs on a cron job, from a hosted service or written by hand. Writing one is more work than it looks. A coding challenge that asks developers to build an agent scheduler lists parsing cron expressions, keeping runs from drifting or getting missed, retrying failures with backoff, and saving schedules so they survive a restart.

A recurring event does the same job. On Hacker News, one developer complained that agent products offer "only toy-level configuration" for scheduled work and wished they had the recurrence options of a calendar app. A calendar event has those options already, because it uses the same RRULE format calendar apps use.

Python

client.inboxes.calendar.create_event(    inbox_id,    title="Weekly pipeline summary",    start="2026-11-09T09:00:00",  # a Monday    end="2026-11-09T09:15:00",    timezone="America/New_York",    recurrence={"rule": "FREQ=WEEKLY;BYDAY=MO"},    metadata={"job": "pipeline_summary"},)

Every Monday at 9:00 New York time, the agent gets calendar.event.starting with the event's metadata, reads the job name and runs it. To skip a week, cancel that date. To move the job to Tuesdays, update the series. The schedule also shows up on the agent's calendar in the AgentMail console, so anyone on the team can see what the agent runs and when.

How do you stop the agent booking over your own meetings?

The agent's calendar only knows about the agent's events. It can't look up when other people are free, so it proposes times over email and books once the other side agrees.

For your own availability, give the agent free/busy access to your calendar instead of read and write access. The Google Calendar API has a calendar.freebusy scope that returns your busy times and nothing else. The agent checks it before proposing times, books on its own calendar and invites you. We wrote about why that split matters in Should You Give Your Agent Access to Your Calendar?.

How do you limit what the agent's calendar accepts?

An invitation only lands on the agent's calendar if the email passed sender authentication and came from the event's organizer. You can narrow that further in two places.

A receive allow list on the inbox limits who can email it, which also limits who can invite it. An entry can be one address or a whole domain:

Python

client.inboxes.lists.create(inbox_id, "receive", "allow", entry="yourcompany.com")

A scoped API key gives the agent only the calendar permissions it needs. There are six: calendar_read, calendar_update, calendar_event_read, calendar_event_create, calendar_event_update and calendar_event_delete. An agent that only books and answers meetings doesn't need calendar_event_delete.

Can you give an agent a calendar without writing code?

If your agent runs in Claude, Cursor or another MCP client, connect the AgentMail MCP server. It includes calendar tools: get_agenda, get_event, create_event, update_event, delete_event, respond_to_invitation, get_calendar and update_calendar. You can ask the agent what's on its calendar this week, or tell it to accept the invite from Jane, and it calls the tools itself.

The webhooks from step 3 still need a server. An MCP client can read and change the calendar, but it can't wake up on its own when a meeting starts.

What should you know before going live?

  • Calendar is available in US production. It isn't in EU production yet.
  • A calendar holds up to 10,000 events by default.
  • Webhooks can arrive out of order. An event created a moment before it starts can deliver calendar.event.starting before calendar.event.created.
  • A start or end webhook carries the event as it was when that moment was first processed, and a retry carries the same body. Read the event from the API when you need its current state.
  • If more than one process writes to the same calendar, pass the event's etag as if_match when you update it. A write based on an old read then fails with a 412 instead of overwriting someone else's change.
  • All-day events have an inclusive end date. A one-day event has the same start and end.
  • Recurring invitations from Google Calendar that include edited dates aren't imported yet.
  • You can see the agent's calendar in the AgentMail console, with a badge on anything still waiting for a reply.

The full field list, payloads and error codes are in the calendar documentation.

FAQ

Questions, answered.

Create an AgentMail inbox. Every inbox has a calendar at the same address, so the agent can create events, send standard invitations, receive invitations by email and get webhooks when events change, start and end.

Yes. Create the event with attendees and send_invites set to true. Each attendee gets a standard iCalendar invitation that opens in Google Calendar, Outlook and Apple Calendar, and their replies update the event.

Subscribe to the calendar.event.starting and calendar.event.ending webhooks. They fire for every event and every date of a recurring event, usually within a minute of the start or end, never early, and not at all for cancelled events.

The updated invitation changes the event on the agent calendar, the start and end webhooks move to the new time, and the agent gets calendar.event.updated. For one-off and recurring events the payload includes previous, the old values of the fields that changed.

Update the event with the new start and end and send_invites set to true. Attendees get an updated invitation and the start and end webhooks move to the new time. Deleting the event with send_invites set to true sends a cancellation. For an invitation the agent received, only the organizer can move it, so the agent asks by email.

Yes. Give it free/busy access to your calendar, such as the calendar.freebusy scope in the Google Calendar API, which returns busy times without titles or attendees. The agent books on its own calendar and invites you.

Yes. The AgentMail MCP server includes calendar tools for reading the agenda, creating and changing events, and responding to invitations. Webhooks still need a server, because an MCP client cannot wake itself up when a meeting starts.

Blog

Suggested reading

View all posts

News · October 15, 2026

Your agent has an inbox. Now it has a calendar.

Every AgentMail inbox now includes a calendar. Agents book meetings, accept invitations, and get webhooks when meetings start and end. Live for every US organization.

Prompt

Give yourself access to AgentMail by following the guide at:

https://docs.agentmail.to/agent-onboarding.md

and refer to:

https://docs.agentmail.to/llms.txt

for the documentation index.

Start integrating AgentMail into your AI agents today.