cd /news/ai-agents/let-an-agent-own-the-invite-rsvp-rec… Β· home β€Ί topics β€Ί ai-agents β€Ί article
[ARTICLE Β· art-46444] src=dev.to β†— pub= topic=ai-agents verified=true sentiment=↑ positive

Let an agent own the invite: RSVP reconciliation with Agent Accounts

Nylas introduced Agent Accounts that allow AI agents to act as meeting organizers with automatic RSVP reconciliation. The system uses iCalendar standards to send invites and automatically updates participant status via webhooks, eliminating the need for manual email parsing. This approach handles the complexity of cross-provider RSVP replies from Google Calendar, Outlook, and Apple Calendar.

read10 min views1 publishedJul 1, 2026

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.

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:

notify_participants=true

. Nylas sends an ICS REQUEST

from the Agent Account's address to each attendee.REPLY

back to the Agent Account's mailbox. Nylas reads it and updates that participant's status

on the event object to yes

, no

, maybe

, or noreply

. You don't parse anything.event.updated

webhook 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 Agent Account calendars doc confirms this reconciliation behavior specifically for Agent Account grants: each response lands in the mailbox, Nylas reads it, and the event's participants[].status

is updated automatically.

If you went the naive route β€” agent reads its inbox, finds the "Accepted: Product demo" email, regexes out who and what β€” you'd be reimplementing an iCalendar parser badly. A few reasons not to:

REPLY

messages are not consistent across providers.METHOD:REPLY

block is consistent, but now you're parsing MIME and text/calendar

parts by hand.status

. Re-deriving that from the raw email is strictly more work for a worse result.The honest tradeoff: you're trusting Nylas's reconciliation instead of owning the parse. For organizer-side RSVP tracking, that's the trade you want. If you needed full round-trip time negotiation β€” propose slots, collect picks, book the winner β€” that's a different problem, and one the Events API doesn't try to solve on its own.

You need an Agent Account and an API key. If you don't have an account yet, the Agent Accounts quickstart walks through provisioning; the short version from the CLI is:

nylas agent account create scheduler@yourcompany.nylas.email --name "Scheduling Agent"

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": "scheduler@yourcompany.nylas.email" }
  }'

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>"

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 scheduler@yourcompany.nylas.email

.

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&notify_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" }
    ]
  }'

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 "alice@example.com" \
  --participant "bob@example.com"

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.

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": "alice@example.com", "status": "yes" },
  { "email": "bob@example.com",   "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.

When a participant's status changes, Nylas fires an event.updated webhook for the Agent Account's calendar. The supported-endpoints reference lists

event.updated

explicitly as a trigger for Agent Account grants, so this is a supported path, not a hopeful one. This is what turns the loop from "the agent checks occasionally" into "the agent knows within seconds."One important detail about scope: webhooks are application-scoped, not grant-scoped. You subscribe once at the app level, and notifications for every grant in the app land at the same endpoint, each payload carrying the grant_id

you filter on. So you create the subscription against /v3/webhooks

, not against the grant:

curl --request POST \
  --url "https://api.us.nylas.com/v3/webhooks" \
  --header "Authorization: Bearer $NYLAS_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "trigger_types": ["event.updated"],
    "webhook_url": "https://yourapp.com/webhooks/nylas",
    "description": "RSVP reconciliation for the scheduling agent"
  }'

The CLI mirrors this with nylas webhook create

:

nylas webhook create \
  --url "https://yourapp.com/webhooks/nylas" \
  --triggers event.updated \
  --description "RSVP reconciliation for the scheduling agent"

--triggers

takes a comma-separated list or repeated flags, so in practice I subscribe to event.created,event.updated,event.deleted

together and branch in the handler. The part I like as an SRE: one subscription covers every Agent Account you ever provision in that app. You don't re-subscribe per agent.

When event.updated

arrives, the payload carries the event id and grant_id

. The handler shape is the same regardless of why the event changed:

X-Nylas-Signature

, a hex HMAC-SHA256 of the raw request body using your webhook secret. Compare it constant-time, and guard that both buffers are the same length first or the comparison throws on mismatch. nylas webhook verify

does this locally while you're testing.id

GET

from Step 2. Don't trust a status snapshot embedded in the payload β€” read the current participant list from the source of truth.Here's the boundary worth being precise about. Nylas keeps participants[].status

correct. Everything you do with a status change β€” that's your application logic, and it lives in your code and your database.

Concretely, the things teams usually want β€” "nudge everyone still on noreply

24 hours out," "alert the host if the key stakeholder declines," "auto-cancel if fewer than three people accept" β€” none of those are Nylas features. They're a function over the participant list that you run when the webhook fires. The pattern is:

metadata

on events, so you can't stash this on the Nylas side β€” own the state yourself.event.updated

, refetch, diff against your stored row, and emit whatever actions the delta implies (a reminder email via nylas email send

, a Slack ping, a row update).And to be clear about what's not on the table here: Scheduler isn't available for Agent Account grants, so this isn't a "let people pick a slot" flow. The agent already knows the time. Its job is to send the invite and keep an accurate, real-time picture of who's in β€” which the Events API plus event.updated

handles cleanly.

Reconciliation isn't only inbound. When the agent changes the meeting β€” say it pushes the start time, or swaps the participant list to nudge non-responders β€” those changes have to propagate too. A PUT

on the event sends an ICS update to every participant:

curl --request PUT \
  --url "https://api.us.nylas.com/v3/grants/$GRANT_ID/events/<EVENT_ID>?calendar_id=primary&notify_participants=true" \
  --header "Authorization: Bearer $NYLAS_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{ "when": { "start_time": 1744390800, "end_time": 1744394400 } }'

The CLI equivalent is nylas calendar events update

, which takes the event id and the fields you're changing:

nylas calendar events update <EVENT_ID> "$GRANT_ID" \
  --calendar primary \
  --start "2026-07-15 11:00" \
  --end "2026-07-15 12:00"

Same --notify-participants

caveat as create: the CLI flag doesn't exist in v3.1.27, so when you need the explicit notify_participants=true

query parameter on the update, use the API call.

Cancelling is a DELETE

. With the Agent Account as organizer, it sends an ICS CANCEL

to every participant when notify_participants=true

:

curl --request DELETE \
  --url "https://api.us.nylas.com/v3/grants/$GRANT_ID/events/<EVENT_ID>?calendar_id=primary&notify_participants=true" \
  --header "Authorization: Bearer $NYLAS_API_KEY"

From the CLI that's nylas calendar events delete

, with --force

to skip the confirmation prompt in a script:

nylas calendar events delete <EVENT_ID> "$GRANT_ID" --calendar primary --force

The gotcha: deleting without notification leaves the meeting sitting on everyone's calendar. Cancel with notification unless you have a specific reason not to.

send-rsvp

for the inverse case where the agent is the 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.

── more in #ai-agents 4 stories Β· sorted by recency
── more on @nylas 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/let-an-agent-own-the…] indexed:0 read:10min 2026-07-01 Β· β€”