---
title: "Update messages of a campaign in batch"
method: POST
path: "/v1/campaigns/{campaignId}/messages/batch/update"
tags: ["Campaigns"]
---

# Update messages of a campaign in batch

`POST /v1/campaigns/{campaignId}/messages/batch/update`

## Request

- Base URL: `https://api.folk.app`
- URL: `https://api.folk.app/v1/campaigns/{campaignId}/messages/batch/update`
- Auth: HTTP bearer

Update up to 100 messages of a campaign in review, each with the fields `PATCH /v1/campaigns/:campaignId/messages/:messageId` accepts. Each record is updated on its own, in order: one that fails does not prevent the others from being updated. A campaign that is not in review fails the whole request with a 409.

Edit a message of a campaign in review, as the app does: only the fields given change. The content of a message is final, so `{{variables}}` are rejected, apart from `{{signature}}` and `{{unsubscribeLink}}`. A body read back ends with `<p>{{signature}}</p>` when signed: keep it to keep the message's signature as it is, edited or not; a body without it has no signature. `{{unsubscribeLink}}` links to the unsubscribe page of that message.

A blank subject or body is rejected. A follow-up sent in the same thread takes no subject; a new subject on a message of the first step carries to the follow-ups of its thread. Edits are lost if the campaign goes back to draft, as its messages are generated again on the next review.<br/><Warning>This endpoint is currently in open beta. The API surface may change as we iterate based on feedback.</Warning>

## Path parameters

- `campaignId` string, required

## Headers

- `Idempotency-Key` string: A unique key, such as a UUID, that makes retrying this request safe: a retry with the same key and body returns the original response without running the operation again. Keys are kept for 24 hours after the request completes.

## Request body

- object
  - `records` object[], required
    - `id` string, required: The ID of the message to update.
    - `subject` string: The subject of the message, in plain text. A follow-up sent in the same thread has the first step's subject, so it takes none. Leave it out to keep the current one.
    - `body` string: The body of the message, in HTML (or plain text with `bodyFormat: "text"`). Its content is final: `{{variables}}` are rejected, apart from `{{signature}}` and `{{unsubscribeLink}}`. End it with `<p>{{signature}}</p>` to keep the message's signature as it is, edited or not; a body without it has no signature. An unsubscribe link, for SendGrid senders only, is `<a href="{{unsubscribeLink}}">`. Leave it out to keep the current body.
    - `bodyFormat` 'html' | 'text': How `body` is written. `html` (default) is read as HTML. `text` is plain text: a blank line starts a new paragraph, each further blank line adds an empty paragraph, a single newline is a line break, and `<` and `&` are literal characters. Bodies always read back as HTML.
    - `cc` string[]: Email addresses copied on the message. Leave it out to keep the current ones. At most 5 distinct addresses; duplicates are removed.
    - `bcc` string[]: Email addresses blind-copied on the message. Leave it out to keep the current ones. At most 5 distinct addresses; duplicates are removed.

## Response `200`

The outcome of each record, with each updated message.

- object
  - `data` object, required
    - `results` union[], required: One result per record, in the order of `records`: the first result is the outcome of the first record.
      - union
        - object
          - `status` 'succeeded', required
          - `data` MSGMessage, required: A message of a campaign: the email of one step for one recipient, with the variables of the step filled in for that recipient. There is one message per recipient and per step.
            - `id` string, required
            - `campaignId` string, required
            - `step` integer, required: The position of the step the message belongs to.
            - `recipient` object, required: Who the message is addressed to.
              - …
            - `sender` object, nullable, required: Who the message is from: the address sending used, once it was attempted; the step's sender otherwise, while draft or when stopped before sending. `id` is null once the sender was deleted. Null when there is no sender.
              - …
            - `subject` string, required: The subject of the message, in plain text.
            - `body` string, required: The body of the message, in HTML. A signed body ends with `<p>{{signature}}</p>`. Some older messages instead end with the signature written out as ordinary HTML.
            - `cc` string[], required: Email addresses copied on the message.
            - `bcc` string[], required: Email addresses blind-copied on the message.
            - `status` 'draft' | 'sent' | 'not_sent', required: The status of the message. One of: draft, sent, not_sent.
            - `notSentReason` string, nullable, required: Why the message was not sent, when its status is `not_sent`. Known values: no_recipient, recipient_not_found, recipient_no_email, recipient_invalid_email, too_many_copies, invalid_sender, stopped, stopped_after_reply, previous_messages_not_sent, dropped_unsubscribed_address, dropped_bounced_address, dropped_spam_reporting_address, dropped_spam_content, dropped_invalid, bounce_invalid_address, bounce_technical, bounce_content, bounce_reputation, bounce_frequency_or_volume_too_high, bounce_mailbox_unavailable, bounce_unclassified, send_failed. New values may be added in the future: handle unknown values gracefully. Treat unknown ones as `send_failed`.
            - `unsubscribed` boolean, required: Whether the recipient unsubscribed through the link in this message. The message stays sent.
          - `warnings` string[]: What could not be applied to this record although it succeeded, such as a group it could not be added to.
        - object
          - `status` 'failed', required
          - `error` BatchRecordError, required: Why this record did not succeed.
            - `code` string, required
            - `message` string, required
            - `documentationUrl` string, uri, required
            - `details` object
  - `deprecations` string[]

## Other responses

- `400`: The request was unacceptable, often due to missing an invalid parameter.
- `401`: No valid API key provided.
- `403`: The API key doesn’t have permissions to perform the request.
- `404`: The requested resource doesn’t exist.
- `409`: A request with the same `Idempotency-Key` is still being processed, or the resource is in a state that does not allow the operation.
- `422`: The request was unacceptable, often due to missing or invalid parameters.
- `429`: Too many requests hit the API too quickly. We recommend an exponential backoff of your requests.
- `500`: Something went wrong on our end.
- `503`: The server is overloaded or down for maintenance.

## Changes

- **2026-11-03** `c6818f3ba60d`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/folk/apis/folk-external-api/changes/v1/campaigns/:campaignId/messages/batch/update/post.md)

---

[API](https://skmtc.dev/folk/apis/folk-external-api.md) - [All operations](https://skmtc.dev/folk/apis/folk-external-api/llms.txt) - [OpenAPI document](https://skmtc.dev/folk/apis/folk-external-api/revisions/c6818f3ba60d?raw)
