Skip to content

How to list Exchange calendar events

Exchange on-premises servers are still common in enterprise environments, especially in regulated industries and government organizations. If your users run self-hosted Exchange (2007 or later), Nylas connects to their calendars through Exchange Web Services (EWS) - a separate protocol from the Microsoft Graph API used for Exchange Online and Microsoft 365.

The same Events API you use for Google Calendar and Outlook works for Exchange on-prem accounts. This guide covers the EWS-specific details: when to use EWS vs. Microsoft Graph, authentication and autodiscovery, recurring event restrictions, and on-prem networking considerations.

This is the first thing to figure out. The two provider types target different Exchange deployments:

Provider typeConnectorUse when
Microsoft GraphmicrosoftExchange Online, Microsoft 365, Office 365, Outlook.com
Exchange Web ServicesewsSelf-hosted Exchange servers (on-premises)

If the user’s calendar is hosted by Microsoft in the cloud, use the Microsoft guide instead. The ews connector is specifically for organizations that run their own Exchange servers.

Microsoft announced EWS retirement and recommends migrating to Microsoft Graph. However, many organizations still run on-premises Exchange servers where EWS is the only option. Nylas continues to support EWS for these environments.

EWS is a SOAP-based XML API. Every request requires building XML SOAP envelopes, every response needs XML parsing, and errors come back as SOAP faults with nested XML structures. Calendar operations are particularly verbose in EWS - creating a recurring event with attendees, timezone rules, and reminders means constructing deeply nested XML payloads. You also need to handle autodiscovery to find the right server endpoint (which is frequently misconfigured), manage credential-based authentication with support for two-factor app passwords, and deal with Exchange’s recurrence model that doesn’t map cleanly to iCalendar standards.

Nylas replaces all of that with a JSON REST API. No XML, no WSDL, no SOAP. Authentication and autodiscovery are handled automatically. Your code stays the same whether you’re reading calendar events from Exchange on-prem, Exchange Online, Google Calendar, or iCloud.

If you have deep EWS experience and only target Exchange on-prem, direct integration is an option. For multi-provider calendar support or faster time-to-integration, Nylas is the simpler path.

You’ll need:

  • A Nylas application with a valid API key
  • A grant for an Exchange on-premises account
  • An EWS connector configured with the ews.calendars scope
  • The Exchange server accessible from outside the corporate network (not behind a VPN or firewall that blocks external access)

New to Nylas? Start with the quickstart guide to set up your app and connect a test account before continuing here.

EWS uses credential-based authentication. During the auth flow, users sign in with their Exchange credentials - typically the same username and password they use for Windows login. The username format is usually [email protected] or DOMAIN\username.

If EWS autodiscovery is configured on the server, Nylas automatically locates the correct EWS endpoint. If autodiscovery is disabled or misconfigured, users can click “Additional settings” during authentication and manually enter the Exchange server address (for example, mail.company.com).

Users with two-factor authentication must generate an app password instead of using their regular password. See Microsoft’s app password documentation for instructions.

Create an EWS connector with the scopes your app needs:

ScopeAccess
ews.messagesEmail API (messages, drafts, folders)
ews.calendarsCalendar API
ews.contactsContacts API

The full setup walkthrough is in the Exchange on-premises provider guide.

Make a List Events request with the grant ID and a calendar_id. Nylas returns the most recent events by default. You can use primary as the calendar_id to target the account’s default calendar. These examples limit results to 5:

The calendar_id=primary shortcut works for EWS accounts, targeting the user’s default calendar. The response format is identical across providers, so your parsing logic works the same for Exchange on-prem, Exchange Online, Google, and iCloud.

You can narrow results with query parameters. Here’s what works with Exchange accounts:

ParameterWhat it doesExample
calendar_idRequired. Filter by calendar?calendar_id=primary
titleMatch on event title (case insensitive)?title=standup
descriptionMatch on description (case insensitive)?description=quarterly
locationMatch on location (case insensitive)?location=Room%20A
startEvents starting at or after a Unix timestamp?start=1706000000
endEvents ending at or before a Unix timestamp?end=1706100000
attendeesFilter by attendee email (comma-delimited)[email protected]
busyFilter by busy status?busy=true
metadata_pairFilter by metadata key-value pair?metadata_pair=project_id:abc123

