# Read and parse incoming email

Source: https://developer.nylas.com/docs/cookbook/email/read-parse-incoming-email/

You connected a mailbox, and now you need what's actually inside each email: who sent it, the subject line, the body text, and any files attached. Pulling that out of raw email yourself means wrestling with MIME boundaries, base64 blobs, and a dozen character encodings.

The Nylas Email API returns every message already parsed. One GET request gives you structured `from`, `to`, `subject`, `body`, and `attachments` fields across Gmail, Outlook, Yahoo, iCloud, and IMAP, so your code never opens a MIME parser.


> **Info:** 
> **New to Nylas?** Start with the [quickstart guide](/docs/v3/getting-started/) to set up your app and connect a test account before continuing here.


## How do I read an incoming email with an API?

Send a GET request to `/v3/grants/{grant_id}/messages` to read recent email from a connected account. The response returns each message as JSON with parsed `from`, `subject`, `body`, and `attachments` fields. There's no fetch command and no MIME decoding step on your side.

The request below lists the 5 most recent messages from one mailbox using the `limit` query parameter. The endpoint returns 50 messages by default and up to 200 per page, so set `limit` deliberately when you only need the latest few.

```bash
curl --compressed --request GET \
  --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?limit=5" \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json'

```

```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",
          "email": "nylasdev@nylas.com"
        }
      ],
      "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",
          "email": "nyla@nylas.com"
        }
      ],
      "created_at": 1706811644,
      "body": "Learn how to send emails using the Nylas APIs!"
    }
  ],
  "next_cursor": "123"
}


```

```js [readMessages-Node.js SDK]

import Nylas from "nylas";

const nylas = new Nylas({
  apiKey: "<NYLAS_API_KEY>",
  apiUri: "<NYLAS_API_URI>",
});

async function fetchRecentEmails() {
  try {
    const messages = await nylas.messages.list({
      identifier: "<NYLAS_GRANT_ID>",
      queryParams: {
        limit: 5,
      },
    });

    console.log("Messages:", messages);
  } catch (error) {
    console.error("Error fetching emails:", error);
  }
}

fetchRecentEmails();


```

```python [readMessages-Python SDK]

from nylas import Client

nylas = Client(
    "<NYLAS_API_KEY>",
    "<NYLAS_API_URI>"
)

grant_id = "<NYLAS_GRANT_ID>"

messages = nylas.messages.list(
  grant_id,
  query_params={
    "limit": 5
  }
)

print(messages)

```

The same request shape works for every provider. Swap the grant and you read Gmail, Outlook, or a self-hosted IMAP server through one schema.

## What fields does a parsed message include?

Each message object carries the parsed headers and content you need to display or process email. Sender and recipient fields arrive as arrays of name and email pairs, the body comes back as a single string, and attachments are listed as metadata you fetch separately. The table below covers the fields you'll use most.

| Field | Description |
| --- | --- |
| `from`, `to`, `cc`, `bcc` | Arrays of `{ name, email }` objects for each participant. |
| `subject` | The message subject as a plain string. |
| `body` | The full message body as HTML or plain text, depending on the sender. |
| `snippet` | A short preview of the body, useful for inbox list views. |
| `date` | Send time as a Unix timestamp in seconds (for example, `1706811644`). |
| `folders` | The folder or label IDs the message belongs to, such as `INBOX`. |
| `thread_id` | The ID of the conversation this message belongs to. |
| `attachments` | Metadata for each file: `filename`, `size` in bytes, and `content_type`. |
| `unread`, `starred` | True or false flags for read state and flag state. |

Those 11 fields replace the headers, parts, and flags you'd otherwise extract from a raw message. To pull just one conversation, use the `thread_id` to fetch the [full thread](/docs/cookbook/email/get-message-thread/).

## Why not parse MIME and email headers yourself?

Raw email is a MIME document with nested multipart sections, per-part character sets, and content encoded as base64 or quoted-printable. Parsing it by hand with Python's `email` module or a Node library means handling each encoding, telling inline images from real attachments, and surviving malformed messages from real senders.

MIME has accumulated quirks since RFC 2045 standardized it in 1996, and every provider bends the rules a little. A REST API does that decoding server-side and hands back clean fields, which is the main reason teams reach for one instead of an [IMAP client](/docs/cookbook/email/imap-vs-rest-email-api/). You trade some low-level control for not maintaining a parser against thousands of mailbox variations.

