Guide

Build a Meeting-Booking Assistant Agent

A scheduling assistant that lives in an inbox needs to do four things: read the request, find a time everyone can make, put it on a calendar, and confirm it. An agent account gives one identity all four. This guide builds the loop on the CLI — read inbound mail as JSON, find a slot across participant timezones, book the event with attendees, and send a confirmation — then adds an outbound guardrail so the agent can only email the people it's supposed to.

Written by Aaron de Mello Senior Engineering Manager

VerifiedCLI 3.1.16 · Nylas managed · last tested June 7, 2026

What is a meeting-booking agent?

A meeting-booking agent is an autonomous program that turns a scheduling request in an inbox into a confirmed calendar event. It reads the incoming message, picks a time that fits every participant's working hours, writes the event with attendees, and emails a confirmation. Built on an agent account, all four steps run against a single managed identity — the agent owns the inbox and the calendar it books on.

The reason this is hard with raw provider APIs is that scheduling spans two products — mail and calendar — that usually live behind separate OAuth scopes and separate SDKs. An agent account collapses that into one grant. The same identity that runs nylas email list also runs nylas calendar find-time, so the loop is 4 CLI calls, not 2 API integrations.

Booking loop: read request email, find a time, book the event, send a confirmation emailRead requestemail list --jsonFind a timefind-timeBook eventevents createConfirmemail sendall four steps run on one agent account grant

Why give the booking agent its own account?

A booking agent should own a dedicated identity, not borrow a person's calendar. When the agent books on its own account, every event it creates is attributable to the agent, the meetings don't clutter a human's calendar, and you can shut the agent off by deleting one grant instead of revoking someone's OAuth session. That isolation is the whole point of an agent account.

It also keeps the blast radius small. If a scheduling request contains a prompt-injection payload trying to steer the agent into emailing strangers, the damage is bounded by the rules on the agent's workspace, not by the permissions of a real employee's mailbox. Containment lives outside the agent's decision loop — see Stop Your AI Agent From Going Rogue for the full pattern. The guardrail section below applies the booking-specific slice of it.

How do I set up the agent's account and calendar?

Provision the identity with one command. The nylas agent account create call returns a grant in under 2 seconds, with email and calendar both live. Capture the grant ID into an environment variable so the booking script can reference it without an interactive config:

nylas agent account create scheduler@yourapp.nylas.email
export NYLAS_GRANT_ID="$(nylas agent account get scheduler@yourapp.nylas.email --quiet)"

The agent account exposes one primary calendar, which is all a scheduler needs. Confirm it resolved before booking anything. The -e flag makes jq exit non-zero when no primary calendar matches, so the check fails loudly instead of letting a cron job book into the void:

nylas calendar list --json | jq -e '.[] | select(.is_primary) | .id'

How does the agent read a meeting request?

The agent watches its inbox for new requests with nylas email list. The --unread --json flags return only fresh messages as structured data, so the loop never re-processes a request it already handled. Project the fields the model actually needs — sender, subject, and the snippet that holds the request text:

nylas email list --unread --json \
  | jq '.[] | {id, from: .from[0].email, subject, snippet}'

From here a model extracts the intent: who wants to meet, for how long, and any constraints. The model's job is classification and extraction — turning prose into a participant list and a duration. The deterministic work of checking availability and writing the event belongs to the CLI, not the model, which keeps a hallucinated time from ever reaching a real calendar.

How does the agent propose times?

With the participants and duration extracted, the agent surfaces candidate slots using nylas calendar find-time. Pass each participant's IANA timezone in the same order as the emails so the search intersects everyone's working day. The defaults search 7 days out for a 1-hour meeting between 09:00 and 17:00 with weekends excluded — override any of them per request:

nylas calendar find-time \
  --participants requester@example.com,scheduler@yourapp.nylas.email \
  --timezones America/New_York,Europe/London \
  --duration 30m \
  --days 5

The output is a ranked picker, not a JSON payload: up to 5 suggested times, each scored out of 100 across working-hours fit, time quality, weekday, and even regional holidays. The agent presents the top options back to the requester, or picks the highest-scoring slot automatically. A careful agent offers the top 3 and books only after a reply — the human-in-the-loop variant in Build a Human-in-the-Loop Email Agent.

