---
title: "Forward a message"
method: POST
path: "/v1/messages/{id}/forward"
tags: ["Messages"]
---

# Forward a message

`POST /v1/messages/{id}/forward`

## Request

- Base URL: `https://api.mails.ai`
- URL: `https://api.mails.ai/v1/messages/{id}/forward`
- Auth: HTTP bearer

**Requires scope:** `send`. Forwards a sent or received message to new recipients with a quoted body and `Fwd:` subject. Always sends immediately. Both parts carry the forwarded message: the HTML part is the covering note (`body_html`, else `body_text`), the forwarded header and the original (its own HTML, or its text), sent whenever there is HTML to carry. A received message's files go with it, up to the limits of any send (10 files, 25 MB in all), one shown inside its HTML keeping its `content_id`; they are listed in `attachments`. Files that cannot go are named in both parts under `Not attached:`: a sent message's (only their names are kept), a received file that was too large to keep, and any past the limits. A forward cannot add files of its own: `attachments` in the request answers 422 `invalid_field` (param `attachments`); send files with POST /v1/messages.

## Path parameters

- `id` string, required

## Headers

- `Idempotency-Key` string

## Request body

- ForwardMessageRequest
  - `to` union, required: Who the forward goes to: one address, or a list of 1 to 50.
    - string, email
    - string[]
  - `cc` string[]: Addresses to copy, up to 50. Every recipient sees them.
  - `bcc` string[]: Addresses to blind-copy, up to 50. They receive the forward but appear in none of its headers.
  - `subject` string: Subject of the forward, 1 to 998 characters. Left out, it is `Fwd: ` and the original's subject, kept as it is when that already starts with `Fwd:` or `Fw:`.
  - `body_text` string: Your note, up to 1,000,000 characters, placed above a `---------- Forwarded message ----------` block that quotes the original's From, To, Subject and plain text. Left out, the forward carries that block alone.
  - `body_html` string: Your note as HTML, up to 1,000,000 characters, placed above the same `---------- Forwarded message ----------` block as the plain-text part and then the original's own HTML (or its plain text when it has none). Of a whole HTML document, only what is inside `<body>` is used. Left out, `body_text` is the note there, and the forward has an HTML part only when the original has one.
  - `html` string: Alias of `body_html`: used when `body_html` is left out, ignored when both are sent.
  - `text` string: Alias of `body_text`: used when `body_text` is left out, ignored when both are sent.
  - `list_unsubscribe` boolean: Opt in to RFC 8058 one-click unsubscribe: the message carries `List-Unsubscribe` and `List-Unsubscribe-Post: List-Unsubscribe=One-Click` headers, so the recipient's mail app shows an Unsubscribe button. One click suppresses the address for your whole workspace: every later send to it from any of your agents is refused with `recipient_suppressed` until you allowlist it (`POST /v1/suppression/allow`). Turn it on for newsletters, digests and other recurring mail. Leave it off (the default) for password resets, sign-in links, receipts and anything else the recipient must keep receiving. Needs exactly one `to` recipient and no `cc` or `bcc`.

## Response `201`

The forwarded message.