## How do I get the readable text from a body?

Message bodies often arrive as HTML stuffed with signatures, quoted replies, and tracking images. To strip that down to plain text or markdown, use the clean messages endpoint, which removes boilerplate and quoted history. It handles up to 20 messages in 1 request, so you can clean a batch before showing previews or feeding text to a language model.

The full request, options, and language samples live in the [clean messages guide](/docs/cookbook/email/clean-messages/). Reach for it whenever the raw `body` field is too noisy to display or summarize directly.

## Things to know about reading email content

A few practical details shape how the `body` and `attachments` fields behave in production. Most providers return HTML bodies, so plan to sanitize or render the markup rather than print it raw. The `snippet` field gives you a quick text preview without parsing the HTML at all.

The message lists attachments by metadata, not embedded bytes in the JSON. Each entry lists a `size` in bytes (the sample invite above is 2,504 bytes) and a `content_type`, and you fetch the binary content from the [attachments endpoint](/docs/cookbook/email/attachments/download-attachments/) when you need it. Inline images carry a `content_id` referenced from `cid:` URLs in the body. Dates are Unix timestamps in seconds, so multiply by 1,000 before passing them to a JavaScript `Date`.

## How do I read a single message by ID?

Fetch one message with a GET request to `/v3/grants/{grant_id}/messages/{message_id}`. You get the same parsed object the list endpoint returns, scoped to a single email, with all 11 fields populated. Reach for it when you already hold a message ID from a webhook or an earlier list call and need the full body and attachments.

The request below fetches one message by its ID. The response is a single `message` object rather than an array, so there's no `data` list to iterate.

```bash
curl --request GET \
  --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/<MESSAGE_ID>" \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

A webhook is the usual source of that message ID. To get one whenever mail arrives, set up a [new email webhook](/docs/cookbook/use-cases/build/new-email-webhook/) and read each message by the ID in the event payload.

## How do I read only unread or recent messages?

Narrow the list with query parameters instead of pulling every message. The `unread=true` filter returns only unread mail, and `received_after` takes a Unix timestamp in seconds to fetch recent messages. Combine them with `limit` to cap the page at 10 messages. These filters run server-side, so you transfer less data over the wire.

The request below returns up to 10 unread messages received after a given timestamp, which is a common shape for an inbox sync loop.

```bash
curl --request GET \
  --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?unread=true&received_after=1706000000&limit=10" \
  --header 'Authorization: Bearer <NYLAS_API_KEY>'
```

You can also filter by `from`, `to`, `subject`, `in` (folder or label ID), and `has_attachment`. The [Messages API reference](/docs/reference/api/messages/) lists every parameter and its accepted values.

## FAQ

### Does the API decode base64 and quoted-printable for me?

Yes. The `body` field arrives already decoded, no matter how the original MIME parts were encoded. You never call a base64 or quoted-printable decoder yourself, which is the main thing a REST email API removes from your code.

### Is the body HTML or plain text?

It depends on what the sender sent, and most messages come back as HTML in the `body` field. Use the `snippet` field for a quick 100-character plain-text preview, or the [clean messages endpoint](/docs/cookbook/email/clean-messages/) to convert HTML to plain text or markdown.

### Are attachments included in the body?

No. The `body` holds text and HTML only. Attachments appear in the `attachments` array as metadata (`filename`, `size` in bytes, and `content_type`), and you download the bytes from the [attachments endpoint](/docs/cookbook/email/attachments/download-attachments/) when you need them.

### What timezone is the date field?

The `date` field is a Unix timestamp in seconds, which is timezone-independent. Convert it to the user's local time in your app. In JavaScript, multiply the value by 1,000 and pass it to `new Date()`.

## What's next

- [List IMAP messages](/docs/cookbook/email/messages/list-messages-imap/) for connecting generic IMAP and custom mail servers.
- [IMAP vs REST email API](/docs/cookbook/email/imap-vs-rest-email-api/) to decide how to read mail at scale.
- [Build a unified inbox](/docs/cookbook/email/unified-inbox/) to merge parsed messages from several accounts.
- [Messages API reference](/docs/reference/api/messages/) for every query parameter and response field.