Skip to main content

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.

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:
npm install @team-plain/graphql
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, the same as any other reply. This operation requires the thread:reply permission.
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 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:
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.
  • markdown is capped at 5,000 characters.
  • This operation requires the generatedReply:create permission.
See the 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.
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.
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 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:
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:
await plain.mutation.unassignThread({
  input: { threadId: thread.id },
});
A common handoff pattern is to post a note explaining the context, then reassign. See assignment for the full reference.

Other useful operations

These mutations cover most agents. The same PlainClient exposes everything else available in Plain’s API:
ActionMutation
Mark as done or move to todomarkThreadAsDone, markThreadAsTodo
Send a new outbound emailsendNewEmail
Reply to a specific emailreplyToEmail
Set a custom thread fieldupsertThreadField
Create a customer eventcreateCustomerEvent
The GraphQL 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