{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreig5lciik533tcgdjn6eaazrkytqx3r6auxgrdyntxdviqz7cqzfiq",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpfexi2mib22"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreia2voazuuvkebn4ccaou7hmgafsevwcn3ijc7tfcd4yac6gvo2xua"
    },
    "mimeType": "image/webp",
    "size": 49464
  },
  "path": "/mqasimca/let-your-agent-rsvp-to-invites-it-receives-3dia",
  "publishedAt": "2026-06-29T01:17:57.000Z",
  "site": "https://dev.to",
  "tags": [
    "ai",
    "email",
    "api",
    "productivity",
    "Agent Accounts overview",
    "calendar mechanics page",
    "free/busy",
    "Agent Account calendars",
    "Supported endpoints for Agent Accounts",
    "Agent Accounts quickstart",
    "Nylas CLI command reference"
  ],
  "textContent": "An agent that gets invited to meetings should respond like a real attendee. Not with a polite \"I'm an AI assistant, please contact my human\" auto-reply. Not by silently dropping the invite on the floor. It should look at the meeting, look at its own calendar, and click Yes, No, or Maybe — the same three buttons every other person on the invite gets.\n\nMost \"AI calendar\" demos run the other direction. They point a model at a human's Google Calendar and let it _organize_ : propose times, send invites, chase RSVPs. That's the organizer flow, and it's useful. But the moment your agent has its own email address and people start inviting _it_ to things, you need the mirror image. The agent is now an **invitee** , and invitees don't organize — they respond.\n\nThis post is about that response. An invite lands in the agent's mailbox, Nylas turns it into an event on the agent's calendar, your code decides yes/no/maybe based on whether the agent is actually free, and you fire a single `send-rsvp` call that updates the organizer's calendar the way any human's RSVP would. I work on the Nylas CLI, so the terminal commands below are the exact ones I reach for, and I'll pair every one with the raw HTTP call so you can wire it into a backend with no SDK.\n\n##  The grant is the whole data plane\n\nBefore the calendar mechanics, the one abstraction worth internalizing: an **Agent Account is just a grant**. It has a `grant_id`, and that `grant_id` addresses the same `/v3/grants/{grant_id}/*` endpoints every other Nylas grant does — Messages, Threads, Events, Calendars, free/busy, all of it. There's no special \"agent API.\" If you've ever listed events or sent an RSVP for a connected Google or Microsoft account, you already know the surface area here. Nothing new to learn on the data plane.\n\nWhat makes an Agent Account different is the _identity_ : it owns a real mailbox and a real calendar on Nylas-hosted infrastructure, so it can be addressed, invited, and RSVP'd-against like a person. The endpoints are boring on purpose. The interesting part is that a piece of software is now a first-class participant on the invite.\n\nIf you're new to Agent Accounts, the Agent Accounts overview and the calendar mechanics page are the two pages to read alongside this one.\n\n##  How an inbound invite becomes an RSVP-able event\n\nHere's the lifecycle, because it's the part people get wrong. The agent doesn't parse ICS attachments by hand, and it doesn't reply to the invite email to accept. Nylas does the ICS plumbing for you:\n\n  1. Someone in Google Calendar, Outlook, Apple Calendar — or another Agent Account — creates a meeting and adds the agent's address (say `assistant@yourcompany.com`) as a participant.\n  2. Their calendar sends the iCalendar invitation to the agent's mailbox. Nylas sees it, parses the meeting details, and **creates a matching event on the agent's primary calendar**. An `event.created` webhook fires.\n  3. On that event, the agent is listed in `participants[]` with `status: \"noreply\"`. The organizer is whoever sent the invite — _not_ the agent. The RSVP is pending.\n\n\n\nThat third point is the conceptual pivot. _The agent is a participant, not the organizer._ It can't update or cancel the meeting; it can only respond. And because the event object already carries the organizer, the participants, the time window, and the description, you can drive your whole decision off `event.created` without ever opening the invite email.\n\nThe agent _also_ gets a `message.created` webhook for the invitation email itself, because an Agent Account is always a real mailbox too. You get to pick which signal drives your logic. My advice: drive the RSVP off `event.created` (it's already structured data) and treat the `message.created` as optional context you fetch only if you want the human-readable note the organizer wrote.\n\nOne honest caveat on the webhook body, because the Nylas docs hedge on it: don't rely on the webhook payload to hand you the full message body. Fetch the full message with `GET /v3/grants/{grant_id}/messages/{message_id}` when you need it, and branch on the `message.created.truncated` trigger name (it appears when a body exceeds ~1 MB and the body is omitted). For RSVP decisions you usually don't need the email at all — the event has everything.\n\n##  Step 1: see the invite\n\nYou can react to `event.created` from a webhook, or you can poll. Webhooks are application-scoped — you subscribe once at the app level with `POST /v3/webhooks`, every grant's events land at your endpoint, and each payload carries a `grant_id` you filter on. That's the right call for near-real-time RSVPs. But for a tour, polling the events list is the clearest way to _see_ the invite that just arrived.\n\nList the agent's upcoming events with curl:\n\n\n\n    curl --request GET \\\n      --url \"https://api.us.nylas.com/v3/grants/<GRANT_ID>/events?calendar_id=primary\" \\\n      --header \"Authorization: Bearer <NYLAS_API_KEY>\" \\\n      --header \"Accept: application/json\"\n\n\nAnd the CLI equivalent — same grant, same primary calendar:\n\n\n\n    nylas calendar events list --calendar primary\n\n\nThe invite shows up as an event where the agent's own address sits in `participants[]` with `status: \"noreply\"` and the `organizer` is someone else. That's your signal: this is an invitation the agent hasn't answered yet. Grab the `id` off that event — you'll need it to RSVP. Pull a single one for detail:\n\n\n\n    nylas calendar events show <event-id>\n\n\nIf you want the organizer's actual words — the \"hey, can you join our planning sync?\" note — fetch the invite email directly. The `event.created` and `message.created` webhooks both hand you a message id, so read that one message by id instead of listing the inbox:\n\n\n\n    # curl: fetch the invite message by id, body included\n    curl --request GET \\\n      --url \"https://api.us.nylas.com/v3/grants/<GRANT_ID>/messages/<MESSAGE_ID>\" \\\n      --header \"Authorization: Bearer <NYLAS_API_KEY>\" \\\n      --header \"Accept: application/json\"\n\n\n\n    # CLI: read the invite message by id\n    nylas email read <message-id>\n\n\nIf the invite came in on an existing thread, `email threads show <thread-id>` gives you the whole conversation so the agent has context before it commits to a time. None of this is required to RSVP — it's the layer you add when \"should I accept?\" depends on what the organizer wrote, not just on whether the slot is free.\n\n##  Step 2: check whether the agent is actually free\n\nThis is the part that makes the RSVP _mean_ something. Before the agent says yes, ask its calendar whether it's busy during the meeting window. Nylas exposes free/busy at the grant level, and the agent can query its own address.\n\nWith curl, you pass the meeting's start and end as epoch seconds and the agent's own email:\n\n\n\n    curl --request POST \\\n      --url \"https://api.us.nylas.com/v3/grants/<GRANT_ID>/calendars/free-busy\" \\\n      --header \"Authorization: Bearer <NYLAS_API_KEY>\" \\\n      --header \"Content-Type: application/json\" \\\n      --data '{\n        \"start_time\": 1744387200,\n        \"end_time\": 1744390800,\n        \"emails\": [\"assistant@yourcompany.com\"]\n      }'\n\n\nThe response comes back with the busy blocks in that window. If the meeting's window overlaps a busy block, the agent has a conflict. If it's clear, the agent is free.\n\nThe CLI wraps the same endpoint with human-friendly time parsing, which is genuinely nice when you're testing by hand:\n\n\n\n    nylas calendar availability check \\\n      --emails assistant@yourcompany.com \\\n      --start \"tomorrow 2pm\" \\\n      --end \"tomorrow 3pm\"\n\n\nYou can also pass `--duration 1h` instead of an explicit `--end`. Either way you get the agent's busy slots for the window, and that's the raw signal your decision logic consumes.\n\n##  Step 3: the yes/no/maybe decision is your code\n\nHere's the boundary worth being explicit about, because it's where people expect Nylas to do more than it does. **Nylas tells you when the agent is busy. It does not decide whether to accept.** That judgment — accept if free, decline if double-booked, maybe if it's a low-priority meeting during focus time — is your application logic. A reasonable first cut:\n\n  * Free for the whole window → `yes`\n  * Hard conflict with an existing meeting → `no`\n  * Free, but the organizer is external / the meeting is optional / it overlaps a soft block → `maybe`\n\n\n\nTwo constraints to design around. First, **store the decision and its reasoning in your own database.** Agent Accounts don't support custom `metadata` on events yet, so you can't stamp \"declined because double-booked at 14:00\" onto the Nylas event and read it back later. Keep that state — the event id, the decision, the rule that produced it — in your own store. Second, this is genuinely _your_ logic to own. The model (or your heuristics) decides; Nylas just executes the RSVP. If you want the agent's \"reasoning\" to be auditable, that audit trail lives in your DB, not in Nylas.\n\nIf your real need is round-trip negotiation — the agent proposing alternative slots, collecting picks, booking the winner — note that **Scheduler isn't available for Agent Accounts** today. Counter-proposing a time isn't a first-class endpoint either. The honest pattern for \"that time doesn't work\" is to RSVP `no` or `maybe` and reply to the invite email with a suggested alternative, then let the organizer create a new event.\n\n##  Step 4: send the RSVP\n\nOnce your code has picked a status, one call does it. Use the **send-rsvp** endpoint — `POST /v3/grants/{grant_id}/events/{event_id}/send-rsvp` — with `status` set to `yes`, `no`, or `maybe`, and the `calendar_id` the event lives on:\n\n\n\n    curl --request POST \\\n      --url \"https://api.us.nylas.com/v3/grants/<GRANT_ID>/events/<EVENT_ID>/send-rsvp?calendar_id=primary\" \\\n      --header \"Authorization: Bearer <NYLAS_API_KEY>\" \\\n      --header \"Content-Type: application/json\" \\\n      --data '{ \"status\": \"yes\" }'\n\n\nThe CLI command is `nylas calendar events rsvp`, which takes the event id and the status as positional arguments:\n\n\n\n    # Accept\n    nylas calendar events rsvp <event-id> yes\n\n    # Decline, with a note for the organizer\n    nylas calendar events rsvp <event-id> no --comment \"I have a conflict at that time\"\n\n    # Tentatively accept\n    nylas calendar events rsvp <event-id> maybe\n\n\n`--comment` attaches a short note to the reply, and `--calendar` (defaults to `primary`) targets a non-primary calendar if the invite landed on one.\n\nBehind the scenes, `send-rsvp` sends an ICS `REPLY` to every participant. From the organizer's side, the agent now shows as \"accepted\" (or \"declined\", or \"tentative\") next to every other attendee — exactly like a human clicking the button in their mail client. Other attendees see it too, because their calendars get the update automatically. This is why you _must_ use `send-rsvp` and not a plain reply email: a reply email won't move the organizer's calendar status. The RSVP endpoint is the only thing that does.\n\nAfter the call, an `event.updated` webhook fires on the agent's _own_ calendar, so the agent observes its own state change land. If you're keeping a mirror of RSVP state in your DB, that's a clean confirmation signal to reconcile against.\n\n##  Guardrails worth knowing\n\nA few things I'd bake in before pointing this at production traffic:\n\n  * **Dedupe your webhooks.** Nylas guarantees at-least-once delivery (up to three attempts per event). Dedupe on the top-level notification `id`, which stays constant across retries of the same event. You can additionally guard on the inner event id so the agent never RSVPs twice to one invite.\n  * **The agent is a participant, not the organizer.** It can RSVP, but it can't move or cancel someone else's meeting. If your flow needs to reschedule, that's a conversation with the organizer, not a `PUT` on the event.\n  * **`noreply` is your \"needs action\" filter.** When you poll or react, the events to act on are the ones where the agent's participant `status` is still `noreply`. Once you've RSVP'd, it flips, which keeps you from reprocessing.\n  * **Free/busy is point-in-time.** Check it right before you decide. If your agent batches RSVPs on a cron, re-check availability per event rather than trusting a snapshot from the top of the run — a yes earlier in the batch may have created a conflict for a later one.\n  * **Mind the send quota.** Each RSVP is an outbound ICS reply and counts toward the Agent Account's daily send quota. On the free plan that's 200 messages per account per day. A chatty agent that RSVPs to everything can burn through it faster than you'd expect.\n\n\n\n##  What's next\n\nThe mechanics are small once the model clicks: an invite becomes an event, free/busy becomes a decision, `send-rsvp` becomes a reply on the organizer's calendar. Everything rides the same grant you'd use for a connected account, so there's no second integration to build.\n\nFrom here:\n\n  * Agent Account calendars — the full organizer + invitee model, including hosting events the agent owns.\n  * Supported endpoints for Agent Accounts — every Calendars, Events, and webhook trigger that works with an Agent Account grant.\n  * Agent Accounts quickstart — provision an account and exercise the Events API end to end.\n  * Nylas CLI command reference — every `nylas calendar` and `nylas email` command used above.\n\n\n\nIf you've already built the organizer side — the agent that _sends_ invites and chases RSVPs — this is the missing half. Wire both and your agent isn't watching a calendar from the outside. It's on the invite.",
  "title": "Let your agent RSVP to invites it receives"
}