Gmail and Google Workspace accounts are the most common provider type developers integrate with. If you’ve ever worked with the Gmail API directly, you know the OAuth verification process, scope restrictions, and label system add significant friction. Nylas abstracts all of that behind the same Messages API you’d use for Microsoft or IMAP.
This guide walks through listing messages from Google accounts and covers the Google-specific details you need to know: labels, search operators, OAuth scopes, and rate limits.
Why use Nylas instead of the Gmail API directly?
Section titled “Why use Nylas instead of the Gmail API directly?”The Gmail API requires more setup overhead than most developers expect. You need to configure a GCP project, navigate Google’s three-tier OAuth scope system (non-sensitive, sensitive, restricted), and for any scope beyond basic metadata, go through Google’s OAuth verification or even a full third-party security assessment. On top of that, the Gmail data model uses labels instead of folders, message IDs are hex strings, and rate limits are enforced at both the per-user and per-project level.
Nylas normalizes all of this. Labels map to a unified folder model. Token refresh and scope management happen automatically. You don’t need a GCP project or a security assessment to get started. And your code works across Gmail, Outlook, Yahoo, and IMAP without any provider-specific branches.
If you only need Gmail and want full control over the integration, the Gmail API works. If you need multi-provider support or want to skip the verification process, Nylas is the faster path.
Before you begin
Section titled “Before you begin”You’ll need:
- A Nylas application with a valid API key
- A grant for a Gmail or Google Workspace account
- The appropriate Google OAuth scopes configured in your GCP project
New to Nylas? Start with the quickstart guide to set up your app and connect a test account before continuing here.
Google OAuth scopes and verification
Section titled “Google OAuth scopes and verification”Google classifies OAuth scopes into three tiers, and each one comes with different verification requirements:
| Scope tier | Example | What’s required |
|---|---|---|
| Non-sensitive | gmail.labels | No verification needed |
| Sensitive | gmail.readonly, gmail.compose | OAuth consent screen verification |
| Restricted | gmail.modify | Full security assessment by a third-party auditor |
If your app needs to read message content (not just metadata), you’ll need at least the gmail.readonly scope, which is classified as sensitive. For read-write access, gmail.modify is restricted and requires a security assessment.
Nylas handles the token management, but your GCP project still needs the right scopes configured. See the Google provider guide for the full setup.
List messages
Section titled “List messages”Make a List Messages request with the grant ID. By default, Nylas returns the 50 most recent messages. 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 Google 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 |
When using the in parameter with Google accounts, you must use the folder (label) ID, not the display name. Nylas does not support filtering by label name on Google accounts. Use the List Folders endpoint to get the correct IDs.
Here’s how to combine filters. 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, })Use Gmail search operators
Section titled “Use Gmail search operators”For more advanced filtering, you can use Gmail’s native search syntax through the search_query_native parameter. This supports the same search operators you’d use in the Gmail search bar:
curl --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?search_query_native=subject%3Ainvoice%20OR%20subject%3Areceipt" \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'const messages = await nylas.messages.list({ identifier: grantId, queryParams: { searchQueryNative: "subject:invoice OR subject:receipt", limit: 10, },});messages = nylas.messages.list( grant_id, query_params={ "search_query_native": "subject:invoice OR subject:receipt", "limit": 10, })Some useful Gmail search operators:
| Operator | What it does | Example |
|---|---|---|
from: | Messages from a sender | from:[email protected] |
to: | Messages to a recipient | to:[email protected] |
subject: | Subject line contains | subject:invoice |
has:attachment | Has file attachments | has:attachment |
filename: | Attachment filename | filename:report.pdf |
after: | Messages after a date | after:2025/01/01 |
before: | Messages before a date | before:2025/02/01 |
is:unread | Unread messages | is:unread |
label: | Messages with a label | label:important |
OR | Combine conditions | from:alex OR from:priya |
When using search_query_native, you can only combine it with the in, limit, and page_token parameters. Other query parameters will return an error. See the search best practices guide for more details.
Things to know about Google
Section titled “Things to know about Google”A few provider-specific details that matter when you’re building against Gmail and Google Workspace accounts.
Labels, not folders
Section titled “Labels, not folders”This is the biggest conceptual difference from Microsoft. Gmail uses labels instead of folders, so a single message can have multiple labels at once. When you list messages with ?in=INBOX, you’re filtering by the INBOX label, not a folder.
Nylas normalizes this into a folders array on each message object. A Gmail message might look like:
{ "folders": ["INBOX", "UNREAD", "CATEGORY_PERSONAL", "IMPORTANT"]}Common system labels you’ll see:
| Gmail UI name | Label ID |
|---|---|
| Inbox | INBOX |
| Sent | SENT |
| Drafts | DRAFT |
| Trash | TRASH |
| Spam | SPAM |
| Starred | STARRED |
| Important | IMPORTANT |
| Primary tab | CATEGORY_PERSONAL |
| Social tab | CATEGORY_SOCIAL |
| Promotions tab | CATEGORY_PROMOTIONS |
| Updates tab | CATEGORY_UPDATES |
Custom labels created by the user will have auto-generated IDs. Use the List Folders endpoint to discover them. Google also supports custom label colors via text_color and background_color parameters when creating or updating folders.
Message IDs are shorter than Microsoft’s
Section titled “Message IDs are shorter than Microsoft’s”Google message IDs are shorter hex-style strings (like 18d5a4b2c3e4f567), compared to Microsoft’s long base64-encoded IDs. They’re stable across syncs and safe to store in your database.
The thread_id field is a Google-native concept. Gmail groups related messages into threads automatically. If you’re building an inbox UI, you’ll probably want to use the Threads API instead of listing individual messages.
Rate limits are per-user and per-project
Section titled “Rate limits are per-user and per-project”Google enforces quotas at two levels:
- Per-user: Each authenticated user has a daily quota for API calls
- Per-project: Your GCP project has an overall daily limit across all users
Nylas handles retries when you hit rate limits, but if your app polls aggressively for many users, you may exhaust your project quota. Two ways to avoid this:
- Use webhooks instead of polling so Nylas notifies your server when new messages arrive
- Set up Google Pub/Sub for real-time sync, which gives you faster notification delivery for Gmail accounts with
gmail.readonlyorgmail.labelsscopes
One-click unsubscribe headers
Section titled “One-click unsubscribe headers”If your app sends marketing or subscription email through Gmail accounts, be aware that Google requires one-click unsubscribe headers for senders who send more than 5,000 messages per day to Gmail addresses. You’ll need to include List-Unsubscribe-Post and List-Unsubscribe custom headers in your send requests.
This doesn’t affect listing messages, but it’s worth knowing if your app both reads and sends email. See the email documentation for implementation details.
Google Workspace vs. personal Gmail
Section titled “Google Workspace vs. personal Gmail”Both work with Nylas, but there are differences:
- Workspace admins can restrict which third-party apps have access. If a Workspace user can’t authenticate, their admin may need to allow your app in the Google Admin console.
- Delegated mailboxes (shared mailboxes in Workspace) require special handling. See the shared accounts guide.
- Service accounts are available for Google Workspace Calendar access (not email). See the service accounts guide.
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 Gmail-style conversations
- Search best practices for advanced search with
search_query_nativeand provider-specific operators - Webhooks for real-time notifications instead of polling
- Google Pub/Sub for real-time sync with Gmail accounts
- Google provider guide for full Google setup including OAuth scopes and verification
- Google verification & security assessment, required for restricted scopes in production