Most "AI scheduling" demos point a model at a human's inbox, parse a few invite emails, and call it a day. That's fine until you want the agent to actually run the meeting — to be the organizer whose name is at the top of the invite, whose calendar is the source of truth, and who has to know, in real time, who's coming.
That last part is the interesting one. When your agent is the organizer, sending the invite is the easy 20%. The other 80% is reconciliation: someone accepts, someone declines, someone flips from "yes" to "maybe" the morning of, and the agent's internal picture of "who is attending this meeting" has to stay correct without you babysitting it. RSVP replies arrive over the next few hours and days as ICS REPLY messages bouncing back from Google Calendar, Outlook, and Apple Calendar. If you're treating those as emails to parse, you've signed up for the worst job in calendaring.
This post is about doing it the boring, correct way: let the agent send the invite, let Nylas fold the incoming RSVP replies into the event's participant list, and react to a single webhook when status changes. I work on the Nylas CLI, so the terminal commands below are the exact ones I reach for when I'm poking at this by hand before wiring it into app code.
What you actually get
An Agent Account is a Nylas grant with its own real mailbox and its own real calendar. From a participant's side there's nothing special about it — it shows up as a normal organizer on a normal invite. Under the hood it speaks standard iCalendar, so it interoperates with Google Calendar, Microsoft 365, and Apple Calendar as a first-class participant.
The reconciliation loop has three moving parts, and Nylas owns two of them for you:
Send the invite. Create an event with participants andnotify_participants=true. Nylas sends an ICSREQUESTfrom the Agent Account's address to each attendee.
Absorb the replies. When an attendee clicks Yes / No / Maybe in their calendar client, their provider mails an ICSREPLYback to the Agent Account's mailbox. Nylas reads it and updates that participant'sstatuson the event object toyes,no,maybe, ornoreply. You don't parse anything.
React. Each time a participant's status changes, anevent.updatedwebhook fires for the Agent Account's calendar. That's your cue to recompute attendance and do whatever your app does with it.
The conceptual pivot worth internalizing: the event object is your participant database. You don't reconstruct attendance from a pile of reply emails — you read it off participants[].status, which Nylas keeps current. The walks through provisioning; the short version from the CLI is:
The same thing over raw HTTP is a POST /v3/connect/custom with provider: "nylas" and a settings.email on a domain you've registered — no refresh token, no OAuth dance:
curl --request POST \
--url "https://api.us.nylas.com/v3/connect/custom" \
--header "Authorization: Bearer $NYLAS_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"provider": "nylas",
"name": "Scheduling Agent",
"settings": { "email": "[email protected]" }
}'
Either way you get a grant with a primary calendar already provisioned. Grab the grant_id — every endpoint below is grant-scoped at /v3/grants/{grant_id}/..., which is the whole point of the grant abstraction: there's nothing new to learn on the data plane. An Agent Account hits the same Events endpoints as any connected Google or Microsoft grant.
For the raw HTTP examples I'll use https://api.us.nylas.com and a bearer token:
export NYLAS_API_KEY="<your-api-key>"
export GRANT_ID="<agent-account-grant-id>"
Step 1 — Send the invite as the organizer
Creating an event with participants and notify_participants=true is what makes the Agent Account the organizer of record. Nylas sends the ICS REQUEST and the attendees see an invite from [email protected].
Here's the API call. Note notify_participants=true is a query parameter, not a body field — this is the flag that turns "save a private event" into "send invitations":
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": "[email protected]" },
{ "email": "[email protected]" }
]
}'
The response includes the new event's id and a participants array where each entry currently reads "status": "noreply". Hold onto that event id — it's the key you'll read status back from and the key the webhook will reference.
The CLI equivalent uses nylas calendar events create with one --participant flag per attendee:
nylas calendar events create "$GRANT_ID" \
--calendar primary \
--title "Product demo" \
--start "2026-07-15 10:00" \
--end "2026-07-15 11:00" \
--participant "[email protected]" \
--participant "[email protected]"
One thing to call out honestly: as of CLI v3.1.27, nylas calendar events create doesn't expose a --notify-participants flag — there's no such flag, so don't invent one. When you need to be explicit about the notify_participants=true query parameter (for example to guarantee invites go out, or to suppress them with false for a silent backfill), reach for the API call. The CLI is the fast path for creating the event; the curl form is where you control the notification semantics precisely. I use the CLI to set things up interactively and the API in the actual service.
A note on invite quota: every create, update, and delete sent with notify_participants=true counts against the Agent Account's daily send limit, because each invitation is an outbound email. If the account is over quota the event still saves but the invitation is skipped silently. Worth a guard in your app.
Step 2 — Read participant status off the event
Once invites are out, the event object becomes your live attendance record. As RSVP replies arrive, Nylas updates participants[].status in place. You read it back with a plain GET on the event:
curl --request GET \
--url "https://api.us.nylas.com/v3/grants/$GRANT_ID/events/<EVENT_ID>?calendar_id=primary" \
--header "Authorization: Bearer $NYLAS_API_KEY"
The participants array now reflects reality — something like:
"participants": [
{ "email": "[email protected]", "status": "yes" },
{ "email": "[email protected]", "status": "noreply" }
]
Alice accepted; Bob hasn't answered. No email parsing happened on your side — Nylas folded Alice's ICS REPLY into the event for you. The four values you'll see are yes, no, maybe, and noreply.
From the CLI, nylas calendar events show (aliased as read and get) fetches the same event:
nylas calendar events show <EVENT_ID> "$GRANT_ID" --calendar primary --json
Pipe that through jq '.data.participants' and you've got the same status list in the terminal. This is genuinely the move I make first when something looks off — read the event, look at the statuses, trust the object before I trust anything I think I remember sending.
Polling this endpoint on a cadence is a legitimate pattern for batch jobs. But if you want to react the moment someone responds, polling is the wrong tool. That's what the webhook is for.
Step 3 — React to status changes with the event.updated webhook
When a participant's status changes, Nylas fires an — the organizer/invitee model in full, including send-rsvp for the inverse case where the agent is the invitee.
— every
nylas calendar events and nylas webhook flag, verified against v3.1.27.The short version: when the agent owns the invite, don't parse reply emails — let Nylas reconcile RSVPs onto the event, read status off the object, and let event.updated tell you the moment it changes. The reaction logic is yours; the reconciliation is handled.
SOCIAL SHARE CARD GENERATOR