> ## Documentation Index
> Fetch the complete documentation index at: https://www.plain.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Acting on threads

> The actions your agent can take on a thread.

Once your agent has decided to act on a thread, it uses the GraphQL API to do something. The most common actions are:

* **Reply directly** to the customer.
* **Suggest a reply** for a teammate to review and send.
* **Post a note** that's visible to your team but never the customer.
* **Add labels** to classify or flag the thread.
* **Change the assignee** to route the thread or hand it off to a human.

This page walks through each one. Everything happens through the [GraphQL SDK](/graphql/sdk):

```bash theme={null}
npm install @team-plain/graphql
```

```ts theme={null}
import { PlainClient } from "@team-plain/graphql";

const plain = new PlainClient({ apiKey: process.env.PLAIN_API_KEY! });
```

## Reply directly

The mutation for sending a reply is `replyToThread`. It works on threads whose channel is `API`, `CHAT`, `EMAIL`, `SLACK`, or `MS_TEAMS`, and Plain takes care of delivering the message through the right channel. Replies show up in the thread as messages from the agent's [machine user](/agents/machine-users), the same as any other reply.

This operation requires the `thread:reply` permission.

```ts theme={null}
const result = await plain.mutation.replyToThread({
  input: {
    threadId: thread.id,
    textContent: "Thanks for reaching out, let me look into this.",
    markdownContent: "Thanks for reaching out, let me look into this.",
  },
});

if (result.error) {
  console.error(result.error.message);
}
```

Always provide both `textContent` and `markdownContent`. `textContent` is shown in clients that don't render markdown (some email clients, plain-text contexts), and `markdownContent` is rendered in the Plain UI, the chat widget, and modern email clients.

See the [reply to thread](/graphql/messaging/reply-to-thread) page for the full mutation reference, error codes, and channel-specific options.

## Suggest a reply

Instead of sending immediately, your agent can add a reply to the thread as a **suggestion**. The suggestion shows up in Plain attached to a specific customer message, and a teammate can review it, edit it, and send it (or discard it) from the UI. The customer doesn't see anything until the teammate sends.

This is a great default while you're tuning an agent. You get all the value of the agent drafting replies without committing to autonomous send.

The mutation is `addGeneratedReply`. It takes the thread ID, the `timelineEntryId` of the customer message the reply is for, and the suggested content as markdown:

```ts theme={null}
const result = await plain.mutation.addGeneratedReply({
  input: {
    threadId: thread.id,
    timelineEntryId: customerMessage.id,
    markdown: "Hi! You can reset your password under **Settings → Security**.",
  },
});

if (result.error) {
  console.error(result.error.message);
}
```

A few things to know:

* The `timelineEntryId` must point at a customer message. You can get one from the webhook payload (for example `payload.email.timelineEntryId` on `thread.email_received`) or by paginating [thread timeline entries](/agents/reading-threads).
* `markdown` is capped at 5,000 characters.
* This operation requires the `generatedReply:create` permission.

See the [suggested replies](/graphql/messaging/suggested-replies) page for the full reference.

## Post a note

A note is an internal message that lives on the thread's timeline but is never delivered to the customer. Notes are useful for leaving context for the next teammate who picks up the thread, recording why the agent did (or didn't) act, or attaching extra information you don't want to show the customer.

```ts theme={null}
await plain.mutation.createNote({
  input: {
    customerId: thread.customer.id,
    threadId: thread.id,
    text: "Customer asked for a refund. Confidence: low. Escalating.",
    markdown: "Customer asked for a refund. **Confidence: low.** Escalating.",
  },
});
```

## Add labels

Labels are useful for classifying threads, flagging them for review, or driving downstream automation (workflows, reporting, or routing rules). A classifier agent that just labels each new thread is one of the simplest agents you can build.

You add labels by referencing existing **label types**, which you create in Plain under **Settings → Labels**.

```ts theme={null}
await plain.mutation.addLabels({
  input: {
    threadId: thread.id,
    labelTypeIds: ["lt_01HB8BTNTZ58730MX8H5VMKFD5"],
  },
});
```

To remove labels, use `removeLabels` with the IDs of the labels (not label types) you want to remove. See [labels](/graphql/labels/add) for the full reference.

## Change the assignee

Reassigning a thread is how your agent hands off to (or accepts handoffs from) the rest of your team. The mutation is `assignThread`, and `AssignThreadInput` accepts either a `userId` or a `machineUserId`:

```ts theme={null}
await plain.mutation.assignThread({
  input: {
    threadId: thread.id,
    userId: "u_01FSVKMHFDHJ3H5XFM20EMCBQN", // a teammate or on-call user
  },
});
```

Or unassign the thread and let your workflows or on-call rotation route it from there:

```ts theme={null}
await plain.mutation.unassignThread({
  input: { threadId: thread.id },
});
```

A common handoff pattern is to post a note explaining the context, then reassign. See [assignment](/graphql/threads/assignment) for the full reference.

## Other useful operations

These mutations cover most agents. The same `PlainClient` exposes everything else available in Plain's API:

| Action                       | Mutation                                                                       |
| ---------------------------- | ------------------------------------------------------------------------------ |
| Mark as done or move to todo | [`markThreadAsDone`](/graphql/threads), [`markThreadAsTodo`](/graphql/threads) |
| Send a new outbound email    | [`sendNewEmail`](/graphql/messaging/send-email)                                |
| Reply to a specific email    | [`replyToEmail`](/graphql/messaging/reply-email)                               |
| Set a custom thread field    | [`upsertThreadField`](/graphql/threads/thread-fields)                          |
| Create a customer event      | [`createCustomerEvent`](/graphql/events/create-customer-event)                 |

The [GraphQL API explorer](https://app.plain.com/developer/api-explorer/) is the fastest way to discover what's available and try it out.

If your agent attempts a mutation without sufficient permissions, the response error will tell you which permission is missing. Add it to your machine user's API key and try again.

## Resources

* [GraphQL SDK](/graphql/sdk): the typed client your agent calls
* [Reply to threads](/graphql/messaging/reply-to-thread): the full `replyToThread` reference
* [Suggested replies](/graphql/messaging/suggested-replies): the full `addGeneratedReply` reference
* [Assignment](/graphql/threads/assignment): assigning and unassigning threads
* [Labels](/graphql/labels/add): adding and removing labels
* [API explorer](https://app.plain.com/developer/api-explorer/): browse and test queries interactively
