Guide
Sync Calendars Across Providers
Four calendar sync protocols exist, and none of them talk to each other. ICS exports are static snapshots. CalDAV is bidirectional but Google dropped it. Google's syncToken and Microsoft's deltaToken both do incremental sync, but each locks you into one provider. This guide compares all four methods, then shows how to read events across Gmail, Outlook, iCloud, and Exchange with one CLI command.
Written by Pouya Sanooei Software Engineer
Why sync calendars from the terminal?
Developers who manage work in Outlook and personal events in Google Calendar end up with two disconnected schedules. According to a 2023 Microsoft Work Trend Index, the average knowledge worker attends 25.6 meetings per week across an average of 2.3 calendar accounts. Keeping those accounts in sync manually means opening multiple browser tabs, comparing time slots by eye, and hoping nothing overlaps.
Terminal-based sync fits two workflows that browsers can't. First, CI/CD pipelines that need to check calendar state before scheduling deployments or on-call rotations. Second, scripts that aggregate events from multiple providers into a single JSON feed for dashboards or AI agents. Both need structured output, not a web UI.
What is ICS file export?
ICS is a static calendar snapshot defined in RFC 5545, published in 2009. An .ics file contains events, timezones, and recurrence rules in plain text. Every major calendar app can import and export ICS files, making it the lowest common denominator for calendar interchange.
The problem is that ICS is a point-in-time dump. There's no push notification, no delta tracking, and no way to detect that a meeting moved from 2 PM to 3 PM without re-exporting the entire file. Google Calendar's ICS export URL updates roughly every 12 hours, so a script polling that URL misses changes for half a day. For one-time migrations between providers, ICS works. For ongoing sync, it doesn't.
How does CalDAV calendar sync work?
CalDAV is a WebDAV extension (RFC 4791) that adds bidirectional calendar operations over HTTP. Unlike ICS export, CalDAV supports creating, updating, and deleting events on the server. Apple Calendar, Fastmail, and Nextcloud still use CalDAV as their primary sync protocol, handling over 300 million iCloud Calendar accounts as of 2024.
Google deprecated CalDAV support for third-party apps in 2019, directing developers to the Google Calendar API instead. Microsoft never supported CalDAV in Exchange or Outlook.com. That leaves CalDAV useful for Apple and self-hosted setups, but it can't be the universal sync layer across providers.
How does Google Calendar API sync events?
Google's Calendar API v3 uses a syncToken mechanism for incremental sync. The first request fetches all events and returns a nextSyncToken. Subsequent requests pass that token and receive only events that changed since the last call. According to Google's documentation, this reduces bandwidth by up to 95% compared to full fetches on active calendars with 500+ events.
The catch is lifecycle management. When the sync token expires (typically after 7 days of inactivity), the API returns a 410 Gone response, and your code must fall back to a full sync. Push notifications via the watch mechanism can trigger syncs in near real-time, but they require a publicly reachable HTTPS endpoint to receive webhooks. That's a non-trivial infrastructure requirement for what started as a calendar read.
How does Microsoft Graph sync calendar?
Microsoft Graph uses delta queries to track calendar changes. The pattern mirrors Google's syncToken: an initial request returns all events plus a @odata.deltaLink. Follow-up requests with that link return only additions, updates, and deletions since the last call. Microsoft's documentation notes that delta tokens remain valid for up to 30 days, longer than Google's 7-day window.
Graph delta queries cover Outlook.com, Microsoft 365, and Exchange Online. On-premises Exchange servers require EWS (Exchange Web Services) instead, which uses a completely different subscription model. A sync solution targeting "all Microsoft calendars" needs to handle both Graph and EWS, plus the authentication differences between personal Microsoft accounts and Azure AD tenants.
How do the four sync methods compare?
Each protocol covers a different slice of the calendar market. No single one works across all providers. This table compares them on 8 dimensions that matter for developers building sync workflows.
| Dimension | ICS export | CalDAV | Google API | Graph delta | Nylas CLI |
|---|---|---|---|---|---|
| Direction | Read only | Read/write | Read/write | Read/write | Read/write |
| Incremental sync | No (full re-export) | ctag/etag polling | syncToken | deltaToken | Per-provider via API |
| Providers | All (universal) | Apple, Fastmail, self-hosted | Google only | Microsoft only | Google, Outlook, Exchange, iCloud, Yahoo, IMAP |
| Auth setup | URL only | Username/password or OAuth | GCP project + OAuth | Azure AD app registration | nylas init |
| Token lifetime | N/A | Session-based | ~7 days | ~30 days | Auto-refresh |
| Webhook support | None | None (poll only) | Watch API (needs HTTPS endpoint) | Change notifications | Nylas webhooks (API tier) |
| Setup time | Minutes | 30+ min | 15-30 min | 20-40 min | 30 seconds |
| Lines of code | 10-20 (parser) | 50-100 | 40-80 | 60-120 | 0 (CLI commands) |
How do you list events across providers with one command?
The Nylas CLI normalizes calendar data from all connected providers into a single JSON schema. One nylas calendar events list call returns events from Google, Outlook, iCloud, or Exchange in the same format, regardless of whether the provider uses syncToken, deltaToken, or CalDAV underneath.
# List all calendars across connected accounts
nylas calendar list
# List next 14 days of events with timezone info
nylas calendar events list --days 14 --show-tz
# Output as JSON for scripting
nylas calendar events list --days 7 --jsonThe --json flag outputs each event with 15-20 fields including title, when, participants, conferencing, and provider-specific metadata. This structured output works directly with jq for filtering and reporting.
How do you script cross-provider calendar sync?
A common sync pattern reads events from one provider and creates matching events on another. The CLI's JSON output provides the event data, and jq reshapes it into the format needed for nylas calendar events create. This approach avoids writing provider-specific API code for each side of the sync.
The script below reads events from one connected account and creates them on another. In practice, you'd add deduplication logic (checking by title + start time) and handle recurring events separately. A production sync for 50 events completes in under 30 seconds.
#!/bin/bash
set -euo pipefail
# Export events from source account as JSON
events=$(nylas calendar events list --days 7 --json)
# Count events to sync
count=$(echo "$events" | jq length)
echo "Found $count events to sync"
# Create each event on the target calendar
echo "$events" | jq -c '.[]' | while read -r event; do
title=$(echo "$event" | jq -r '.title')
start=$(echo "$event" | jq -r '.when.start_time // .when.start_date')
end=$(echo "$event" | jq -r '.when.end_time // .when.end_date')
echo "Syncing: $title ($start)"
nylas calendar events create \
--title "$title" \
--start "$start" \
--end "$end"
done
echo "Synced $count events"For bidirectional sync, run the script in both directions and use a local state file (event ID mapping) to avoid creating duplicates. The --calendar flag lets you target a specific calendar on the destination account if you don't want events landing on the primary calendar.
How do you check availability across providers?
Cross-provider availability checks are where single-provider APIs fall apart. Google's FreeBusy API can only query Google calendars. Graph's getSchedule endpoint can only query Microsoft calendars. If your team uses both, you need two API calls with two auth flows and custom merge logic.
The CLI checks availability across all connected accounts in a single call. This is the same command whether the participants use Gmail, Outlook, or iCloud.
# Check free/busy across all connected accounts
nylas calendar availability check
# Find mutual free time for participants on different providers
nylas calendar find-time \
--participants alice@gmail.com,bob@outlook.com \
--duration 30 \
--days 7How do you filter synced events for dashboards?
Once events are in JSON format, jq handles the filtering that would otherwise require provider-specific query parameters. These patterns work identically regardless of which provider the events came from, because the CLI normalizes the output schema.
# Events with participants (skip focus time / holds)
nylas calendar events list --days 7 --json | \
jq '[.[] | select(.participants | length > 0)]'
# Events on a specific calendar
nylas calendar events list --calendar cal_abc123 --days 14 --json
# Count events per day this week
nylas calendar events list --days 7 --json | \
jq 'group_by(.when.start_date) | map({date: .[0].when.start_date, count: length})'
# Export titles and times as CSV
nylas calendar events list --days 7 --json | \
jq -r '.[] | [.title, .when.start_time, .when.end_time] | @csv'Next steps
Run nylas calendar events list --days 7 --json to see events from all connected providers in one output.
- Manage calendar events from terminal -- DST-aware scheduling, timezone locking, and AI-powered meeting finder
- Google Calendar CLI -- Google-specific features like Meet links, color labels, and focus time
- Check calendar availability from terminal -- free/busy checks and scheduling scripts
- Getting started with Nylas CLI -- install, authenticate, and run your first commands
- Full command reference -- every calendar flag and subcommand