# How to forward an email

Source: https://developer.nylas.com/docs/cookbook/email/forward-email/

Forwarding an email means sending the original message to a new set of recipients, with the original content quoted below your note and the attachments carried along. There's no dedicated forward field in the v3 API, and that surprises people. You compose a new message yourself, set fresh `to` recipients, prefix the subject with `Fwd:`, and include the original body and files.

This recipe shows the full forward composition against `/messages/send`: how to fetch the original message, build a quoted body, re-attach files, and why forwarding differs from a reply. The same flow works whether a person triggers it in your UI or an automated process forwards an inbound message to a downstream team.

## How forwarding differs from replying

Forwarding and replying both call `POST /v3/grants/{grant_id}/messages/send`, but they set different fields. A reply uses `reply_to_message_id` to thread under the original conversation and usually goes back to the original sender. A forward sends the original content to brand-new recipients you choose, so you leave threading off and compose the quoted content into the body yourself.

Per the [Messages API reference](/docs/reference/api/messages/), `reply_to_message_id` is the ID of the message you're replying to. For Gmail and Microsoft Graph it's the provider message ID, and for IMAP it's the RFC822 `Message-ID` header. The API reads that message and stamps `In-Reply-To` and `References` headers so the outbound nests in the thread. That's exactly what you don't want when forwarding, because a forward starts a fresh conversation with people who weren't on the original. So for a true forward you omit `reply_to_message_id`, set new `to` recipients, and put the quoted original into the `body`. If you need a reply instead, see [How to reply to an email thread](/docs/cookbook/email/threads/reply-to-a-thread/).

## Fetch the original message

Before you can forward an email, you need its content: the subject, the body, the sender, and the list of attachments. Fetch the message by ID with `GET /v3/grants/{grant_id}/messages/{message_id}`. The response includes an `attachments` array where each of the 4 core fields per file (`id`, `filename`, `content_type`, and `size` in bytes) describes the file, but not the file bytes themselves.

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

```

```json
{
  "request_id": "5fa64c92-e840-4357-86b9-2aaa1f8b6c1f",
  "data": {
    "id": "<MESSAGE_ID>",
    "grant_id": "<NYLAS_GRANT_ID>",
    "object": "message",
    "subject": "Q3 proposal next steps",
    "from": [{ "name": "Alex Customer", "email": "customer@example.com" }],
    "to": [{ "name": "You", "email": "you@example.com" }],
    "date": 1717942920,
    "body": "<p>Hi, here are the revised numbers we discussed...</p>",
    "attachments": [
      {
        "id": "<ATTACHMENT_ID>",
        "grant_id": "<NYLAS_GRANT_ID>",
        "filename": "q3-proposal.pdf",
        "content_type": "application/pdf",
        "size": 84213
      }
    ],
    "thread_id": "<THREAD_ID>"
  }
}
```

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

import Nylas from "nylas";

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

async function fetchMessageById() {
  try {
    const message = await nylas.messages.find({
      identifier: "<NYLAS_GRANT_ID>",
      messageId: "<MESSAGE_ID>",
    });

    console.log("message:", message);
  } catch (error) {
    console.error("Error fetching message:", error);
  }
}

fetchMessageById();


```

```python [getMessage-Python SDK]

from nylas import Client

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

grant_id = "<NYLAS_GRANT_ID>"
message_id = "<MESSAGE_ID>"

message = nylas.messages.find(
  grant_id,
  message_id,
)

print(message)

```

The `body` field holds the original HTML, which you'll quote in the forwarded message. The `attachments` array lists files by ID, but each file needs its own download before you can re-attach it, so a message with 2 attachments means 2 requests after this fetch. Keep the `from`, `date`, `to`, and `subject` values too, since a clean forward header repeats them at the top of the quoted body.

## Compose and send the forward

A forward is a new message with new `to` recipients and a body that holds your note plus the quoted original. The request below sends through `POST /v3/grants/{grant_id}/messages/send` with the subject prefixed `Fwd:` and an HTML body that reproduces the original headers and content. There's no `reply_to_message_id`, so the message starts a fresh conversation rather than threading.

```bash
curl --request POST \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/send' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "to": [{ "email": "ops-team@example.com", "name": "Ops Team" }],
    "subject": "Fwd: Q3 proposal next steps",
    "body": "<p>Forwarding this for your review.</p><hr><p><b>From:</b> Alex Customer &lt;customer@example.com&gt;<br><b>Date:</b> Mon, 9 Jun 2025 14:22:00<br><b>Subject:</b> Q3 proposal next steps</p><p>Hi, here are the revised numbers we discussed...</p>"
  }'
```

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


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

// `original` is the message you fetched in the previous step.
const quoted =
  `<p>Forwarding this for your review.</p><hr>` +
  `<p><b>From:</b> ${original.from[0].name} &lt;${original.from[0].email}&gt;<br>` +
  `<b>Subject:</b> ${original.subject}</p>` +
  original.body;

const subject = original.subject.startsWith("Fwd:")
  ? original.subject
  : `Fwd: ${original.subject}`;

const sent = await nylas.messages.send({
  identifier: "<NYLAS_GRANT_ID>",
  requestBody: {
    to: [{ email: "ops-team@example.com", name: "Ops Team" }],
    subject,
    body: quoted,
  },
});
```

```python [forwardMessage-Python SDK]
from nylas import Client

nylas = Client("<NYLAS_API_KEY>")