Because find-time is for display, the deterministic guard before booking is a separate call. Run nylas calendar availability check on the exact chosen window as close to booking time as possible — calendars change between suggestion and confirmation. The command returns a machine-readable outcome via its exit code: 0 when the slot is free, 2 when it's busy, and 1 on a command or auth error.

nylas calendar availability check "$NYLAS_GRANT_ID" \
  --emails requester@example.com \
  --start "2026-06-12T14:00:00Z" \
  --end "2026-06-12T14:30:00Z" \
  --json

How does the agent book and confirm?

Booking and confirming are two CLI calls. First, nylas calendar events create writes the event to the agent's calendar and adds each attendee, which sends a standard RFC 5545 calendar invite that Gmail, Outlook, and Apple Calendar all render natively:

nylas calendar events create "$NYLAS_GRANT_ID" \
  --title "Intro call" \
  --start "2026-06-12T14:00:00Z" \
  --end "2026-06-12T14:30:00Z" \
  --participant requester@example.com \
  --location "Google Meet" \
  --description "Booked automatically by the scheduling agent" \
  --json

Then the agent sends a plain confirmation with nylas email send. The sender address comes from the active grant, so there's no --from flag — the confirmation arrives from the same scheduler identity that holds the calendar:

nylas email send \
  --to requester@example.com \
  --subject "Confirmed: Intro call, Thu 14:00 UTC" \
  --body "You're booked for Thursday at 14:00 UTC. The calendar invite is in your inbox."

How do I run the full loop?

Tied together, the calls form a single script a cron job runs every few minutes. The version below handles one request end to end: it reads the oldest unread message, guards the proposed window with an availability check, books it, and confirms. A real deployment adds the model step that extracts the participant and the requested window from the message body; this skeleton shows the deterministic spine the model plugs into.

#!/usr/bin/env bash
set -euo pipefail

# 1. Read the oldest unread request
req=$(nylas email list --unread --json | jq -r '.[0] // empty')
[ -z "$req" ] && exit 0
requester=$(echo "$req" | jq -r '.from[0].email')

# 2. A model extracts the proposed window from the request body.
#    find-time gives ranked options to choose from; here we use the chosen slot.
start="2026-06-12T14:00:00Z"
end="2026-06-12T14:30:00Z"

# 3. Guard: book only if the window is still free (exit 0 = free, 2 = busy, 1 = error)
if nylas calendar availability check "$NYLAS_GRANT_ID" \
     --emails "$requester" --start "$start" --end "$end" --json; then
  : # free — fall through to booking
else
  status=$?
  [ "$status" -eq 1 ] && { echo "availability check error (auth/command)" >&2; exit 1; }
  echo "Slot no longer free — re-run find-time" >&2
  exit 2
fi

# 4. Book the event
nylas calendar events create "$NYLAS_GRANT_ID" \
  --title "Intro call" --start "$start" --end "$end" \
  --participant "$requester" --json

# 5. Confirm by email
nylas email send --to "$requester" \
  --subject "Confirmed: Intro call" \
  --body "You're booked. The calendar invite is on its way."

The script exits 0 when there's nothing to do and fails loud on any error because of set -euo pipefail. That matters for unattended automation: a booking agent that swallows errors will silently drop requests. For the broader reliability patterns — idempotency, retries, exit codes — see Build Reliable Email Automation.

How do I keep the agent from booking the wrong people?

The booking-specific risk is outbound: a malicious request could try to make the agent email or invite someone it shouldn't. Constrain that at the workspace, not in the prompt. An outbound rule on a flagged recipient domain is evaluated before the message reaches the send pipeline, so the agent can't prompt its way past it. This rule blocks any outbound mail to a known-bad domain:

nylas agent rule create \
  --name "Block flagged recipient domain" \
  --trigger outbound \
  --condition recipient.domain,is,spammer.example \
  --action block

Pair the rule with a policy that caps outbound volume so a runaway loop can't blast hundreds of invites. The policy and rule both attach to the agent's workspace, and you can swap them without touching the grant. The full set of triggers, conditions, and actions — plus send limits — is documented in Agent Rules and Policies. This containment sits outside the agent's decision loop, which is exactly why a prompt injection can't disable it.

Next steps