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.
Before you begin
Section titled “Before you begin”You’ll need:
- A Nylas application with a valid API key
- A grant for a Microsoft 365 or Outlook account
- The
Mail.Readscope 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 admin consent
Section titled “Microsoft admin consent”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.
List messages
Section titled “List messages”Make a List Messages request with the grant ID. Nylas returns the 50 most recent messages by default. These examples limit results to 5:
curl --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?limit=5" \ --header 'Accept: application/json, application/gzip' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json'{ "request_id": "d0c951b9-61db-4daa-ab19-cd44afeeabac", "data": [ { "starred": false, "unread": true, "folders": [ "UNREAD", "CATEGORY_PERSONAL", "INBOX" ], "grant_id": "1", "date": 1706811644, "attachments": [ { "id": "1", "grant_id": "1", "filename": "invite.ics", "size": 2504, "content_type": "text/calendar; charset=\"UTF-8\"; method=REQUEST" }, { "id": "2", "grant_id": "1", "filename": "invite.ics", "size": 2504, "content_type": "application/ics; name=\"invite.ics\"", "is_inline": false, "content_disposition": "attachment; filename=\"invite.ics\"" } ], "from": [ { "name": "Nylas DevRel", } ], "id": "1", "object": "message", "snippet": "Send Email with Nylas APIs", "subject": "Learn how to Send Email with Nylas APIs", "thread_id": "1", "to": [ { "name": "Nyla", } ], "created_at": 1706811644, "body": "Learn how to send emails using the Nylas APIs!" } ], "next_cursor": "123"}app.get("/nylas/recent-emails", async (req, res) => { try { const identifier = process.env.USER_GRANT_ID; const messages = await nylas.messages.list({ identifier, queryParams: { limit: 5, }, });
res.json(messages); } catch (error) { console.error("Error fetching emails:", error); }});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")
messages = nylas.messages.list( grant_id, query_params={ "limit": 5 })
print(messages)require 'nylas'
nylas = Nylas::Client.new(api_key: '<NYLAS_API_KEY>')query_params = { limit: 5 }messages, _ = nylas.messages.list(identifier: '<NYLAS_GRANT_ID>', query_params: query_params)
messages.each {|message| puts "[#{Time.at(message[:date]).strftime("%d/%m/%Y at %H:%M:%S")}] \ #{message[:subject]}"}import com.nylas.NylasClient;import com.nylas.models.*;import java.text.SimpleDateFormat;
public class ListMessages { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build(); ListMessagesQueryParams queryParams = new ListMessagesQueryParams.Builder().limit(5).build(); ListResponse<Message> message = nylas.messages().list("<NYLAS_GRANT_ID>", queryParams);
for(Message email : message.getData()) { String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"). format(new java.util.Date((email.getDate() * 1000L)));
System.out.println("[" + date + "] | " + email.getSubject()); } }}import com.nylas.NylasClientimport com.nylas.models.*import java.text.SimpleDateFormatimport java.util.*
fun main(args: Array<String>) { val nylas = NylasClient(apiKey = "<NYLAS_API_KEY>") val queryParams = ListMessagesQueryParams(limit = 5) val messages = nylas.messages().list("<NYLAS_GRANT_ID>", queryParams).data
for (message in messages) { val date = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(Date(message.date.toLong() * 1000)) println("[$date] | ${message.subject}") }}Filter messages
Section titled “Filter messages”You can narrow results with query parameters. Here’s what works with Microsoft accounts:
| Parameter | What it does | Example |
|---|---|---|
subject | Match on subject line | ?subject=Weekly standup |
from | Filter by sender | [email protected] |
to | Filter by recipient | [email protected] |
unread | Unread messages only | ?unread=true |
in | Filter by folder or label ID | ?in=INBOX |
received_after | After a Unix timestamp | ?received_after=1706000000 |
received_before | Before a Unix timestamp | ?received_before=1706100000 |
has_attachment | Only messages with attachments | ?has_attachment=true |
Combining filters works the way you’d expect. This pulls unread messages from a specific sender:
curl --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/[email protected]&unread=true&limit=10" \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'const messages = await nylas.messages.list({ identifier: grantId, queryParams: { unread: true, limit: 10, },});messages = nylas.messages.list( grant_id, query_params={ "unread": True, "limit": 10, })Search with search_query_native
Section titled “Search with search_query_native”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.
curl --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?search_query_native=subject%3Ainvoice&limit=10" \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'const messages = await nylas.messages.list({ identifier: grantId, queryParams: { searchQueryNative: "subject:invoice", limit: 10, },});messages = nylas.messages.list( grant_id, query_params={ "search_query_native": "subject:invoice", "limit": 10, })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.
Things to know about Microsoft
Section titled “Things to know about Microsoft”A few provider-specific details that matter when you’re building against Microsoft accounts.
Folder names aren’t what you’d expect
Section titled “Folder names aren’t what you’d expect”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 name | Microsoft internal name |
|---|---|
| Inbox | inbox |
| Sent Items | sentitems |
| Drafts | drafts |
| Deleted Items | deleteditems |
| Junk Email | junkemail |
| Archive | archive |
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.
Message IDs are long and base64-encoded
Section titled “Message IDs are long and base64-encoded”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.
Sync is fast, but not instant
Section titled “Sync is fast, but not instant”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.
Rate limits are per-mailbox
Section titled “Rate limits are per-mailbox”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.
Attachments and inline images
Section titled “Attachments and inline images”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 replacecid: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 (
.icsfiles) show up as attachments on messages. Check thecontent_typefield fortext/calendarorapplication/ics.
Paginate through results
Section titled “Paginate through results”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:
curl --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?limit=10&page_token=<NEXT_CURSOR>" \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'let pageCursor = undefined;
do { const result = await nylas.messages.list({ identifier: grantId, queryParams: { limit: 10, pageToken: pageCursor, }, });
// Process result.data here
pageCursor = result.nextCursor;} while (pageCursor);page_cursor = None
while True: query = {"limit": 10} if page_cursor: query["page_token"] = page_cursor
result = nylas.messages.list(grant_id, query_params=query)
# Process result.data here
page_cursor = result.next_cursor if not page_cursor: breakKeep paginating until the response comes back without a next_cursor.
What’s next
Section titled “What’s next”- Messages API reference for full endpoint documentation and all available parameters
- Using the Messages API for search, modification, and deletion
- Threads to group related messages into conversations
- Attachments to download and upload file attachments
- Webhooks for real-time notifications instead of polling
- Microsoft admin approval to configure consent for enterprise organizations
- Microsoft publisher verification, required for production apps