Exchange (EWS) supports several additional filter parameters:

  • tentative_as_busy - treat tentative events as busy when checking availability (defaults to true)
  • updated_after / updated_before - filter by last-modified timestamp, useful for incremental sync
  • ical_uid - find a specific event by its iCalendar UID
  • master_event_id - list all instances and overrides for a specific recurring series

A few parameters are not supported on EWS:

  • show_cancelled - Exchange does not support retrieving cancelled events
  • event_type - this filter is Google-only

Combining filters works the way you’d expect. This example pulls events in a specific time range:

Exchange on-prem behaves differently from Exchange Online (Microsoft Graph) in several ways that matter for calendar integrations.

Exchange treats tentative events as busy by default when calculating availability, the same behavior as Microsoft Graph. The tentative_as_busy query parameter controls this. If your app needs to distinguish between confirmed and tentative events, check the status field on each event object rather than relying on the default availability calculation.

The show_cancelled parameter is not supported on EWS. When an organizer cancels an event or removes an occurrence from a recurring series, Exchange deletes it entirely rather than marking it as cancelled. You cannot retrieve cancelled events from Exchange on-prem accounts through the Nylas API.

This is important if you’re building audit or compliance features that need to track event cancellations. Consider using webhooks to capture deletion events in real time before they become unrecoverable.

Microsoft Exchange has specific constraints around recurring events that don’t apply to Google Calendar:

  • No overlapping instances. You cannot reschedule an instance of a recurring event to fall on the same day as, or the day before, the previous instance. Exchange rejects the update to prevent overlapping occurrences within a series.
  • Overrides removed on recurrence change. If you modify the recurrence pattern of a series (for example, changing from weekly to daily), Exchange removes all existing overrides. Google keeps them if they still fit the new pattern.
  • EXDATE recovery is not possible. Once you remove an occurrence from a recurring series, there is no way to restore it. You would need to create a standalone event to fill the gap.
  • No multi-day monthly BYDAY. You cannot create a monthly recurring event on multiple days of the week (like the first and third Thursday). Exchange’s recurrence model does not support different indices within a single rule.

For the full breakdown of provider-specific recurring event behavior, see Recurring events.

The Exchange server’s EWS endpoint must be reachable from Nylas infrastructure. This is the most common source of connection failures for on-prem deployments.

  • EWS must be enabled on the server and exposed outside the corporate network
  • If the server is behind a firewall, you need to allow Nylas’s IP addresses (available on contract plans with static IPs)
  • A reverse proxy in front of the Exchange server is a common workaround if direct firewall rules are not feasible
  • Accounts in admin groups are not supported

If calendar data is not syncing for an Exchange account, verify that the EWS endpoint is accessible before investigating other causes.

Nylas uses Exchange autodiscovery to locate the EWS endpoint automatically during authentication. This works well when autodiscovery is properly configured on the Exchange server. When it is not, users must manually provide the server address.

If users report authentication failures, the Exchange administrator can test autodiscovery using Microsoft’s Remote Connectivity Analyzer. Misconfigured autodiscovery is one of the most common issues with Exchange on-prem integrations.

Exchange stores timezone information using Windows timezone identifiers like “Eastern Standard Time” or “Pacific Standard Time.” Nylas normalizes these to IANA identifiers (like “America/New_York” or “America/Los_Angeles”) automatically, so event times in API responses always use IANA format. You do not need to maintain a Windows-to-IANA mapping table in your application.

Calendar sync performance for Exchange on-prem depends on the EWS server’s responsiveness and network latency between Nylas infrastructure and the Exchange server. On-prem servers with high load or limited bandwidth may introduce noticeable sync delays compared to cloud-hosted Exchange.

For apps that need real-time awareness of calendar changes, use webhooks instead of polling. Nylas pushes a notification to your server as soon as the event syncs, regardless of the underlying server speed.

Unlike Google and Microsoft’s cloud services, Exchange on-prem rate limits are set by the server administrator. Nylas cannot predict what they will be. If the Exchange server throttles a request, Nylas returns a Retry-After header with the number of seconds to wait.

For apps that check calendars frequently, webhooks are the best way to avoid hitting rate limits. Let Nylas notify you of changes instead of polling.

The Events API returns paginated responses. When there are more results, the response includes a next_cursor value. Pass it back as page_token to get the next page:

Keep paginating until the response comes back without a next_cursor.