Skip to main content
Technology & EngineeringScheduling Services349 lines

Nylas

"Nylas: unified calendar/email/contacts API, scheduling, calendar CRUD, email send/read, OAuth providers, Node SDK"

Quick Summary18 lines
Nylas provides a single API abstraction over dozens of email and calendar providers -- Gmail, Outlook, Exchange, Yahoo, IMAP, and more. Instead of writing provider-specific integrations, you write against the Nylas API once and get cross-provider support for calendar events, email, and contacts. The platform handles OAuth token management, provider quirks, and real-time sync. Choose Nylas when your product needs to read or write to your users' existing calendars and inboxes without building separate integrations for each provider.

## Key Points

1. **Store the grant ID, not raw OAuth tokens** -- Nylas manages token refresh internally; your app only needs the stable grant ID to make API calls.
2. **Use calendar IDs explicitly** -- many accounts have multiple calendars; always let the user choose which calendar to read from and write to, defaulting to "primary".
3. **Enable webhook challenge verification** -- the GET endpoint must return the challenge parameter as plain text for Nylas to activate the subscription.
4. **Handle provider differences gracefully** -- not all features work identically across Gmail, Outlook, and Exchange; check the `provider` field and adapt UI affordances accordingly.
5. **Paginate with cursors** -- list endpoints return a `nextCursor`; always follow it to retrieve complete result sets.
6. **Use `expandRecurring: true` for event queries** -- without this flag, recurring events return as a single master record instead of individual occurrences.
1. **Storing raw provider tokens alongside Nylas tokens** -- this creates a maintenance burden and risks token conflicts; let Nylas handle all provider auth.
2. **Ignoring grant status changes** -- grants can become invalid when users revoke access; listen for `grant.expired` webhooks and prompt re-authentication.
3. **Fetching all events without time bounds** -- unbounded event queries return years of data; always pass `start` and `end` timestamps.
4. **Creating one webhook per grant** -- use a single webhook endpoint for all grants; Nylas includes the grant ID in each webhook payload.
5. **Hardcoding provider-specific scopes** -- scopes vary by provider; use Nylas-recommended scope sets and let the platform translate them.
6. **Treating email and calendar as always available** -- some grants may only have calendar or email permissions; check the grant's scope before calling endpoints that require specific access.
skilldb get scheduling-services-skills/NylasFull skill: 349 lines
Paste into your CLAUDE.md or agent config

Nylas Unified Communication API

Core Philosophy

Nylas provides a single API abstraction over dozens of email and calendar providers -- Gmail, Outlook, Exchange, Yahoo, IMAP, and more. Instead of writing provider-specific integrations, you write against the Nylas API once and get cross-provider support for calendar events, email, and contacts. The platform handles OAuth token management, provider quirks, and real-time sync. Choose Nylas when your product needs to read or write to your users' existing calendars and inboxes without building separate integrations for each provider.

Setup

SDK Installation and Configuration

// npm install nylas

import Nylas from "nylas";

const nylas = new Nylas({
  apiKey: process.env.NYLAS_API_KEY!,
  apiUri: process.env.NYLAS_API_URI || "https://api.us.nylas.com",
});

// Each connected account is identified by a grant ID
// obtained after the user completes the OAuth flow
const grantId = process.env.NYLAS_GRANT_ID!;

OAuth Flow for Connecting User Accounts

import express from "express";

const app = express();

// Step 1: Redirect user to Nylas hosted auth
app.get("/auth/connect", (req, res) => {
  const authUrl = nylas.auth.urlForOAuth2({
    clientId: process.env.NYLAS_CLIENT_ID!,
    redirectUri: `${process.env.APP_URL}/auth/callback`,
    loginHint: req.query.email as string | undefined,
    scope: ["https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/gmail.modify"],
  });
  res.redirect(authUrl);
});

