# Update draft forward

> PUT /v1/forwards/{id} · Core resources API

Source: https://developer.kustomer.com/api/core-resources/update-draft-forward

```http
PUT https://{orgname}.api.kustomerapp.com/v1/forwards/{id}
Authorization: Bearer <API key>
```

## Authorization

Any API key or signed-in user. No specific role is required; any valid credential works.

Updates a draft forward.

## About Drafts

A draft is an outbound message that hasn't been sent yet. Unlike creating a message, sending a draft makes Kustomer deliver it to the customer through a connected channel such as email, SMS, chat or WhatsApp.

A draft is what an agent is composing in the reply box before they press send, and it's also how integrations send real messages. [Creating a message](https://developer.kustomer.com/api/core-resources#messages) only records something on the timeline; creating and sending a draft makes Kustomer deliver it through the channel and account you pick. Once a draft is sent, Kustomer removes it and records the delivered message as an `out` message on the conversation. Integrations use drafts to prepare replies for an agent to review, such as an AI-suggested answer, or to send notifications and follow-ups on a schedule.

#### Channels

Every draft is created for a [customer](https://developer.kustomer.com/api/core-resources#customers) and has a `channel`: `email`, `sms`, `chat`, `whatsapp`, `facebook`, `instagram`, `twitter-dm`, `twitter-tweet` or `note`. The channel decides which fields apply and the `body` limit, which ranges from 280 characters for a tweet to 10,240 for email, chat and notes. Email drafts take `to`, `from`, `cc`, `bcc`, `subject` and an optional `template`; `app` picks the sending integration, such as `gmail` or `postmark` for email or `twilio` for SMS. The sender (`from`) should be one of your org's connected accounts, which [`GET /v1/outboundAccounts`](https://developer.kustomer.com/api/core-resources/get-outbound-accounts) lists for every channel. Pass `conversation` to reply in an existing conversation.

A `note` draft is an unsent internal [note](https://developer.kustomer.com/api/core-resources#notes), and a conversation can have only one. Add `createConversation=true` when creating a note draft to open a new conversation for it.

#### Sending and scheduling

| `status` | Meaning |
| --- | --- |
| `draft` | Saved, not sent. |
| `scheduled` | Waiting for its `sendAt` time. |
| `error` | Sending failed; see `error`. |

`sendAt` controls sending. `"sendAt": "now"` sends the draft right away. To schedule it, send a future timestamp together with `scheduled: true`; scheduling works up to 30 days ahead. A future timestamp without `scheduled: true` is treated as now, so the draft goes out immediately. Leaving `sendAt` out keeps it a draft. On `PUT /v1/drafts/{id}`, an empty `sendAt` unschedules a scheduled draft and returns it to `draft`.

Scheduled drafts are locked. Editing one for any other reason, or deleting it, fails with `409 Conflict` unless you pass `force=true`.

#### Which endpoint to use

| To | Call |
| --- | --- |
| Create a draft, or send a message right away | [`POST /v1/customers/{id}/drafts`](https://developer.kustomer.com/api/core-resources/create-draft-for-customer) |
| List a customer's unsent drafts | [`GET /v1/customers/{id}/drafts`](https://developer.kustomer.com/api/core-resources/get-all-drafts-for-customer) |
| Edit, send, schedule or unschedule a draft | [`PUT /v1/drafts/{id}`](https://developer.kustomer.com/api/core-resources/update-draft) |
| Discard a draft | [`DELETE /v1/drafts/{id}`](https://developer.kustomer.com/api/core-resources/delete-draft) |
| Find the accounts you can send from | [`GET /v1/outboundAccounts`](https://developer.kustomer.com/api/core-resources/get-outbound-accounts) |
| Read or edit a draft [forward](https://developer.kustomer.com/api/core-resources#conversations-forwards) | [`GET`](https://developer.kustomer.com/api/core-resources/get-draft-forward-by-id) or [`PUT /v1/forwards/{id}`](https://developer.kustomer.com/api/core-resources/update-draft-forward) |

A client can create 120 drafts per customer per minute, including scheduled ones, and WhatsApp drafts have their own per-user limit (see [Rate limits](https://developer.kustomer.com/docs/concepts/rate-limits)). WhatsApp media must be one of the supported file types listed on the create endpoint. `DELETE` with `deleteConversation=true` also removes the conversation, but only if it has no messages, notes or other drafts and was created by the same user. Files go on drafts through the [draft attachment endpoints](https://developer.kustomer.com/api/core-resources#drafts-attachments).

Related: [Messages](https://developer.kustomer.com/api/core-resources#messages), [Conversation forwards](https://developer.kustomer.com/api/core-resources#conversations-forwards), [Draft attachments](https://developer.kustomer.com/api/core-resources#drafts-attachments), [Conversations and messages](https://developer.kustomer.com/docs/concepts/conversations-and-messages)

### Recipes

#### 1. Send an email reply through Kustomer

Have Kustomer deliver an email to a customer from one of your support addresses, threaded into their conversation, for example from an order system or an AI agent.

**Step 1.** [`GET /v1/outboundAccounts`](https://developer.kustomer.com/api/core-resources/get-outbound-accounts.md). Find the address to send from. Each account has a `channel` and an `account`, which for email is the address. The sender you use must be one of these.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/outboundAccounts" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/outboundAccounts \
  | jq -r '.data[] | select(.attributes.channel == "email") | [.attributes.account, .attributes.name] | @tsv'
```

**Step 2.** [`GET /v1/customers/{id}`](https://developer.kustomer.com/api/core-resources/get-customer-by-id.md). Read the customer's `emails` to choose the recipient.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/customers/$CUSTOMER_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/customers/$CUSTOMER_ID | jq -r '.data.attributes.emails[0].email'
```

**Step 3.** [`POST /v1/customers/{id}/drafts`](https://developer.kustomer.com/api/core-resources/create-draft-for-customer.md). Create an email draft in the conversation with `"sendAt": "now"`, which sends it straight away. `to` and `from` are `{ email, name }` objects. `app` picks the sending integration, `postmark` or `gmail`; use the one your sending address is connected through. Once it's sent, Kustomer removes the draft and adds the delivered email to the conversation as an `out` message.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/customers/$CUSTOMER_ID/drafts" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"channel\": \"email\", \"app\": \"postmark\", \"conversation\": \"$CONVERSATION_ID\", \"from\": {\"email\": \"$FROM_EMAIL\", \"name\": \"Acme Support\"}, \"to\": [{\"email\": \"$TO_EMAIL\"}], \"subject\": \"Re: Return request for order 4417\", \"body\": \"Your refund for order 4417 has been issued.\", \"sendAt\": \"now\"}"
```

```bash title="kusty"
kusty api POST /v1/customers/$CUSTOMER_ID/drafts \
  --field channel=email --field app=postmark --field conversation=$CONVERSATION_ID \
  --field from.email=$FROM_EMAIL --field from.name="Acme Support" \
  --field to.0.email=$TO_EMAIL --field subject="Re: Return request for order 4417" \
  --field body="Your refund for order 4417 has been issued." --field sendAt=now
```

> For SMS, send `"channel": "sms"` with `to` and `from` as phone-number strings and a `body` of up to 1,600 characters. A client can create 120 drafts per customer per minute. Creating a [message](https://developer.kustomer.com/api/core-resources#messages) instead would only record the email; it would never reach the customer.

#### 2. Leave a suggested reply for an agent to review

Put a prepared answer, such as an AI-generated reply, in the conversation as an unsent draft so an agent can edit it and press send.

**Step 1.** [`POST /v1/customers/{id}/drafts`](https://developer.kustomer.com/api/core-resources/create-draft-for-customer.md). Create the draft in the conversation without `sendAt`, so it stays a `draft`. Use the channel the conversation is on, such as `chat`. Keep the draft's `data.id`.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/customers/$CUSTOMER_ID/drafts" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"channel\": \"chat\", \"conversation\": \"$CONVERSATION_ID\", \"body\": \"$SUGGESTED_REPLY\"}"
```

```bash title="kusty"
kusty api POST /v1/customers/$CUSTOMER_ID/drafts \
  --field channel=chat --field conversation=$CONVERSATION_ID --field body="$SUGGESTED_REPLY" \
  | jq -r .data.id
```

**Step 2.** [`GET /v1/customers/{id}/drafts`](https://developer.kustomer.com/api/core-resources/get-all-drafts-for-customer.md). Later, list the customer's drafts to see whether yours is still unsent. Drafts that were sent are gone from the list.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/customers/$CUSTOMER_ID/drafts" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/customers/$CUSTOMER_ID/drafts --all \
  | jq -r '[.data.id, .data.attributes.status, .data.relationships.conversation.data.id] | @tsv'
```

**Step 3.** [`PUT /v1/drafts/{id}`](https://developer.kustomer.com/api/core-resources/update-draft.md). If the customer writes again before the agent sends it, replace the suggestion. `channel` is required on every update; leaving `sendAt` out keeps the draft unsent.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/drafts/$DRAFT_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"channel\": \"chat\", \"body\": \"$NEW_SUGGESTION\"}"
```

```bash title="kusty"
kusty api PUT /v1/drafts/$DRAFT_ID --field channel=chat --field body="$NEW_SUGGESTION"
```

> To withdraw a suggestion, [delete the draft](https://developer.kustomer.com/api/core-resources/delete-draft). For an internal hint rather than a reply, use `"channel": "note"`; a conversation can have only one note draft.

#### 3. Schedule a follow-up and cancel it if the customer replies

Queue a check-in email for a few days from now, and take it back if the customer answers first.

**Step 1.** [`POST /v1/customers/{id}/drafts`](https://developer.kustomer.com/api/core-resources/create-draft-for-customer.md). Create the draft with a future `sendAt` and `"scheduled": true`, up to 30 days ahead. Without `scheduled: true` a future time is treated as now and the email goes out immediately. The draft's `status` becomes `scheduled`.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/customers/$CUSTOMER_ID/drafts" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"channel\": \"email\", \"app\": \"postmark\", \"conversation\": \"$CONVERSATION_ID\", \"from\": {\"email\": \"$FROM_EMAIL\"}, \"to\": [{\"email\": \"$TO_EMAIL\"}], \"subject\": \"Checking in on your return\", \"body\": \"Did the replacement arrive safely?\", \"sendAt\": \"$SEND_AT\", \"scheduled\": true}"
```

```bash title="kusty"
kusty api POST /v1/customers/$CUSTOMER_ID/drafts \
  --field channel=email --field app=postmark --field conversation=$CONVERSATION_ID \
  --field from.email=$FROM_EMAIL --field to.0.email=$TO_EMAIL \
  --field subject="Checking in on your return" --field body="Did the replacement arrive safely?" \
  --field sendAt=$SEND_AT --field scheduled:=true \
  | jq -r .data.id
```

**Step 2.** [`PUT /v1/drafts/{id}`](https://developer.kustomer.com/api/core-resources/update-draft.md). When the customer replies (for example on a `kustomer.message.create` webhook with `direction: "in"`), unschedule the draft by sending an empty `sendAt`. It goes back to `draft` and nothing is sent.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/drafts/$DRAFT_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"channel": "email", "sendAt": ""}'
```

```bash title="kusty"
kusty api PUT /v1/drafts/$DRAFT_ID --field channel=email --field sendAt=
```

**Step 3.** [`DELETE /v1/drafts/{id}`](https://developer.kustomer.com/api/core-resources/delete-draft.md). Or discard it outright. Scheduled drafts are locked, so deleting or otherwise editing one fails with `409 Conflict` unless you pass `force=true`.

```bash title="cURL"
curl --request DELETE "https://api.kustomerapp.com/v1/drafts/$DRAFT_ID?force=true" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api DELETE /v1/drafts/$DRAFT_ID --query force=true --confirm --yes
```

### Example object

A real record from [`GET /v1/customers/{id}/drafts`](https://developer.kustomer.com/api/core-resources/get-all-drafts-for-customer.md):

```json
{
  "id": "58210c6c2a8b9910002f6730",
  "type": "draft",
  "attributes": {
    "channel": "chat",
    "body": "",
    "payload": {
      "draftJs": {
        "blocks": [
          {
            "data": {},
            "entityRanges": [],
            "inlineStyleRanges": [],
            "depth": 0,
            "type": "unstyled",
            "text": "",
            "key": "dv926"
          }
        ],
        "entityMap": {}
      }
    },
    "status": "draft",
    "createdAt": "2016-11-07T23:21:16.945Z",
    "updatedAt": "2016-11-08T18:49:32.730Z",
    "modifiedAt": "2016-11-08T18:49:32.730Z"
  },
  "relationships": {
    "org": {
      "data": {
        "type": "org",
        "id": "57f29863a1dbf61100e6aa92"
      },
      "links": {
        "self": "/v1/orgs/57f29863a1dbf61100e6aa92"
      }
    },
    "customer": {
      "data": {
        "type": "customer",
        "id": "58210c3db0f09110006b7953"
      },
      "links": {
        "self": "/v1/customers/58210c3db0f09110006b7953"
      }
    },
    "createdBy": {
      "data": {
        "type": "user",
        "id": "57fd2d309d0ef811009cfdd6"
      },
      "links": {
        "self": "/v1/users/57fd2d309d0ef811009cfdd6"
      }
    },
    "modifiedBy": {
      "data": {
        "type": "user",
        "id": "58190b991f2932100010d683"
      },
      "links": {
        "self": "/v1/users/58190b991f2932100010d683"
      }
    },
    "conversation": {
      "data": {
        "type": "conversation",
        "id": "58210c3d728107110018662f"
      },
      "links": {
        "self": "/v1/conversations/58210c3d728107110018662f"
      }
    }
  }
}
```

Fields:

- `attributes.channel` (string): The channel the draft will be sent on: `email`, `sms`, `chat`, `whatsapp`, `facebook`, `instagram`, `twitter-dm`, `twitter-tweet`, or `note` for an unsent internal note. It decides which other fields apply, and you must send it on every `PUT`.
- `attributes.body` (string): The text to send. The limit depends on the channel, from 280 characters for a tweet to 10,240 for email, chat and notes. Empty while an agent hasn't typed anything yet, as here.
- `attributes.payload` (object): Rich-text editor state saved by the agent desktop while an agent composes the draft. Integrations can leave it out and send `body`.
- `attributes.payload.draftJs` (object): The editor's content blocks. Their `text` mirrors `body`.
- `attributes.status` (string): `draft` while it's saved but not sent, `scheduled` while it waits for its `sendAt` time, or `error` when sending failed. Once a draft is sent, Kustomer removes it and records an `out` message.
- `relationships.customer` (object): The customer the draft is addressed to. Drafts are always created for a customer.
- `relationships.createdBy` (object): The user or API key that started the draft.
- `relationships.conversation` (object): The conversation the reply goes into. Set it with `conversation` on create to reply in an existing conversation; for a note draft, `createConversation=true` opens a new one instead.

### Relationships

- Points to **Customers** via `relationships.customer`: The customer a draft will be sent to. Every draft is created for one customer, who can have several unsent drafts.
- Points to **Conversations** via `relationships.conversation`: The conversation a draft replies in. When it's sent, the delivered message is added to this conversation. A conversation can have at most one note draft.
- Points to **Attachments** via `relationships.attachments` (list): Files attached to a draft. Create them with `POST /v1/drafts/{id}/attachments` and upload each file to its signed URL before sending.

### Permissions

Fewest roles for a read-only integration: `org.permission.apps.read`, `org.permission.draft.read`. For every endpoint: `org.permission.apps.read`, `org.permission.draft.read`.

### Events

Workflow triggers:

- `kustomer.draft.create` (Draft Created): An agent starts a draft

Workflow actions:

- `kustomer.draft.internal-message-create` (Draft: Internal message): required inputs `customer`, `conversation`

## Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Forward ID. |

## Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `force` | boolean | no | Force the draft update or delete when supported by the draft service. |

## Request body

Content type: `application/json` (required)

- `status` (string): One of: `draft`, `sent`, `failed`.
- `sendAt` (string<date-time-math>): A timestamp for a future date/time the drafted message to be sent at.
- `lang` (string, nullable): (max length 100)
- `to` (array<object>)
  - `email` (string<email-relaxed>, required): (min length 3; max length 256)
  - `name` (string): (min length 1)
- `from` (object)
  - `email` (string<email-relaxed>, required): (min length 3; max length 256)
  - `name` (string): (min length 1)
- `body` (string): (max length 10240)
- `subject` (string): (min length 1)
- `replyTo` (string<email-relaxed>): (min length 3; max length 256)
- `template` (string<object-id>, nullable)
- `payload` (object)
- `attachments` (array<object>)
  - `id` (string<object-id>, required)
  - `name` (string, required)
  - `contentType` (string, required)
  - `contentLength` (number, required)

Example:

```json
{
  "status": "draft",
  "sendAt": "string",
  "lang": "string",
  "to": [
    {
      "email": "jane.doe@example.com",
      "name": "example_name"
    }
  ],
  "body": "Example text",
  "subject": "Example title"
}
```

## Responses

### 200

OK

- `data` (object, required)
  - `id` (string, required)
  - `type` (string, required)
  - `attributes` (object, required)
    - `app` (string, required)
    - `channel` (string, required)
    - `body` (string, required)
    - `status` (string, required)
    - `from` (object, required)
    - `to` (array<object>, required)
    - `sendAt` (string, required): A timestamp for a future date/time the drafted message to be sent at.
    - `createdAt` (string, required)
    - `updatedAt` (string, required)
    - `modifiedAt` (string, required)
  - `relationships` (object, required)
    - `org` (object, required)
    - `customer` (object, required)
    - `createdBy` (object, required)
    - `modifiedBy` (object, required)
    - `attachments` (array<object>)

```json
{
  "data": {
    "id": "5823e4d3bff98a100085320e",
    "type": "forward",
    "attributes": {
      "app": "gmail",
      "channel": "email",
      "body": "A great response",
      "status": "scheduled",
      "from": {
        "name": "Support",
        "email": "support@example.com"
      },
      "to": [
        {
          "name": "Billing",
          "email": "billing@email.com"
        }
      ],
      "sendAt": "2019-11-10T03:09:07.575Z",
      "createdAt": "2016-11-10T03:09:07.575Z",
      "updatedAt": "2016-11-10T03:16:55.464Z",
      "modifiedAt": "2016-11-10T03:16:55.464Z"
    },
    "relationships": {
      "org": {
        "data": {
          "type": "org",
          "id": "57f29863a1dbf61100e6aa92"
        },
        "links": {
          "self": "/v1/orgs/57f29863a1dbf61100e6aa92"
        }
      },
      "customer": {
        "data": {
          "type": "customer",
          "id": "58210c3db0f09110006b7953"
        },
        "links": {
          "self": "/v1/customers/58210c3db0f09110006b7953"
        }
      },
      "createdBy": {
        "data": {
          "type": "user",
          "id": "58190b991f2932100010d683"
        },
        "links": {
          "self": "/v1/users/58190b991f2932100010d683"
        }
      },
      "modifiedBy": {
        "data": {
          "type": "user",
          "id": "58190b991f2932100010d683"
        },
        "links": {
          "self": "/v1/users/58190b991f2932100010d683"
        }
      },
      "attachments": {
        "data": [
          {
            "type": "attachment",
            "id": "58c766aadd4b90110053bd81"
          }
        ],
        "links": [
          {
            "self": "/v1/forwards/5823e4d3bff98a100085320e/attachments"
          }
        ]
      }
    }
  }
}
```

### 400

Bad Request

### 401

Unauthorized

### 403

Forbidden

### 404

Not Found

## Example request

```bash
curl --request PUT \
  --url 'https://api.kustomerapp.com/v1/forwards/60254e3ec413ae00571e3c99' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "status": "draft",
  "sendAt": "string",
  "lang": "string",
  "to": [
    {
      "email": "jane.doe@example.com",
      "name": "example_name"
    }
  ],
  "body": "Example text",
  "subject": "Example title"
}'
```
