DEV Community

Qasim
Qasim

Posted on

Organize email with folders and labels in Nylas

Gmail organizes mail with labels, where one message can carry several at once. Microsoft, Yahoo, iCloud, and IMAP use folders, where a message lives in exactly one. Build email organization against each provider and you're writing to Gmail's label API, Microsoft Graph's folder API, and an IMAP folder model, each with different rules about how many places a message can be at once. The Nylas Email API gives you one set of calls that manages both, so the same code creates a Gmail label and an Outlook folder.

This post is a working tour of folders and labels from two angles: the HTTP API for your backend, and the nylas CLI for the terminal. I work on the CLI, so the terminal commands below are the ones I reach for when I'm sorting a mailbox by hand.

One API for folders and labels

Nylas treats folders and labels as the same resource, addressed at /v3/grants/{grant_id}/folders, and refers to each one by a folder_id. The difference between the two models surfaces on the message, not the folder API: a message carries a folders array, and on Google that array holds label IDs, so a single Gmail message can appear under several labels like INBOX and CATEGORY_PERSONAL at once. On folder-based providers, that array holds the one folder the message lives in.

That unification is what lets you write the integration once. The folder operations, create, list, rename, and delete, are identical across all six providers, and the provider-specific behavior is confined to how many entries a message's folders array can hold. Where a provider exposes extra fields, Nylas surfaces them: Google's color fields, background_color and text_color, come through on the folder object so you can set a label's color through the same call that creates it.

List folders and their system attributes

Start by seeing what's there. GET /v3/grants/{grant_id}/folders returns every folder on the account, each with an id, name, an attributes array, and provider-specific fields like a system_folder flag on Google and a parent_id on Microsoft and EWS. On Microsoft accounts, a few query parameters narrow the result: parent_id lists the children of one folder (this one also works on EWS), single_level avoids recursing into sub-folders, and include_hidden_folders surfaces folders the provider hides by default.

curl --request GET \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/folders" \
  --header "Authorization: Bearer <NYLAS_API_KEY>"
Enter fullscreen mode Exit fullscreen mode

From the terminal, nylas email folders list prints the folders in a table with their unread and total counts, and --json gives you the raw objects. The attributes array is the part worth understanding: it maps a folder to a standard role like \Inbox, \Sent, or \Trash, which matters because provider names vary. An IMAP account might call its trash "Deleted Messages" rather than "Trash", so you match on the attribute or the id, never on the display name.

Fetch a single folder and its counts

When you have a folder ID and want just that one, GET /v3/grants/{grant_id}/folders/{folder_id} returns it with its current message counts. The folder object carries total_count and unread_count, plus a child_count on Microsoft and EWS, so this is how you render a sidebar badge like "Invoices (3 unread)" without listing the messages themselves.

curl --request GET \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/folders/<FOLDER_ID>" \
  --header "Authorization: Bearer <NYLAS_API_KEY>"
Enter fullscreen mode Exit fullscreen mode

From the terminal, nylas email folders show <folder-id> prints the same folder with its counts. Those numbers come from the provider, so they stay accurate as messages move in and out, and where the provider reports child_count, it tells you whether a folder has nested folders under it before you try to list them.

Create a folder or label

Creating a folder is a POST /v3/grants/{grant_id}/folders with a name. On Microsoft and EWS you can pass a parent_id to nest it under another folder, and on Google you can set background_color and text_color to give the label a color in the Gmail UI. The response returns the new folder with its id, which you use to move messages into it later.

curl --request POST \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/folders" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{ "name": "Invoices" }'
Enter fullscreen mode Exit fullscreen mode

The CLI takes the name as a positional argument: nylas email folders create Invoices. It accepts --parent <folder-id> to nest the folder, and --bg-color and --text-color with hex values to color a Google label. Creating a colored label from the terminal is a single line:

nylas email folders create "Invoices" --bg-color "#16a765" --text-color "#ffffff"
Enter fullscreen mode Exit fullscreen mode

Nest folders into a hierarchy

Folders aren't always flat. Microsoft and EWS support nesting, and you build a tree with parent_id: pass it on create to place a new folder under an existing one, or on the PUT to move a folder under a different parent. Nylas flattens the provider's nested structure and adds parent_id to each child, so you can re-create the hierarchy in your own interface from the list response.

nylas email folders create "2024" --parent <invoices-folder-id>
Enter fullscreen mode Exit fullscreen mode

When you list folders on Microsoft, the single_level query parameter returns only the direct children of a parent_id instead of recursing the whole subtree, which keeps the folder list manageable on a large mailbox. Gmail's label model is flatter than Microsoft's folder tree, so reserve deep nesting for folder-based providers and keep labels shallow with naming conventions if you need to group them.

Rename and recolor a folder

To change a folder, PUT /v3/grants/{grant_id}/folders/{folder_id} updates its name, parent_id, or colors. This is the call you use to rename a label, move a folder under a new parent, or recolor it, and it works the same across providers that support each field.

curl --request PUT \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/folders/<FOLDER_ID>" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{ "name": "Paid invoices" }'
Enter fullscreen mode Exit fullscreen mode

The CLI covers the same ground with nylas email folders rename, which does more than its name suggests. It takes the folder ID and a new name as positional arguments and also accepts --bg-color, --text-color, and --parent, so you can recolor a Google label or re-parent a Microsoft folder from the terminal in one command:

