An agent with an email address gets invited to things. That has been true since the first time we gave an inbox to a support agent and a customer replied with "let's hop on a call Thursday at 2" and a Google Calendar invitation attached. The agent could read the email. It could not do anything sensible with the invitation, because it had nowhere to put it. As of today it does: every Dead Simple inbox has a calendar, and invitations that arrive by email land on it without anyone lifting a finger.
This post walks one scenario end to end. An operations agent receives a meeting invitation, the event appears on its calendar, the agent accepts, then it schedules a follow-up of its own and sends the invite, and finally the human who owns the agent subscribes to its calendar in Google Calendar so they can see what their agent has agreed to. Every call is real, and the calendar page has the full endpoint table.
The setup
One inbox, one webhook. The inbox is ops_9c4e1f2a@box1.deadsimple.email, created the usual way. The webhook subscribes to the three calendar events alongside message.received, and it is scoped to this one inbox with the new inbox_ids field so it never fires for anything else the account does.
from deadsimple import DeadSimple client = DeadSimple(api_key="dse_...") inbox = client.inboxes.create(display_name="Ops Agent") client.webhooks.create( url="https://agent.example.com/hooks/dse", events=["message.received", "calendar.event.created", "calendar.event.updated", "calendar.event.cancelled"], inbox_ids=[inbox.inbox_id], # only this inbox )
1. An invitation arrives
A vendor emails the agent from Google Calendar: "Vendor review, Tuesday September 22, 11:00 to 11:45 Eastern." Google sends that as a normal email with a text/calendar part carrying METHOD:REQUEST. On the way in, Dead Simple parses that part before the message is stored. The event is created on the inbox's calendar with source: inbound, the vendor as organizer, and the agent's address among the attendees with needs-action.
Two things reach the agent. The message.received webhook fires as always, and the message it points at now has a calendar_event block, so the agent does not have to parse an ICS attachment to learn what it was invited to. And a calendar.event.created webhook fires with the same summary as its payload.
{
"message_id": "e7a2c9d1-4b8f-4c3e-9d6a-5f1b0c8e2a74",
"from_email": "dana@vendor.example",
"subject": "Invitation: Vendor review @ Tue Sep 22, 2026 11am - 11:45am (EDT)",
"calendar_method": "REQUEST",
"calendar_event": {
"event_id": "3d7a1f9c-52e4-4b8d-9a6f-1e0c7b2d8f35",
"ical_uid": "7f3a9c1e2b4d4e6f8a0b@google.com",
"title": "Vendor review",
"start_at": "2026-09-22T15:00:00Z",
"end_at": "2026-09-22T15:45:00Z",
"status": "confirmed",
"organizer": { "email": "dana@vendor.example", "name": "Dana" },
"action": "created"
},
"injection_risk": "none",
...
}
The action field is worth a second look. It is what the message did to the calendar: created here, but updated when the organizer moves the meeting, cancelled when they withdraw it, replied when an attendee answers an invitation the agent sent, and unchanged when a stale copy arrives with a lower sequence number and is ignored. An agent that keys its behaviour on action handles the whole lifecycle with one branch per value.
2. The agent RSVPs
The agent checks the slot against its own calendar (one call, calendar.list for that day) and accepts. The RSVP goes to the organizer as a standard iCalendar REPLY through the inbox's normal send path, so Dana sees a green tick in Google Calendar exactly as if a colleague had accepted, and the event's attendee entry for the agent flips to accepted.
ev = msg["calendar_event"] # Anything else on the agent's calendar that day? same_day = client.calendar.list(inbox.inbox_id, start_from="2026-09-22", start_to="2026-09-22") clash = [e for e in same_day["events"] if e["event_id"] != ev["event_id"] and e["start_at"] < ev["end_at"] and e["end_at"] > ev["start_at"]] reply = client.calendar.rsvp( inbox.inbox_id, ev["event_id"], "accepted" if not clash else "tentative", comment="Accepted. I will bring the open items from last week's thread.", ) print(reply["response"], reply["message_id"]) # accepted, the REPLY message we sent
The RSVP endpoint only works on events the inbox does not organize, which is every invitation that arrived by email. Trying to RSVP to your own event is a 400; you would update and re-invite instead. The reply also needs the messages.send permission, because it is a send, and it counts against the plan like one.
3. The agent schedules the follow-up
After the review the agent has an action item: a 30 minute follow-up with Dana and a colleague the week after. This is the agent organizing, so the shape is create, then invite. Creating stores the event and emits calendar.event.created but sends nothing. Inviting sends METHOD:REQUEST to every attendee from the agent's own address, marks the event source: sent, and links the sent message.
followup = client.calendar.create( inbox.inbox_id, title="Vendor review: follow-up on open items", start="2026-09-29T11:00:00", timezone="America/New_York", duration_minutes=30, location="https://meet.example.com/ops-vendor", description="Open items: pricing revision, SLA wording, onboarding dates.", attendees=["dana@vendor.example", {"email": "sam@vendor.example", "optional": True}], ) sent = client.calendar.invite( inbox.inbox_id, followup["event_id"], text_body="Thanks for today. Here is the follow-up we agreed on; accept if the time works.", in_reply_to=msg["message_id"], # thread it under the original invitation ) print(sent["method"], sent["sent_to"]) # REQUEST ['dana@vendor.example', 'sam@vendor.example']
Dana and Sam get a proper invitation with Accept, Maybe and Decline buttons in their mail client. When they click, their client sends a REPLY back to the agent's inbox, and the same inbound path from step one records each answer on the event and fires calendar.event.updated. The agent never polls; it learns who is coming from the webhook.
Now suppose Dana replies by plain email: "Can we do 2pm instead?" The agent moves the event and re-sends. Because the event has already been sent once, the PATCH raises its sequence, and the second invite goes out as a revision of the same ical_uid. Google, Outlook and Apple Calendar apply it to the entry they already have instead of creating a second one, which is the detail that separates a calendar from a script that emails ICS files.
client.calendar.update(inbox.inbox_id, followup["event_id"], start="2026-09-29T14:00:00", timezone="America/New_York") # keeps the 30 minute length client.calendar.invite(inbox.inbox_id, followup["event_id"]) # subject: "Updated invitation: ..." # To call the whole thing off later: cancel, then invite once more (sends METHOD:CANCEL) # client.calendar.update(inbox.inbox_id, followup["event_id"], status="cancelled") # client.calendar.invite(inbox.inbox_id, followup["event_id"])
4. The human subscribes
The person who runs this agent wants to see what it has agreed to without reading its mail. They do not need a Dead Simple login for that. The inbox has a feed, and any calendar app can subscribe to it.
feed = client.calendar.feed(inbox.inbox_id) print(feed["feed_url"]) # https://api.deadsimple.email/v1/inboxes/6f1c.../calendar.ics?token=dsecal_9f3c1a...e7 print(feed["webcal_url"]) # webcal://api.deadsimple.email/v1/inboxes/6f1c.../calendar.ics?token=dsecal_9f3c1a...e7
In Google Calendar on the web: next to Other calendars, click the plus sign, choose From URL, paste the https feed_url, and click Add calendar. "Ops Agent" appears in the sidebar with the vendor review and the follow-up on it, and it syncs to the Google Calendar app on their phone. On a Mac or iPhone, opening the webcal_url subscribes Apple Calendar directly; Outlook takes the https URL under Subscribe from web.
The feed is a plain text/calendar document of every confirmed and tentative event on the inbox. Cancelled events are dropped, so they disappear from the subscriber on the next refresh. There is no API key on the request; the token in the URL is the credential. If it ends up in a screenshot, calendar.rotate_feed issues a new one and the old URL stops answering immediately.
Doing it over MCP instead
Everything above is also two tools on the MCP server, which now has 19. list_calendar_events returns the inbox's calendar soonest first, including everything that arrived by email. create_calendar_event creates an event and, with send_invites=True, sends the invitation in the same call. In Claude, Cursor or Grok Bot the whole follow-up step is one sentence: "Set up a 30 minute follow-up with dana@vendor.example next Tuesday at 11 New York time and send the invite." The older send_calendar_invite tool still exists for one-off invitations that nobody needs to track; the difference is that the calendar remembers.
Where this sits against Nylas and AgentMail
Nylas Agent Accounts bundles a calendar with email, and it also ships Scheduler, a hosted booking page where a human picks a free slot and the event is created for them. Dead Simple does not have a booking page yet. That is the honest gap: if your agent's main job is taking appointments from strangers, a booking link is the right tool and Nylas has one. What Dead Simple has today is the rest: events per inbox, invitations with correct sequencing, RSVPs, automatic ingestion of inbound invitations with a summary on the message and webhooks for each change, and a feed a human can subscribe to. The workaround for booking is the one people used before booking pages existed: the agent proposes two or three times in an email, creates the event when the reply picks one, and sends the invite. It is a few more lines and it works with anyone who has an inbox.
AgentMail has no calendar. An AgentMail inbox that receives an invitation gets an email with an attachment; what happens next is the developer's problem. The same goes for every transactional provider (Resend, Postmark, SendGrid, Mailgun), which is expected, because those are sending products rather than agent inboxes.
Two smaller edges. There is no recurrence expansion yet: a weekly meeting is one event per week. And the feed is read-only: dragging the follow-up to Wednesday in Google Calendar changes the human's copy, not the agent's. Changes go through the API, the SDKs or MCP, which is the point: the agent owns its calendar the way it owns its inbox.
What to build with it
- A support agent that books its own escalation calls. Offer times by email, create the event when the customer picks one, send the invite, and let the human subscribe so they see the call coming.
- A research agent that attends stand-ups. It gets invited like anyone else, accepts, and reads the calendar each morning to know what to prepare. Cancellations remove the entry without the agent parsing a word of the email.
- A coordinator across several agents.
GET /v1/calendar/eventslists every event on every inbox the key can see, so one process can look at the whole fleet's week, and an inbox-scoped webhook per agent keeps each one's changes separate.
Frequently asked questions
Does an agent's inbox have a calendar by default?
Yes. Every inbox, every plan, no setup. Events live under /v1/inboxes/{inbox_id}/calendar/events; GET /v1/calendar/events spans the account.
How does an agent RSVP to an invitation it received by email?
The event is already on the calendar. Call rsvp with accepted, declined or tentative and an optional comment. A standard iCalendar REPLY goes to the organizer and the answer is recorded on the event.
Can a human see the agent's calendar in Google Calendar?
Yes. Get the feed URL from GET .../calendar/feed and add it in Google Calendar under Other calendars, From URL. Apple Calendar and Outlook subscribe to the same feed. Rotate the token if the URL leaks.
Is there a booking page?
Not yet. Nylas has Scheduler; AgentMail has no calendar. On Dead Simple the agent offers times by email and creates the event when the reply arrives. If a booking page would change your decision, say so.
The bottom line
An inbox without a calendar can be invited to things but cannot keep them. Now it can. The invitation lands, the agent answers, it schedules its own follow-ups with invitations that reschedule cleanly, and the human watches it all from the calendar app they already use. The calendar page has every endpoint, the SDK snippets and the subscription steps. Sign up free, create an inbox, and email it an invitation.