// Step 2: Exchange the authorization code for a grant
app.get("/auth/callback", async (req, res) => {
  const { code } = req.query;

  const tokenResponse = await nylas.auth.exchangeCodeForToken({
    clientId: process.env.NYLAS_CLIENT_ID!,
    redirectUri: `${process.env.APP_URL}/auth/callback`,
    code: code as string,
  });

  // Store grantId and email for this user
  const grantId = tokenResponse.grantId;
  const email = tokenResponse.email;

  // Save to your database
  await saveUserGrant(req.session.userId, grantId, email);
  res.redirect("/dashboard");
});

Key Techniques

Calendar Event CRUD

interface NylasEventParams {
  title: string;
  startTime: number; // Unix timestamp
  endTime: number;
  description?: string;
  location?: string;
  participants?: Array<{ name?: string; email: string }>;
  conferencing?: { provider: "Google Meet" | "Zoom Meeting" | "Microsoft Teams" };
}

async function createCalendarEvent(
  grantId: string,
  calendarId: string,
  params: NylasEventParams
) {
  const event = await nylas.events.create({
    identifier: grantId,
    queryParams: { calendarId },
    requestBody: {
      title: params.title,
      when: {
        startTime: params.startTime,
        endTime: params.endTime,
      },
      description: params.description,
      location: params.location,
      participants: params.participants ?? [],
      conferencing: params.conferencing
        ? { provider: params.conferencing.provider, autocreate: {} }
        : undefined,
    },
  });
  return event.data;
}

async function listCalendarEvents(
  grantId: string,
  calendarId: string,
  startTime: number,
  endTime: number
) {
  const events = await nylas.events.list({
    identifier: grantId,
    queryParams: {
      calendarId,
      start: startTime.toString(),
      end: endTime.toString(),
      expandRecurring: true,
    },
  });
  return events.data;
}

async function updateCalendarEvent(
  grantId: string,
  calendarId: string,
  eventId: string,
  updates: Partial<NylasEventParams>
) {
  const event = await nylas.events.update({
    identifier: grantId,
    eventId,
    queryParams: { calendarId },
    requestBody: {
      title: updates.title,
      description: updates.description,
      location: updates.location,
      when: updates.startTime && updates.endTime
        ? { startTime: updates.startTime, endTime: updates.endTime }
        : undefined,
    },
  });
  return event.data;
}

async function deleteCalendarEvent(
  grantId: string,
  calendarId: string,
  eventId: string
) {
  await nylas.events.destroy({
    identifier: grantId,
    eventId,
    queryParams: { calendarId },
  });
}

Email Operations

// Send an email
async function sendEmail(
  grantId: string,
  to: Array<{ name?: string; email: string }>,
  subject: string,
  body: string,
  replyToMessageId?: string
) {
  const message = await nylas.messages.send({
    identifier: grantId,
    requestBody: {
      to,
      subject,
      body,
      replyToMessageId,
    },
  });
  return message.data;
}

// List recent emails
async function listRecentEmails(grantId: string, limit: number = 20) {
  const messages = await nylas.messages.list({
    identifier: grantId,
    queryParams: {
      limit,
      in: ["INBOX"],
    },
  });
  return messages.data;
}

// Read a single message with full body
async function readMessage(grantId: string, messageId: string) {
  const message = await nylas.messages.find({
    identifier: grantId,
    messageId,
  });
  return message.data;
}

// Search emails
async function searchEmails(grantId: string, query: string) {
  const messages = await nylas.messages.list({
    identifier: grantId,
    queryParams: {
      searchQueryNative: query,
      limit: 50,
    },
  });
  return messages.data;
}

Scheduling with Nylas

// Create a scheduling configuration for a user
async function createSchedulingConfig(grantId: string) {
  const config = await nylas.scheduling.configurations.create({
    identifier: grantId,
    requestBody: {
      participants: [
        {
          email: "host@example.com",
          availability: {
            calendarIds: ["primary"],
          },
          booking: {
            calendarId: "primary",
          },
        },
      ],
      availability: {
        durationMinutes: 30,
        availabilityRules: {
          availabilityMethod: "collective",
          buffer: { before: 10, after: 5 },
          roundTo: 15,
        },
      },
      eventBooking: {
        title: "Meeting with {{invitee}}",
        description: "Booked via scheduling API",
        conferencingProvider: "Google Meet",
      },
    },
  });
  return config.data;
}