nylas email folders rename <folder-id> "Paid invoices" --bg-color "#4986e7"
Enter fullscreen mode Exit fullscreen mode

If you only want to recolor or re-parent without changing the name, pass the folder's current name as the new name. Between the rename command and the create command, the CLI exposes the same fields the PUT and POST requests do.

Move a message into a folder

Folders are only useful if you can put messages in them, and a message moves by updating its folders array. An Update Message request with a new folders value reassigns the message, and on single-folder providers this overwrites the folder the message was in. The CLI wraps this in nylas email move, which takes the message ID and a destination folder:

nylas email move <message-id> --folder <folder-id>
Enter fullscreen mode Exit fullscreen mode

On Google the same folders array is the message's label set, so reassigning it changes which labels the message carries. The nylas email move <message-id> --archive flag archives a message, and what that means depends on the provider: on Gmail and other label-based accounts it removes every label including INBOX, while on folder-based accounts like IMAP and Microsoft the provider moves the message to its Archive folder instead. Either way it's the clean way to get a message out of the inbox without choosing a destination.

Find the inbox, sent, and trash by attribute

The folders you reference most are the system ones, and you find them by attributes, not by name. An IMAP account might label its trash "Deleted Messages", and a localized mailbox might translate "Sent", so matching on the display string is fragile. Instead, list the folders and pick the one whose attributes array contains the standard role you want, such as \Inbox, \Sent, \Drafts, or \Trash.

nylas email folders list --json
Enter fullscreen mode Exit fullscreen mode

Read the attributes of each folder in that output and map the standard roles to their provider-specific id values once, at startup. From then on, your code files a message to trash by its trash folder's id, which is stable, rather than guessing at a name that shifts between providers and languages. This is the same reason the move and delete examples above take a <folder-id> rather than a folder name.

List the messages in a folder

Once mail is filed, you read a folder's contents by filtering messages on it. Pass a folder's id to the in query parameter on GET /messages, and the response holds only the messages in that folder. This is the read side of organization: the folder API manages the containers, and the messages endpoint reads what's inside one.

curl --request GET \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/messages?in=<FOLDER_ID>" \
  --header "Authorization: Bearer <NYLAS_API_KEY>"
Enter fullscreen mode Exit fullscreen mode

The CLI mirrors this with nylas email list --folder <folder-id>, which lists messages from one folder instead of the default INBOX, and --all-folders to span every folder at once. You can pass a system name like SENT or a folder id, though on a mailbox where names vary, the id you pulled from folders list is the reliable choice.

Delete a folder carefully

Removing a folder is a DELETE /v3/grants/{grant_id}/folders/{folder_id}, or nylas email folders delete <folder-id> from the terminal. There's a serious caveat that's easy to miss: deleting a folder also deletes every message inside it. The messages don't move to trash, they go with the folder.

curl --request DELETE \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/folders/<FOLDER_ID>" \
  --header "Authorization: Bearer <NYLAS_API_KEY>"
Enter fullscreen mode Exit fullscreen mode

Because of that, the safe pattern is to move any messages you want to keep out of the folder before you delete it. List the folder's messages, move the ones that matter elsewhere with nylas email move, and only then delete the empty folder. Treat a folder delete as a destructive operation, not a tidy-up, and you won't lose mail you meant to file away.

Provider behaviors that surface through the API

A few provider differences carry through the unified API, and knowing them up front saves a confusing afternoon. None of them break the one-API model; they just shape how you read results.

The biggest is the Gmail labels model. A single Gmail message can carry multiple labels, so its folders array holds several IDs like INBOX, UNREAD, and CATEGORY_PERSONAL. On folder-based providers a message has exactly one folder, so the array holds one ID. Write your code to handle an array either way, rather than assuming a single value, and it works across all six providers.

There's also a filtering limitation worth internalizing. You can't filter folders by keyword or attribute: a query like in:inbox returns a 400 error. To list messages in a specific folder, pass the folder's id to the in query parameter on the messages endpoint, not its name or attribute. And because IMAP folder names returned by the API don't always match the provider's UI, always identify a folder by its id or its attributes rather than the display string.

Things to keep in mind

A short list of habits keeps a folders integration predictable across providers.

  • Match folders by id or attributes, never by name. Provider display names vary, and IMAP especially renames system folders.
  • Treat the folders array as multi-valued. Gmail messages carry several labels; assuming a single folder breaks on Google.
  • Deleting a folder deletes its messages. Move anything you want to keep before the DELETE call.
  • folders rename also recolors and re-parents. Despite the name, it takes --bg-color, --text-color, and --parent, matching the PUT API.
  • Filter messages by folder id with in. Keyword folder filters like in:inbox return a 400.
  • Read counts from the folder object. total_count and unread_count come back on each folder (plus child_count on Microsoft and EWS), so you don't list messages just to show a badge.

Wrapping up

Folders and labels collapse into one resource in the Nylas Email API, so the same create, list, rename, and delete calls manage a Gmail label and an Outlook folder without per-provider branching. Move messages with an Update Message request or nylas email move, color Google labels through the folder object, and lean on attributes and IDs instead of names. The one rule to carry everywhere: a message's folders array can hold more than one entry, because on Google it's a set of labels, not a single folder.

Where to go next:

Top comments (0)