Source: https://cli.nylas.com/guides/microsoft-graph-mail-folders

# Microsoft Graph Mail Folders API

The Microsoft Graph mailFolder resource models the folder tree in an Outlook or Microsoft 365 mailbox. This guide covers listing top-level folders, addressing well-known folder names instead of IDs, walking nested child folders, reading item and unread counts, and filtering with OData query parameters. The Nylas CLI gives you the same folder list across Gmail, Outlook, and IMAP without an Azure AD app.

Written by [Prem Keshari](https://cli.nylas.com/authors/prem-keshari) Senior SRE

Updated June 19, 2026

> **TL;DR:** `GET /me/mailFolders` lists top-level folders, 10 per page by default. Each `mailFolder` carries `id`, `displayName`, `childFolderCount`, `totalItemCount`, and `unreadItemCount`. Address system folders by one of 17 well-known names (`inbox`, `sentitems`) rather than `displayName`, which is localized. To skip the Azure AD app, `nylas email folders list` returns folders across providers in one command.

> **Disclosure:** Nylas CLI is built by Nylas, Inc. This comparison reflects our testing and product understanding as of June 19, 2026.

Command references used in this guide: [`nylas email folders list`](https://cli.nylas.com/docs/commands/email-folders-list), [`nylas email list`](https://cli.nylas.com/docs/commands/email-list).

## What is the Microsoft Graph mailFolder resource?

A `mailFolder` is the Graph object that represents one folder in an Outlook or Microsoft 365 mailbox, such as Inbox, Sent Items, or a custom folder. Each folder exposes an `id`, a `displayName`, a `parentFolderId`, and three counters. Folders nest to arbitrary depth through child folders.

According to the Microsoft Learn [mailFolder resource reference](https://learn.microsoft.com/en-us/graph/api/resources/mailfolder), every folder reports `childFolderCount` (how many subfolders it holds), `totalItemCount` (messages in the folder), and `unreadItemCount` (unread messages). A boolean `isHidden` marks folders that clients normally suppress. These five fields cover most folder-management tasks without ever opening a message, so a dashboard can show per-folder unread badges from a single list call.

## How do you list top-level mail folders in Graph?

Issue a `GET` against `/me/mailFolders` to list the top-level folders in the signed-in user's mailbox. One request returns folders with five counter fields each, page size 10 by default. The Microsoft Learn [List mailFolders](https://learn.microsoft.com/en-us/graph/api/user-list-mailfolders) page documents the call. The 401 and 403 codes that block this call resolve in under 5 minutes once you fix the token or scope.

The request below lists folders for the current user with a delegated access token. Each entry includes the counters described above, so you can read unread totals straight from the response. Hidden folders are excluded unless you opt in with the `includeHiddenFolders` query parameter, covered in the nesting section below.

```bash
# List top-level mail folders for the signed-in user
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  | jq '.value[] | {displayName, totalItemCount, unreadItemCount, childFolderCount}'

# Example element from the .value array:
# {
#   "displayName": "Inbox",
#   "totalItemCount": 2847,
#   "unreadItemCount": 31,
#   "childFolderCount": 4
# }
```

The default page size is 10, so a mailbox with more than 10 top-level folders returns an `@odata.nextLink` URL. Call that link to fetch the next page. To pull more folders in one request, raise the page size with `$top`, which the filtering section explains.

## What are the Graph well-known folder names?

Graph defines 17 well-known folder names you can put directly in the path instead of a folder `id`. For example, `GET /me/mailFolders/sentitems` resolves the Sent Items folder in any mailbox without first looking up its ID. According to the mailFolder reference, these constants stay stable across every mailbox and language.

These names matter because a folder's `displayName` is localized to the mailbox language and is not guaranteed unique. The Microsoft Learn reference states a well-known name resolves regardless of the mailbox's language. All 17 names work across every mailbox in the tenant, and one well-known name replaces an ID lookup that would otherwise cost an extra request. Reserve `displayName` for user-created folders that have no constant.

| Well-known name | Folder |
| --- | --- |
| `inbox` | Inbox |
| `archive` | Archive |
| `drafts` | Drafts |
| `sentitems` | Sent Items |
| `deleteditems` | Deleted Items |
| `junkemail` | Junk Email |
| `outbox` | Outbox |
| `clutter` | Clutter |
| `conflicts` | Sync Conflicts |
| `conversationhistory` | Conversation History |
| `localfailures` | Local Failures |
| `recoverableitemsdeletions` | Recoverable Items (purges) |
| `scheduled` | Scheduled |
| `searchfolders` | Search Folders |
| `serverfailures` | Server Failures |
| `syncissues` | Sync Issues |
| `msgfolderroot` | Top of Information Store (mailbox root) |

## How do you read child and nested folders?

Mail folders nest to arbitrary depth, and each folder reports its direct subfolder count in `childFolderCount`. To list a folder's immediate children, call `GET /me/mailFolders/{id}/childFolders`, documented in the Microsoft Learn [List childFolders](https://learn.microsoft.com/en-us/graph/api/mailfolder-list-childfolders) reference. Because nesting has no fixed limit, walking a deep tree means recursing through child folders whose `childFolderCount` is greater than 0. A two-level tree costs 2 commands without a shortcut, so the parameters below collapse the walk into fewer calls.

Two shortcuts reduce round-trips. The `$expand=childFolders` parameter returns one level of children inline with the parent folders, and the `includeHiddenFolders=true` query parameter surfaces folders whose `isHidden` flag is set, which the default list call omits. The example expands children of the well-known `inbox` folder and also lists hidden top-level folders.

```bash
# List the direct child folders of the Inbox (addressed by well-known name)
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders/inbox/childFolders" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  | jq '.value[] | {displayName, childFolderCount}'

# Expand one level of children inline with the top-level folders
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders?\
\$expand=childFolders" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq '.value[].displayName'

# Include hidden folders (isHidden = true) that are normally suppressed
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders?includeHiddenFolders=true" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq '.value | length'
```

Each child folder is itself a full `mailFolder` with the same five fields, so the counters work at every level. A folder with `childFolderCount` of 0 is a leaf, which is the stop condition for a recursive walk.

## How do you filter and page mail folders?

Graph folder queries use the OData system query options documented in Microsoft Learn's [query parameters reference](https://learn.microsoft.com/en-us/graph/query-parameters): `$select` trims the returned fields, `$top` sets the page size, `$filter` restricts results, and `$orderby` sorts them. The default page size is 10; follow the `@odata.nextLink` URL Graph returns to page through additional folders. Trimming to 3 fields with `$select` can cut response size by 70%.

You can filter on `displayName` for user-created folders, but keep the localization caveat in mind: `displayName` values change with the mailbox language, so a hard-coded English name can miss a German or Japanese mailbox. The example pages 50 folders, selects three fields, and filters by display name. To list messages inside a folder, append `/messages` to the folder path.

```bash
# Page 50 folders, select 3 fields, sort by name
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders?\
\$top=50&\
\$select=displayName,unreadItemCount,totalItemCount&\
\$orderby=displayName" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq '.value[]'

# Filter folders whose displayName equals "Receipts"
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders?\
\$filter=displayName eq 'Receipts'" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq '.value[].id'

# List messages in the Inbox by well-known name
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages?\
\$top=5&\$select=subject,receivedDateTime" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq '.value[].subject'
```

Filtering by `displayName` works for ad-hoc lookups, but for stable scripts, resolve a folder once and store its `id`. The ID never changes, while the display name can be renamed or localized out from under you.

## How do you list folders without an Azure AD app?

Graph folder access needs an Azure AD app registration, OAuth scopes, and token refresh logic before the first call returns. For scripts, CI jobs, and quick checks, the `nylas email folders list` command returns the folder list directly. It works against Outlook, Gmail, and IMAP, normalizing Gmail labels and Graph folders into one shape, so the same command covers every connected account.

Setup takes about 2 minutes through `nylas init`, with no Azure portal, no client secret, and no scope selection. The first command lists folders as JSON for scripting; the second scopes a message listing to a single folder by name, which mirrors a Graph `/mailFolders/{id}/messages` call without the ID lookup.

```bash
# Install (macOS or Linux)
brew install nylas/nylas-cli/nylas

# List every folder in the connected mailbox as JSON
nylas email folders list --json

# Scope a message listing to one folder by name
nylas email list --folder "Inbox"
nylas email list --folder "Sent Items"
```

The folder list returns the same display names and IDs Graph exposes, so you can prototype against the CLI and move to direct Graph calls later without relearning the folder model. For other install methods, see the [getting started guide](https://cli.nylas.com/guides/getting-started).

## How do you create, rename, move, and delete mail folders?

Graph mail folders are managed with four write operations against the `mailFolder` resource. `POST /me/mailFolders` with a `displayName` creates a top-level folder, `PATCH` renames it, `POST /me/mailFolders/{id}/move` reparents it, and `DELETE` removes it. Each call needs the `Mail.ReadWrite` permission and finishes in one request.

To create a folder, send a `POST` to `/me/mailFolders` with a JSON body. The Microsoft Learn [Create mailFolder](https://learn.microsoft.com/en-us/graph/api/user-post-mailfolders) reference states that displayName and isHidden are the only writable property for a mailFolder object, so the body needs only those two fields. To nest a folder, `POST` to `/me/mailFolders/{id}/childFolders` instead, which is documented on the [Create childFolder](https://learn.microsoft.com/en-us/graph/api/mailfolder-post-childfolders) page. A successful create returns `201 Created` with the new folder's `id`.

```bash
# Create a top-level folder named "Receipts"
curl -s -X POST "https://graph.microsoft.com/v1.0/me/mailFolders" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"displayName": "Receipts"}' | jq '{id, displayName}'

# Create a nested folder under an existing parent
curl -s -X POST "https://graph.microsoft.com/v1.0/me/mailFolders/$PARENT_ID/childFolders" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"displayName": "2026"}' | jq '{id, parentFolderId}'
```

Renaming a folder is a `PATCH` against the folder's ID with a new `displayName`, documented in the [Update mailFolder](https://learn.microsoft.com/en-us/graph/api/mailfolder-update) reference. Moving a folder is a `POST` to `/me/mailFolders/{id}/move` with a `destinationId`, which reparents the folder and every subfolder under the new parent in 1 call (see the [move mailFolder](https://learn.microsoft.com/en-us/graph/api/mailfolder-move) page). Deleting is a `DELETE` on the folder ID and returns `204 No Content`.

```bash
# Rename a folder (PATCH the displayName)
curl -s -X PATCH "https://graph.microsoft.com/v1.0/me/mailFolders/$FOLDER_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"displayName": "Receipts 2026"}' | jq '.displayName'

# Move a folder under a new parent
curl -s -X POST "https://graph.microsoft.com/v1.0/me/mailFolders/$FOLDER_ID/move" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg id "$NEW_PARENT_ID" '{destinationId: $id}')"

# Delete a folder (and everything inside it)
curl -s -X DELETE "https://graph.microsoft.com/v1.0/me/mailFolders/$FOLDER_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN" -w "%{http_code}\n"
```

Deleting a folder isn't scoped to the folder shell. It removes the folder and every message inside it, and on Outlook those messages move to Deleted Items rather than vanishing. Confirm a folder is empty with the `totalItemCount` field before issuing the `DELETE`, because there is no undo prompt and the call returns `204` in well under 5 seconds whether the folder held 0 or thousands of emails.

## How are Graph mail folders different from Gmail labels?

Graph mail folders and Gmail labels solve the same problem with opposite models. Graph uses a folder tree where every message lives in exactly one folder and nesting happens through child folders. Gmail uses labels, where a single message can carry many labels at once and the "folders" you see in the UI are labels presented as a tree. The two models differ on cardinality, nesting, and how a message is reclassified.

The practical consequence is reclassification cost. In Graph, moving a message between folders is one `move` action that changes its `parentFolderId`. In Gmail, the same effect means adding one label and removing another, because a message has no single home. The Gmail API documents Inbox itself as the system label `INBOX`, so archiving a message is removing that one label rather than moving it. The [Gmail labels API guide](https://cli.nylas.com/guides/gmail-labels-api) covers the label side in full.

| Dimension | Graph mail folders | Gmail labels |
| --- | --- | --- |
| Messages per container | One folder per message | Many labels per message |
| Nesting | `childFolders` tree, arbitrary depth | `/` in label name (display only) |
| Reclassify a message | `move` changes `parentFolderId` | Add label, remove label |
| System containers | 17 well-known names | System labels (`INBOX`, `SENT`) |
| Archive a message | Move to `archive` folder | Remove the `INBOX` label |

For cross-provider tooling, the gap matters because code that assumes one container per message breaks on Gmail, and code that assumes many labels breaks on Outlook. The `nylas email folders list` command normalizes both into one shape across 3 providers (Outlook, Gmail, IMAP), so you write the folder logic once instead of branching on provider.

## How do you move a message into a folder?

Moving a message into a Graph folder is a `POST` to `/me/messages/{id}/move` with a `destinationId` that names either a folder ID or one of the 17 well-known names. The Microsoft Learn [message: move](https://learn.microsoft.com/en-us/graph/api/message-move) reference says the call creates a new copy of the message in the destination folder and removes the original message, so the moved message gets a new `id`.

That new-ID behavior trips up scripts that cache message IDs. Because Graph returns `201 Created` with a fresh message object, any code holding the old ID must read the new `id` from the response before acting again. The example moves a message to the well-known `deleteditems` folder and captures the new ID for follow-up calls.

```bash
# Move a message to Deleted Items, capture the new id
curl -s -X POST "https://graph.microsoft.com/v1.0/me/messages/$MESSAGE_ID/move" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"destinationId": "deleteditems"}' | jq '{newId: .id, parentFolderId}'
```

The Nylas CLI relocates a message with one command, `nylas email move`, instead of a raw Graph call. Pass `--folder` with a folder ID (find IDs with `nylas email folders list`) to move the message, or `--archive` to archive it. The same command works across Outlook, Gmail, and IMAP: on folder-based Outlook accounts it relocates the message, while on Gmail label accounts `--archive` removes every label including INBOX.

```bash
# Move a message into a folder by ID
nylas email move <message-id> --folder <folder-id>

# Or archive it (provider-aware: folder move on Outlook, label removal on Gmail)
nylas email move <message-id> --archive
```

The [move emails between folders guide](https://cli.nylas.com/guides/move-emails-between-folders) covers bulk workflows that pipe IDs from `nylas email list --folder FOLDER_ID --json` into a move loop across all three providers.

## Next steps

- [Microsoft Graph email quick start](https://cli.nylas.com/guides/microsoft-graph-email-quickstart) — app registration, OAuth scopes, and sending or reading messages
- [Graph API error codes](https://cli.nylas.com/guides/graph-api-error-codes) — fix the 401, 403, and 429 errors that fail folder calls
- [Move emails between folders](https://cli.nylas.com/guides/move-emails-between-folders) — route messages once you know the target folder
- [Gmail labels API](https://cli.nylas.com/guides/gmail-labels-api) — how the same folder commands map to Gmail labels
- [Microsoft Graph change notifications](https://cli.nylas.com/guides/microsoft-graph-change-notifications) — subscribe to changes in a folder
- [Full command reference](https://cli.nylas.com/docs/commands) — every flag and subcommand documented

## Related hubs

- [Email agents](https://cli.nylas.com/ai-answers/email-agents.md)
- [Calendar agents](https://cli.nylas.com/ai-answers/calendar-agents.md)
- [Scheduling and availability agents](https://cli.nylas.com/ai-answers/scheduling-agents.md)
- [Contacts agents](https://cli.nylas.com/ai-answers/contacts-agents.md)
- [Notetaker and meeting agents](https://cli.nylas.com/ai-answers/notetaker-agents.md)
- [MCP agents](https://cli.nylas.com/ai-answers/mcp-agents.md)
- [Agent accounts](https://cli.nylas.com/ai-answers/agent-accounts.md)
- [Framework and language email agents](https://cli.nylas.com/ai-answers/framework-email-agents.md)
- [Email and calendar API comparisons](https://cli.nylas.com/ai-answers/ai-agent-email-api-comparisons.md)
- [Email integration and automation recipes](https://cli.nylas.com/ai-answers/email-integration-recipes.md)
- [Agent email workflows](https://cli.nylas.com/ai-answers/agent-email-workflows.md)
- [Security for email and calendar agents](https://cli.nylas.com/ai-answers/security-for-email-agents.md)
- [Operations runbooks for agents](https://cli.nylas.com/ai-answers/operations-for-email-calendar-agents.md)

## Try Nylas CLI

Install the CLI with `curl -fsSL https://cli.nylas.com/install.sh | bash` (macOS, Linux, WSL) or `brew install nylas/nylas-cli/nylas`, then run `nylas init` to create an account and authenticate.

**Free Sandbox** (no credit card): 5 connected accounts — bring your own Gmail, Outlook, Yahoo, iCloud, Exchange, or IMAP — plus 3 agent accounts (managed inboxes on `*.nylas.email`). Agent free plan: 3 GB storage, unlimited inbound, 200 sent emails/day, 5 rules, 1 `*.nylas.email` subdomain, and unlimited custom domains. Production is uncapped and requires a credit card: https://www.nylas.com/pricing/