// Get available time slots
async function getAvailableSlots(configurationId: string, startTime: number, endTime: number) {
  const availability = await nylas.scheduling.availability.get({
    queryParams: {
      configurationId,
      startTime: startTime.toString(),
      endTime: endTime.toString(),
    },
  });
  return availability.data.timeSlots;
}

// Book a time slot
async function bookSlot(configurationId: string, startTime: number, endTime: number, invitee: { name: string; email: string }) {
  const booking = await nylas.scheduling.bookings.create({
    requestBody: {
      configurationId,
      startTime,
      endTime,
      guest: {
        name: invitee.name,
        email: invitee.email,
      },
    },
  });
  return booking.data;
}

Webhook Handling

import crypto from "crypto";
import express from "express";

function verifyNylasWebhook(rawBody: string, signature: string, secret: string): boolean {
  const hmac = crypto.createHmac("sha256", secret);
  hmac.update(rawBody);
  const digest = hmac.digest("hex");
  return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature));
}

const app = express();

app.post("/webhooks/nylas", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["x-nylas-signature"] as string;
  if (!verifyNylasWebhook(req.body.toString(), signature, process.env.NYLAS_WEBHOOK_SECRET!)) {
    return res.status(401).send("Invalid signature");
  }

  const event = JSON.parse(req.body.toString());

  for (const delta of event.data.deltas) {
    switch (delta.type) {
      case "event.created":
        console.log(`Calendar event created: ${delta.object_data.id}`);
        break;
      case "event.updated":
        console.log(`Calendar event updated: ${delta.object_data.id}`);
        break;
      case "message.created":
        console.log(`New email from: ${delta.object_data.from?.[0]?.email}`);
        break;
    }
  }

  res.status(200).json({ success: true });
});

// Nylas sends a challenge parameter on subscription creation
app.get("/webhooks/nylas", (req, res) => {
  const challenge = req.query.challenge as string;
  res.status(200).send(challenge);
});

Best Practices

  1. Store the grant ID, not raw OAuth tokens -- Nylas manages token refresh internally; your app only needs the stable grant ID to make API calls.
  2. Use calendar IDs explicitly -- many accounts have multiple calendars; always let the user choose which calendar to read from and write to, defaulting to "primary".
  3. Enable webhook challenge verification -- the GET endpoint must return the challenge parameter as plain text for Nylas to activate the subscription.
  4. Handle provider differences gracefully -- not all features work identically across Gmail, Outlook, and Exchange; check the provider field and adapt UI affordances accordingly.
  5. Paginate with cursors -- list endpoints return a nextCursor; always follow it to retrieve complete result sets.
  6. Use expandRecurring: true for event queries -- without this flag, recurring events return as a single master record instead of individual occurrences.

Anti-Patterns

  1. Storing raw provider tokens alongside Nylas tokens -- this creates a maintenance burden and risks token conflicts; let Nylas handle all provider auth.
  2. Ignoring grant status changes -- grants can become invalid when users revoke access; listen for grant.expired webhooks and prompt re-authentication.
  3. Fetching all events without time bounds -- unbounded event queries return years of data; always pass start and end timestamps.
  4. Creating one webhook per grant -- use a single webhook endpoint for all grants; Nylas includes the grant ID in each webhook payload.
  5. Hardcoding provider-specific scopes -- scopes vary by provider; use Nylas-recommended scope sets and let the platform translate them.
  6. Treating email and calendar as always available -- some grants may only have calendar or email permissions; check the grant's scope before calling endpoints that require specific access.

Install this skill directly: skilldb add scheduling-services-skills

Get CLI access →