Here's the timeline when a human invites a bot to a meeting, done right:
-
T+0s — Alice adds
scheduler@yourcompany.comto her event in Google Calendar. -
T+2s — the invitation lands in the bot's mailbox; the platform parses it and creates a matching event on the bot's calendar. An
event.createdwebhook fires. - T+3s — your code inspects the event, checks free/busy, decides to attend.
- T+4s — one API call sends the RSVP. Alice's calendar flips the bot to "accepted," and every other attendee's calendar updates too.
No email parsing, no ICS file handling, no human. The mechanism behind it is a Nylas Agent Account — a hosted email-plus-calendar identity, currently in beta — whose primary calendar speaks standard iCalendar with Google Calendar, Microsoft 365, and Apple Calendar. To the organizer, the bot is just another attendee.
What arrives when the bot is invited
You never touch the invitation email. When the invite hits the mailbox, the platform parses the meeting details and creates the event on the agent's primary calendar automatically. The event.created payload already contains the organizer, participants, times, and description — everything the bot needs to decide.
Two details to check on that event object:
- The agent appears in
participants[]withstatus: "noreply"— its RSVP is pending. - The organizer is whoever sent the invite, not the agent. That distinction drives which operations make sense (an invitee RSVPs; only an organizer updates or cancels).
Worth knowing: because an Agent Account is a real mailbox too, the invitation also fires a message.created webhook for the invite email itself. Pick one trigger to drive your calendar logic — event.created is the structured one — and ignore the other, or you'll process every invite twice.
The RSVP call
Responding is a single POST with one of three statuses — yes, no, or maybe:
curl --request POST \
--url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/events/<EVENT_ID>/send-rsvp?calendar_id=primary" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{ "status": "yes" }'
Under the hood this sends an ICS REPLY to every participant. The organizer sees the agent as accepted (or declined, or tentative) right next to the human attendees, and other attendees' calendars pick up the change automatically. After the call, event.updated fires on the agent's own calendar, so your state machine sees its own response land.
One trap to avoid: a plain reply email doesn't update anyone's calendar. If your agent answers an invite with "Sounds good, I'll be there!" in prose, the organizer's calendar still shows it as not responded. send-rsvp is the only path that moves the status. (By all means send the friendly email too — just send the RSVP first.)
Deciding yes or no: free/busy
A bot that accepts everything double-books itself by Wednesday. Before RSVPing, query the free/busy endpoint, which returns the busy blocks on the agent's primary calendar over a time range. If the invite's window collides with an existing block, decline or mark tentative. The calendar docs cover the endpoint alongside the rest of the events surface.
The other direction: the bot as organizer
Invitee and organizer are just the two directions of one calendar. The same account can host:
curl --request POST \
--url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/events?calendar_id=primary¬ify_participants=true" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{
"title": "Product demo",
"when": { "start_time": 1744387200, "end_time": 1744390800 },
"participants": [
{ "email": "alice@example.com" },
{ "email": "bob@example.com" }
]
}'
With notify_participants=true, each participant gets a normal invite from the bot's address. When Alice clicks Yes in Google Calendar, Google sends the response back to the bot's mailbox, the platform reads it, updates her participants[].status on the event, and fires event.updated — so the bot knows who accepted without ever inspecting the response email. PUT /events/{id} pushes time or title changes to every participant's calendar, and DELETE /events/{id} sends the ICS CANCEL.
What about "can we do Tuesday instead?"
Counter-proposing a time isn't a first-class endpoint today. The documented pattern: RSVP no or maybe so calendars reflect reality, then reply to the invite email proposing an alternative and let the organizer create the new event. If your bot's whole job is negotiating times — proposing slots, collecting picks, booking the winner — that's what Scheduler is built for, and it works with Agent Accounts. The Events API is the right layer when the time is already known and the bot just needs to respond or create.
Three details that bite later
-
notify_participantsis a per-call decision. Every create, update, and delete with it set totruesends email; withfalse, the change is silent. Silent deletes are the sneaky one — removing an event without notification leaves the meeting sitting on everyone else's calendar. Delete with notification unless you have a specific reason not to. -
The bot has no default time zone. A human's calendar carries a time zone; an Agent Account's doesn't. Pass
timezoneexplicitly on create, or stick to epochstart_time/end_timevalues everywhere and convert at the edges. -
One calendar isn't the limit. The primary calendar is provisioned automatically (and can't be deleted while other calendars exist), but you can create additional calendars up to your plan's cap — a
sales-callscalendar and aninternalcalendar on the same agent keeps free/busy queries and event listings cleanly separated by concern.
A complete RSVP loop, in five steps
- Subscribe a webhook to
event.created(andevent.updatedif you track changes). - On each new event where the agent's
statusisnoreply, fetch the event details. - Run free/busy for the event's window.
- POST to
send-rsvpwithyes,no, ormaybe. - Optionally, send a short human-readable reply in the email thread for the people on the other side.
That's the entire integration — three webhook triggers (event.created, event.updated, event.deleted) and a handful of endpoints, all hanging off one grant_id. The quickstart gets you an account with a live calendar in under 5 minutes.
Test it the satisfying way: create the account, invite it to a meeting from your own calendar, and run the curl above with "status": "yes". Watching the acceptance pop up on your own invite is the moment the abstraction earns your trust.
For further actions, you may consider blocking this person and/or reporting abuse