# `original` is the message you fetched in the previous step.
sender = original["from"][0]
quoted = (
    "<p>Forwarding this for your review.</p><hr>"
    f"<p><b>From:</b> {sender['name']} &lt;{sender['email']}&gt;<br>"
    f"<b>Subject:</b> {original['subject']}</p>"
    f"{original['body']}"
)

subject = original["subject"]
if not subject.startswith("Fwd:"):
    subject = f"Fwd: {subject}"

sent = nylas.messages.send(
    "<NYLAS_GRANT_ID>",
    request_body={
        "to": [{"email": "ops-team@example.com", "name": "Ops Team"}],
        "subject": subject,
        "body": quoted,
    },
)
```

The API doesn't add the subject prefix for you, so set `Fwd:` yourself and skip it when the original subject already starts with one. Build the quoted header from the original `from`, `date`, and `subject` so recipients see where the message came from. A body-only forward like this one stays well under the 3 MB JSON request limit, and attachments are what push you toward it. The new message lands in the sender's Sent folder like any other outbound email.

## Re-attach the original files

Attachments don't travel automatically. The forwarded message only includes files you put in its `attachments` array, so you download each original file, then attach it to the send request. Download with `GET /v3/grants/{grant_id}/attachments/{attachment_id}/download`, which requires the `message_id` of the message the attachment belongs to as a query parameter.

```bash
curl --request GET \
  --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/attachments/<ATTACHMENT_ID>/download?message_id=<MESSAGE_ID>' \
  --header 'Authorization: Bearer <NYLAS_API_KEY>' \
  --output original-attachment.pdf
```

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


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

// Download each attachment listed on the original message, then base64-encode it.
const attachments = await Promise.all(
  original.attachments.map(async (file) => {
    const stream = await nylas.attachments.download({
      identifier: "<NYLAS_GRANT_ID>",
      attachmentId: file.id,
      queryParams: { messageId: original.id },
    });
    const chunks = [];
    for await (const chunk of stream) chunks.push(chunk);
    return {
      filename: file.filename,
      contentType: file.content_type,
      content: Buffer.concat(chunks).toString("base64"),
    };
  })
);

const sent = await nylas.messages.send({
  identifier: "<NYLAS_GRANT_ID>",
  requestBody: {
    to: [{ email: "ops-team@example.com", name: "Ops Team" }],
    subject: `Fwd: ${original.subject}`,
    body: quoted,
    attachments,
  },
});
```

```python [downloadAttachment-Python SDK]

from nylas import Client

nylas = Client("<NYLAS_API_KEY>")

# Download each attachment listed on the original message, then base64-encode it.
attachments = []
for file in original["attachments"]:
    data = nylas.attachments.download(
        "<NYLAS_GRANT_ID>",
        file["id"],
        query_params={"message_id": original["id"]},
    )
    attachments.append({
        "filename": file["filename"],
        "content_type": file["content_type"],
        "content": base64.b64encode(data).decode("utf-8"),
    })

sent = nylas.messages.send(
    "<NYLAS_GRANT_ID>",
    request_body={
        "to": [{"email": "ops-team@example.com", "name": "Ops Team"}],
        "subject": f"Fwd: {original['subject']}",
        "body": quoted,
        "attachments": attachments,
    },
)
```

Base64-encoded attachments in a JSON request work up to 3 MB total. For files larger than that and up to 25 MB, switch to `multipart/form-data`, which the [send attachments recipe](/docs/cookbook/email/attachments/send-attachments/) covers step by step. Watch out for inline images: if the original body references a `cid:` source, re-attach that file with a matching `content_id` so the image renders instead of breaking.

## Things to watch for

A few details trip people up when they build forwarding on top of `/messages/send`. Most stem from the fact that you compose the forward yourself rather than calling a single forward endpoint, so you re-add every piece of the original you want carried over. The size limit is the most common surprise: an original message with a 10 MB attachment can't ride along in a base64 JSON request.

- **Don't set `reply_to_message_id` on a forward.** That field threads the message under the original conversation and references the original sender. A forward goes to new recipients and should start fresh, so leave it out. Use it only when you mean to [reply to the thread](/docs/cookbook/email/threads/reply-to-a-thread/).
- **Sanitize the quoted body before you send.** The original `body` is raw provider HTML and can carry remote tracking images or broken `cid:` references. Strip or rewrite anything you don't want forwarded, especially when an automated process forwards inbound mail from an unverified source.
- **Re-attach inline images with their `content_id`.** Inline images use a `cid:` reference in the HTML and a matching `content_id` on the attachment. Drop the `content_id` and the image shows as a broken link in the forwarded copy.
- **Carry CC and BCC with intent.** A forward doesn't copy the original `cc` list. Add `cc` or `bcc` only if you want those people on the new message, since forwarding to a fresh recipient set is usually the point.
- **Mind the total payload size.** The base64 JSON path caps at 3 MB. If the combined attachments exceed it, switch to multipart or drop large files and link to them instead.

## What's next

- [How to reply to an email thread](/docs/cookbook/email/threads/reply-to-a-thread/): the threading path with `reply_to_message_id` when you mean to reply, not forward
- [How to send emails with attachments](/docs/cookbook/email/attachments/send-attachments/): the base64 and multipart attachment patterns in detail
- [Read a single message or thread](/docs/cookbook/email/get-message-thread/): fetch the original message content before composing the forward
- [Messages API reference](/docs/reference/api/messages/): full reference for `/messages/send`