Exchange on-premises servers remain widespread in enterprise environments, particularly in regulated industries, government, and organizations that have not yet migrated to Microsoft 365. Creating calendar events on these servers means talking to Exchange Web Services (EWS), a SOAP-based XML protocol that predates modern REST APIs.
Nylas abstracts the EWS complexity behind the same Events API you use for Google Calendar, Outlook, and iCloud. You send a JSON payload, and Nylas translates it into the correct EWS SOAP envelope, handles autodiscovery, and manages credentials. This guide covers the EWS-specific details you need to know when creating events on Exchange on-prem.
EWS vs. Microsoft Graph: which one?
Section titled “EWS vs. Microsoft Graph: which one?”This is the first thing to figure out. The two provider types target different Exchange deployments:
| Provider type | Connector | Use when |
|---|---|---|
| Microsoft Graph | microsoft | Exchange Online, Microsoft 365, Office 365, Outlook.com |
| Exchange Web Services | ews | Self-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.
Why use Nylas instead of EWS directly?
Section titled “Why use Nylas instead of EWS directly?”Creating a calendar event through EWS means constructing a SOAP XML envelope with deeply nested elements for the event title, body, start and end times, timezone definitions, attendees, recurrence patterns, and reminders. A single create-event call can easily exceed 50 lines of XML. You also need to handle autodiscovery to find the correct EWS endpoint (which is frequently misconfigured), manage credential-based authentication with support for two-factor app passwords, parse SOAP fault responses when something goes wrong, and build retry logic around Exchange’s admin-configured throttling policies.
Nylas replaces all of that with a single POST request containing a JSON body. No XML, no WSDL, no SOAP. Authentication, autodiscovery, and timezone conversion are handled automatically. Your event-creation code stays the same whether you target 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.
Before you begin
Section titled “Before you begin”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.calendarsscope (read and write access) - 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.
Autodiscovery and authentication
Section titled “Autodiscovery and authentication”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.
The full setup walkthrough is in the Exchange on-premises provider guide.
Create an event
Section titled “Create an event”Make a Create Event request with the grant ID and a calendar_id. You can use primary as the calendar_id to target the account’s default calendar.
You’re about to send a real event invite! The following samples send an email from the account you connected to the Nylas API to any email addresses you put in the participants sub-object. Make sure you actually want to send this invite to those addresses before running this command!
curl --request POST \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=<CALENDAR_ID>' \ --header 'Accept: application/json, application/gzip' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json' \ --data '{ "title": "Annual Philosophy Club Meeting", "busy": true, "conferencing": { "provider": "Zoom Meeting", "autocreate": { "conf_grant_id": "<NYLAS_GRANT_ID>", "conf_settings": { "settings": { "join_before_host": true, "waiting_room": false, "mute_upon_entry": false, "auto_recording": "none" } } } } "participants": [ { "name": "Leyah Miller", "email": "[email protected]" }, { "name": "Nyla", "email": "[email protected]" } ], "resources": [{ "name": "Conference room", "email": "[email protected]" }], "description": "Come ready to talk philosophy!", "when": { "start_time": 1674604800, "end_time": 1722382420, "start_timezone": "America/New_York", "end_timezone": "America/New_York" }, "location": "New York Public Library, Cave room", "recurrence": [ "RRULE:FREQ=WEEKLY;BYDAY=MO", "EXDATE:20211011T000000Z" ],}'{ "request_id": "1", "data": { "busy": true, "calendar_id": "primary", "conferencing": { "details": { "meeting_code": "<MEETING_CODE>", "url": "<MEETING_URL>" }, "provider": "Google Meet" }, "created_at": 1701974804, "creator": { "name": "Leyah Miller" }, "description": null, "grant_id": "<NYLAS_GRANT_ID>", "hide_participants": false, "html_link": "<EVENT_LINK>", "id": "<NYLAS_EVENT_ID>", "object": "event", "organizer": { "name": "Leyah Miller" }, "participants": [ { "status": "yes" }, { "status": "yes" } ], "read_only": true, "reminders": { "overrides": null, "use_default": true }, "status": "confirmed", "title": "Holiday check in", "updated_at": 1701974915, "when": { "end_time": 1701978300, "end_timezone": "America/Los_Angeles", "object": "timespan", "start_time": 1701977400, "start_timezone": "America/Los_Angeles" } }}import 'dotenv/config'import Nylas from 'nylas'
const NylasConfig = { apiKey: process.env.NYLAS_API_KEY, apiUri: process.env.NYLAS_API_URI,}
const nylas = new Nylas(NylasConfig)
const now = Math.floor(Date.now() / 1000) // Time in Unix timestamp format (in seconds)
async function createAnEvent() { try { const event = await nylas.events.create({ identifier: process.env.NYLAS_GRANT_ID, requestBody: { title: 'Build With Nylas', when: { startTime: now, endTime: now + 3600, } }, queryParams: { calendarId: process.env.CALENDAR_ID, }, })
console.log('Event:', event); } catch (error) { console.error('Error creating event:', error) }}
createAnEvent()from dotenv import load_dotenvload_dotenv()
import osimport sysfrom nylas import Client
nylas = Client( os.environ.get('NYLAS_API_KEY'), os.environ.get('NYLAS_API_URI'))
grant_id = os.environ.get("NYLAS_GRANT_ID")
events = nylas.events.create( grant_id, request_body={ "title": 'Build With Nylas', "when": { "start_time": 1609372800, "end_time": 1609376400 }, }, query_params={ "calendar_id": os.environ.get("CALENDAR_ID") })
print(events)require 'nylas'
nylas = Nylas::Client.new(api_key: "<NYLAS_API_KEY>")
query_params = { calendar_id: "<NYLAS_GRANT_ID>"}
today = Date.todaystart_time = Time.local(today.year, today.month, today.day, 13, 0, 0).strftime("%s")end_time = Time.local(today.year, today.month, today.day, 13, 30, 0).strftime("%s")
request_body = { when: { start_time: start_time.to_i, end_time: end_time.to_i }, title: "Let's learn some Nylas Ruby SDK!", location: "Nylas' Headquarters", description: "Using the Nylas API with the Ruby SDK is easy.", participants: [{ name: "Blag", status: 'noreply' }]}
events, _request_ids = nylas.events.create( identifier: "<NYLAS_GRANT_ID>", query_params: query_params, request_body: request_body)import com.nylas.NylasClient;import com.nylas.models.*;
import java.time.Instant;import java.time.LocalDate;import java.time.ZoneOffset;import java.time.temporal.ChronoUnit;import java.util.*;
public class create_calendar_events { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
// Get today's date LocalDate today = LocalDate.now();
// Set time. Because we're using UTC we need to add the hours in difference from our own timezone. Instant sixPmUtc = today.atTime(13, 0).toInstant(ZoneOffset.UTC);
// Set the date and time for the event. We add 30 minutes to the starting time. Instant sixPmUtcPlus = sixPmUtc.plus(30, ChronoUnit.MINUTES);
// Get the Date and Time as a Unix timestamp long startTime = sixPmUtc.getEpochSecond(); long endTime = sixPmUtcPlus.getEpochSecond();
// Define title, location, and description of the event String title = "Let's learn some about the Nylas Java SDK!"; String location = "Nylas Headquarters"; String description = "Using the Nylas API with the Java SDK is easy.";
// Create the timespan for the event CreateEventRequest.When.Timespan timespan = new CreateEventRequest. When.Timespan. Builder(Math.toIntExact(startTime), Math.toIntExact(endTime)). build();
// Create the list of participants. List<CreateEventRequest.Participant> participants_list = new ArrayList<>();
participants_list.add(new CreateEventRequest. "John Doe", "", ""));
// Build the event details. CreateEventRequest createEventRequest = new CreateEventRequest.Builder(timespan) .participants(participants_list) .title(title) .location(location) .description(description) .build();
// Build the event parameters. In this case, the Calendar ID. CreateEventQueryParams createEventQueryParams = new CreateEventQueryParams.Builder("<CALENDAR_ID>").build();
// Create the event itself Event event = nylas.events().create( "<NYLAS_GRANT_ID>", createEventRequest, createEventQueryParams).getData(); }}import com.nylas.NylasClientimport com.nylas.models.*
import java.time.LocalDateTimeimport java.time.ZoneOffset
fun main(args: Array<String>) { val nylas: NylasClient = NylasClient(apiKey = "<NYLAS_API_KEY>") var startDate = LocalDateTime.now()
// Set the time. Because we're using UTC, we need to add the difference in hours from our own timezone. startDate = startDate.withHour(13); startDate = startDate.withMinute(0); startDate = startDate.withSecond(0); val endDate = startDate.withMinute(30);
// Convert the dates from Unix timestamp format to integer. val iStartDate: Int = startDate.toEpochSecond(ZoneOffset.UTC).toInt() val iEndDate: Int = endDate.toEpochSecond(ZoneOffset.UTC).toInt()
// Create the timespan for the event. val eventWhenObj: CreateEventRequest.When = CreateEventRequest.When. Timespan(iStartDate, iEndDate);
// Define the title, location, and description of the event. val title: String = "Let's learn about the Nylas Kotlin/Java SDK!" val location: String = "Blag's Den!" val description: String = "Using the Nylas API with the Kotlin/Java SDK is easy."
// Create the list of participants. val participants: List<CreateEventRequest.Participant> = listOf(CreateEventRequest. Participant("<PARTICIPANT_EMAIL>", ParticipantStatus.NOREPLY, "<PARTICIPANT_NAME>"))
// Create the event request. This adds date/time, title, location, description, and participants. val eventRequest: CreateEventRequest = CreateEventRequest(eventWhenObj, title, location, description, participants)
// Set the event parameters. val eventQueryParams: CreateEventQueryParams = CreateEventQueryParams("<CALENDAR_ID>")
val event: Response<Event> = nylas.events().create(dotenv["NYLAS_GRANT_ID"], eventRequest, eventQueryParams)}The calendar_id=primary shortcut works for EWS accounts, targeting the user’s default calendar. The response format is identical across providers, so your event-creation logic works the same for Exchange on-prem, Exchange Online, Google, and iCloud.
Add participants and send invitations
Section titled “Add participants and send invitations”When you include a participants array in your create request, Exchange handles the meeting invitations through its internal mail transport. The notify_participants query parameter controls whether invitations are sent:
notify_participants=true(the default) sends a meeting invitation to every address in theparticipantsarray. Exchange delivers these through its own transport, not through a separate email send.notify_participants=falsecreates the event on the organizer’s calendar without notifying anyone. Participants do not receive a message or an ICS file, and the event does not appear on their calendars.
This behavior is consistent with how Exchange handles meeting requests natively. One thing to watch for: if the participant is on the same Exchange server, the event may appear on their calendar almost instantly. External participants receive a standard ICS invitation email.
Things to know about Exchange
Section titled “Things to know about Exchange”Exchange on-prem behaves differently from Exchange Online (Microsoft Graph) in several ways that matter when creating calendar events.
EWS connector scope
Section titled “EWS connector scope”The ews.calendars scope on your EWS connector grants both read and write access to the Calendar API. Without this scope, create requests fail with a permissions error. You can configure scopes when setting up the connector:
| Scope | Access |
|---|---|
ews.messages | Email API (messages, drafts, folders) |
ews.calendars | Calendar API |
ews.contacts | Contacts API |
No conferencing auto-create
Section titled “No conferencing auto-create”EWS does not support automatically creating conferencing links (Teams, Zoom, or otherwise) when you create an event. If you need a video conferencing link on the event, generate it through the conferencing provider’s API first, then include the URL in the event’s location or description field. This is a platform limitation of Exchange on-prem, not a Nylas restriction.
Recurring event restrictions
Section titled “Recurring event restrictions”Microsoft Exchange has specific constraints around recurring events that do not 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.
Timezone handling
Section titled “Timezone handling”Nylas accepts IANA timezone identifiers (like America/New_York or Europe/London) in your create request. You do not need to convert to Windows timezone IDs like “Eastern Standard Time.” Nylas handles the translation to Exchange’s internal format automatically.
Room resources
Section titled “Room resources”Exchange supports booking room resources through the resources field on an event. If the room is configured as an Exchange resource mailbox, you can include it as a resource when creating the event. The resource mailbox’s auto-accept policy determines whether the room is automatically confirmed or requires approval.
On-prem networking
Section titled “On-prem networking”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 event creation is failing for an Exchange account, verify that the EWS endpoint is accessible before investigating other causes.
Rate limits
Section titled “Rate limits”Unlike Google and Microsoft’s cloud services, Exchange on-prem rate limits are set by the server administrator. Write operations like event creation may be more restricted than read operations. Nylas cannot predict what the limits 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 create events frequently, consider batching operations and building backoff logic around the Retry-After response.
Sync timing
Section titled “Sync timing”Created events depend 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 delays before the event appears in sync results.
For apps that need confirmation that an event was created successfully, use webhooks to receive a notification as soon as the event syncs. This is more reliable than polling.
What’s next
Section titled “What’s next”- Events API reference for full endpoint documentation and all available parameters
- Using the Events API for updating, deleting, and managing events
- List Exchange events for reading events from Exchange on-prem accounts
- Recurring events for series creation, overrides, and provider-specific behavior
- Availability to check free/busy across multiple calendars
- Webhooks for real-time notifications when events are created or updated
- Exchange on-premises provider guide for full Exchange setup including authentication and network requirements
- Microsoft create events guide for cloud-hosted Exchange (Microsoft 365, Exchange Online)