Skip to content

How to list Microsoft email messages

If you’re building an app that reads email from Microsoft 365 or Outlook accounts, you have two choices: work directly with the Microsoft Graph API, or use Nylas as a unified layer that handles the provider differences for you.

With Nylas, the API call to list messages is the same whether the account is Microsoft, Google, or IMAP. The differences show up in folder naming, message ID formats, admin consent requirements, and rate limiting. This guide covers all of that.

Why use Nylas instead of Microsoft Graph directly?

Section titled “Why use Nylas instead of Microsoft Graph directly?”

The Microsoft Graph API is capable, but integrating it requires significant setup. You need to register an app in Azure AD, configure OAuth scopes, manage token refresh with MSAL, handle admin consent flows for enterprise tenants, and deal with Microsoft-specific data formats like base64-encoded message IDs and internal folder names like sentitems.

Nylas handles all of that behind a single REST API. Your code stays the same whether you’re reading from Outlook, Gmail, or Yahoo. No Azure AD registration, no MSAL token lifecycle, no mapping deleteditems to “Trash” in your UI. If you need to support multiple providers or want to skip the Graph onboarding, Nylas is the faster path.

If you only need Microsoft and already have Graph experience, you can integrate directly.

You’ll need:

  • A Nylas application with a valid API key
  • A grant for a Microsoft 365 or Outlook account
  • The Mail.Read scope enabled in your Azure AD app registration

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

Microsoft organizations often require admin approval before third-party apps can access mailbox data. If your users see a “Need admin approval” screen during auth, it means their organization restricts user consent.

You have two options:

  • Ask the tenant admin to grant consent for your app via the Azure portal
  • Configure your Azure app to request only permissions that don’t need admin consent

Nylas has a detailed walkthrough: Configuring Microsoft admin approval. If you’re targeting enterprise customers, you’ll almost certainly need to deal with this.

You also need to be a verified publisher. Microsoft requires it since November 2020, and without it users see an error during auth.

Make a List Messages request with the grant ID. Nylas returns the 50 most recent messages by default. These examples limit results to 5:

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

ParameterWhat it doesExample
subjectMatch on subject line?subject=Weekly standup
fromFilter by sender[email protected]
toFilter by recipient[email protected]
unreadUnread messages only?unread=true
inFilter by folder or label ID?in=INBOX
received_afterAfter a Unix timestamp?received_after=1706000000
received_beforeBefore a Unix timestamp?received_before=1706100000
has_attachmentOnly messages with attachments?has_attachment=true

Combining filters works the way you’d expect. This pulls unread messages from a specific sender:

Microsoft supports the search_query_native parameter, which maps to the $search query parameter in Microsoft Graph. This uses Microsoft’s Keyword Query Language (KQL) syntax.

Like Google, Microsoft restricts which query parameters you can combine with search_query_native. You can only use it with in, limit, and page_token. Other query parameters will return an error.

KQL queries must be URL-encoded. For example, subject:invoice becomes subject%3Ainvoice in the URL. The SDKs handle this automatically, but you need to encode manually in curl requests.

See the search best practices guide for more on search_query_native across providers.

A few provider-specific details that matter when you’re building against Microsoft accounts.

Microsoft uses internal names like sentitems, deleteditems, and junkemail instead of the display names you see in the Outlook UI. Here’s the mapping:

Outlook UI nameMicrosoft internal name
Inboxinbox
Sent Itemssentitems
Draftsdrafts
Deleted Itemsdeleteditems
Junk Emailjunkemail
Archivearchive

If you’re building a folder picker or filtering messages by folder, call the List Folders endpoint first to get the actual folder IDs and display names for the account. Don’t hardcode folder names because some organizations customize them.

Microsoft message IDs look like AAMkAGI2TG93AAA=, which are long base64 strings compared to Google’s shorter numeric IDs. These IDs are stable and persist across syncs, so you can safely store and reference them. Just be aware that they’ll take more space in your database and URLs.

New messages typically appear within a few seconds of being sent or received. If a message you know exists isn’t showing up in list results yet, wait a moment and retry. This is a Microsoft-side delay, not a Nylas one.

For apps that need real-time notification of new messages, don’t poll. Use webhooks instead. Nylas will push a notification to your server as soon as the message syncs.

Microsoft throttles API requests at the mailbox level. If your app hits a 429 response, Nylas handles the retry automatically, so you don’t need to implement backoff logic yourself.

That said, if you’re building something that checks a mailbox frequently (like every few seconds), you’ll burn through rate limits fast. Webhooks solve this by notifying you of changes in real time without any polling requests.

Microsoft handles attachments differently from Google. A few things to watch for:

  • Inline images in HTML email bodies are returned as attachments with is_inline: true. If you’re rendering email content, you’ll need to replace cid: references in the HTML with the actual attachment URLs.
  • Attachment size limits vary. Microsoft allows up to 150 MB for messages with attachments, but individual files over 3 MB should use the upload attachment flow.
  • Calendar invites (.ics files) show up as attachments on messages. Check the content_type field for text/calendar or application/ics.

The Messages 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.