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
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.
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 5The 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" \
--jsonHow 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" \
--jsonThen 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 blockPair 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
- Give Your AI Agent a Managed Calendar — the calendar capability behind this loop: availability, find-time, and event booking
- Getting Started with Agent Accounts — the architecture that puts email and calendar on one grant
- Build a Recruiting Interview Coordinator — the multi-person panel version of this verify-then-book loop
- Build a Human-in-the-Loop Email Agent — present time options and book only after the requester replies
- Agent Rules and Policies — every outbound trigger, condition, action, and send limit for the guardrail
- Build Reliable Email Automation — exit codes, retries, and idempotency for the cron loop
- Full command reference — every
nylas calendar,nylas email, andnylas agentflag - Nylas v3 Calendar API documentation — the API surface behind these commands