- SentMessage
  - `id` string, required: Message id (`msg_…`), the `{id}` in `/v1/messages/{id}`.
  - `agent_id` string: Id of the agent that sent the message (`agt_…`).
  - `from` string, email: The agent address this was sent as — the resolved `from`. Read it back when you pass `from` as a naming hint instead of an explicit `agent`: it is the receipt for which identity the API picked. On a verified custom domain the message goes out from this exact address. On the shared mails.ai domain it goes out from `<name>.<workspace>@send.mails.ai`, and replies come back through `<name>.<workspace>@in.mails.ai`.
  - `thread_id` string: The thread the message belongs to (`thrd_…`).
  - `in_reply_to_message_id` string: The message this one answers: the message replied to or forwarded, or the `in_reply_to_message_id` the send set. Present on reply responses and on the single-message GET, when set.
  - `to` string[]: The To recipients, always a list. On a reply they come from the original: its Reply-To or sender for a received message, its `to` for one of your sent messages.
  - `cc` string[]: Cc recipients. Always present on reply responses (`[]` when none); on the list and the single-message GET, present when there are any.
  - `bcc` string[]: Bcc recipients. Present only on the list and the single-message GET, when there are any.
  - `subject` string: The subject as sent. On a reply, `Re: ` and the original's subject, unless that already starts with `Re:`; on a forward, the `subject` you gave or `Fwd: ` and the original's.
  - `body_text` string: The plain-text body as sent; on a forward it includes the quoted original. Present only on the single-message GET, when there is one.
  - `body_html` string: The HTML body as sent. Present only on the single-message GET, when there is one.
  - `classifier_score` number: The cold-email firewall's score for the message, from 0 (clean) to 1 (cold or spam). Present on the send response, the list and the single-message GET.
  - `status` 'scheduled' | 'sending' | 'sent' | 'delivered' | 'delayed' | 'bounced' | 'complained' | 'rejected' | 'canceled', required: `sent` once handed off; `delivered`, `bounced` or `complained` when the receiving side reports it (with several recipients the latest report wins). `delayed` when a transient (soft) bounce came back: the receiving server has not taken the message yet; it is not a bounce, does not count toward the bounce rate, raises `message.delayed` (sent only to endpoints that list it) and may still become `delivered`; with no delivery confirmed within 5 days of sending, it becomes `bounced`. `sending` only while a scheduled send is going out; `rejected` when the send failed, or a scheduled send was refused when it came due; `canceled` when a scheduled send was stopped. A live send only to the reserved test address reply@test.mails.ai is `delivered` at once, with a `message.delivered` event, since it is never transmitted; with a test key it stays `sent`, as every test-mode send does. Either way its automatic reply arrives as reply.received (at most 3 replies in one thread; a 4th message there gets none).
  - `refused_recipients` string[]: The recipients the email service refused when the message was sent, while it took the message for the others: it went to everyone else, and not to these. The message is still a send, so sending it again with the same `Idempotency-Key` gives this answer back and sends nothing; to reach them, check the addresses and send them a new message. Present only when there were any: on the send, batch, reply and forward responses, the list, the single-message GET and the message's `message.sent` event. When every recipient is refused, nothing is sent and the send fails instead.
  - `cost_usd` number: USD cost of the send. 0 for scheduled or test-mode sends.
  - `ses_message_id` string: The sending provider's id for the message, which its delivery, bounce and complaint reports refer to; `test-<message id>` for a test-mode send. Present only on the list and the single-message GET, once the message has been handed off.
  - `attachments` AttachmentMeta[]: Name, type and size of each file the send carried; the files themselves are not kept. Present when there were any, on the send response, the list and the single-message GET.
    - `filename` string, required: The file name, as given in the send's `attachments`.
    - `content_type` string, required: The MIME type given in the send's `attachments`. A file fetched from its `path` without one takes the type the URL answered with, or `application/octet-stream` when it named none.
    - `size_bytes` integer, required: Size of the file in bytes: `content_base64` decoded, or the bytes fetched from `path`.
    - `content_id` string: Only on a file shown inside the HTML body: the id its `cid:` names.
  - `references` string[]: The `References` chain kept with the message: the `references` the send passed, or on a reply the ids of the earlier messages it answers. Present only on the single-message GET, when there is one.
  - `metadata` object: The `metadata` the send passed; a scheduled send refused when it came due also carries `rejected_reason` and `rejected_at`. Present only on the single-message GET, when there is any.
  - `tags` Tag[]: The tags the message was sent with. Present when there are any, on send, batch and reply responses, the list and the single-message GET.
    - `name` string, required: The tag's name, as given on the send, reply or draft.
    - `value` string, required: The tag's value, as given; it may be an empty string.
  - `sent_at` string, date-time: When the message was handed off for sending: at once for an immediate send, when it came due for a scheduled one. Present only on the list and the single-message GET, once that has happened.
  - `delivered_at` string, date-time: When delivery was confirmed; with several recipients, the time of the latest report. Present only on the list and the single-message GET, once a delivery is confirmed.
  - `scheduled_at` string, date-time: The time a scheduled send was set for. On send, batch and reply responses it is present only when that time was in the future; the list and the single-message GET keep it after the message has gone out or been canceled.
  - `canceled_at` string, date-time: When the scheduled send was canceled. Present only on the list and the single-message GET, for a canceled message.
  - `bounced_at` string, date-time: When a bounce was last reported for the message; a soft bounce, which marks it `delayed`, does not set it. Present only on the single-message GET.
  - `complained_at` string, date-time: When a recipient's complaint about the message was last reported. Present only on the single-message GET.
  - `forwarded_from` string: Source message id (forward responses only).
  - `test_mode` boolean: True for a message sent with a test key (`mk_test_…`): nothing was transmitted and nothing was billed.
  - `classifier_warning` object: Present only on a test-mode send that a live key would refuse: the cold-email firewall's verdict, reported instead of thrown. Nothing was transmitted.
    - `code` 'cold_email_prohibited', required: Always `cold_email_prohibited`, the error code a live key would get.
    - `message` string, required: The verdict in words: why a live key would refuse the message, what would pass instead, and that nothing was transmitted.
    - `score` number, required: The firewall's score for the message, from 0 (clean) to 1 (cold or spam).
    - `reason` string, required: What the message was classified as, such as `cold_outreach`, `spam_pattern` or `phishing_lure`.
  - `created_at` string, date-time: When the message was created; for a scheduled send, when it was scheduled, not when it went out.

## Other responses

- `400`: Invalid request — malformed JSON or a field failed validation. Also `feature_not_enabled` when this server has a feature switched off (custom domains before they are available).
- `401`: Missing or invalid API key.
- `403`: The API key lacks the required scope (`insufficient_scope`), or the token belongs to an app connected by sign-in and this operation is closed to connected apps (`connected_app_not_allowed`).
- `404`: Resource not found in this workspace, or the agent a send names is archived (`agent_archived`).
- `422`: Unprocessable — agent paused, recipient suppressed, a recipient at test.mails.ai other than reply@test.mails.ai (`unknown_test_address`), or abuse guard.
- `429`: Rate or quota limit exceeded. The body's `resets_at` and `retry_after_seconds` say when the request can succeed. A `Retry-After` header is sent only when that is 60 seconds or less; a longer wait (an hourly, daily or monthly cap) sends none, so retry at `resets_at` or raise the limit instead of sleeping.
- `500`: Internal server error.
- `502`: The email service the mail goes out through did not confirm it took it (`upstream_error`), so it is recorded as not sent. The message is a fixed sentence naming the request id to quote to support@mails.ai; the service's own error is never returned. When the service refuses every recipient as undeliverable the answer is 422 `recipient_suppressed` instead, and when it is only not taking more mail for now, 503 `upstream_unavailable`.
- `503`: Temporarily unavailable, and nothing was done. On a send: the email service is not taking more mail right now (`upstream_unavailable`), so nothing was sent; send it again after the seconds in `Retry-After`.

---

[API](https://skmtc.dev/mails/apis/mails-ai-api.md) · [All operations](https://skmtc.dev/mails/apis/mails-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mails/apis/mails-ai-api/revisions/4d4930a8c33b?raw)
