> ## 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.

# Building Agents on Plain

Plain is designed to be a great home for agents. You can build your own customer support agent using whatever AI stack you prefer and have it work alongside your team in the same threads, with the same tools, and the same audit trail as everyone else.

These docs are about the **plumbing** of building an agent on Plain: how it gets an identity, how it receives events, how it decides when to act, and how it replies. The AI part (e.g. model choice, prompts, RAG, tool use) is up to you.

<Note>
  If you just want to chat about support data with a local agent, our [MCP server](https://plain.support.site/article/mcp-server) is the fastest way to get a model talking to Plain. This guide is about building autonomous agents within Plain.
</Note>

## What an agent looks like in Plain

An agent in Plain is made of three pieces:

<Card title="A machine user" icon="user" href="/agents/machine-users">
  The agent's identity in Plain. Has a public name, an avatar, and one or more API keys.
</Card>

<Card title="A webhook listener" icon="webhook" href="/agents/receiving-events">
  A public HTTPS endpoint that receives Plain events like new threads, incoming messages, and assignment changes.
</Card>

<Card title="A GraphQL client" icon="code" href="/graphql/sdk">
  Makes calls back to Plain to read threads, reply, change assignment, add labels, create notes, and more.
</Card>

When something happens in Plain (a customer sends an email, a teammate assigns a thread), Plain delivers a webhook to your endpoint. Your code decides whether the agent should act and uses the GraphQL API to do whatever it does: read context, reply, label, summarise, hand off, post a note, anything else.

Agents come in many shapes. Some reply to every new thread; some only act when assigned; some never reply at all and just classify, summarise, or post internal notes. The building blocks below are the same regardless of what your agent does.

## The journey

<Steps>
  <Step title="Create a machine user">
    A machine user is the agent's identity in Plain. Give it a public name (what customers see) and create an API key with the right permissions.

    [Set up a machine user →](/agents/machine-users)
  </Step>

  <Step title="Stand up a webhook endpoint">
    Plain delivers events as HTTP POST requests. Use the [`@team-plain/webhooks`](/webhooks/sdk) package to verify and parse them with full type safety.

    [Receive events from Plain →](/agents/receiving-events)
  </Step>

  <Step title="Decide when your agent should act">
    You can filter events in code, or use Plain's built-in [workflows](/agents/routing) to assign specific threads to the machine user and only react to those.

    [Route threads to your agent →](/agents/routing)
  </Step>

  <Step title="Read the thread">
    If your agent needs the conversation context, query the thread and pull its content as LLM-ready text.

    [Read thread content →](/agents/reading-threads)
  </Step>

  <Step title="Search your knowledge">
    If your agent answers questions, search your help center articles and indexed documents to ground its replies in real content.

    [Search knowledge sources →](/agents/searching-knowledge)
  </Step>

  <Step title="Act on the thread">
    Use the [`@team-plain/graphql`](/graphql/sdk) SDK to do whatever your agent does: reply, label, change status, create a note, hand off to a human, or anything else.

    [Act on the thread →](/agents/acting-on-threads)
  </Step>
</Steps>

## A minimal example

Here's one possible shape: an agent that replies to every new thread. It uses [Express](https://expressjs.com/), but the same shape works with any HTTP framework. Your own agent will likely do something different in the handler.

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

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

const app = express();
app.use(express.text({ type: "*/*" })); // we need the raw body for signature verification

app.post("/webhooks/plain", async (req, res) => {
  const result = verifyPlainWebhook(
    req.body,
    req.header("plain-request-signature")!,
    process.env.PLAIN_WEBHOOK_SECRET!,
  );

  if (result.error) {
    return res.status(400).send(result.error.message);
  }

  const event = result.data;

  if (event.payload.eventType === "thread.thread_created") {
    const reply = await generateReply(event.payload.thread); // your AI goes here

    await plain.mutation.replyToThread({
      input: {
        threadId: event.payload.thread.id,
        textContent: reply,
      },
    });
  }

  res.sendStatus(200);
});

app.listen(3000);
```

Replace `generateReply` with whatever AI library you prefer, like the [Vercel AI SDK](https://ai-sdk.dev/), [Anthropic SDK](https://docs.claude.com/en/api/getting-started), [OpenAI SDK](https://platform.openai.com/docs/libraries), or your own.

## What's next

<CardGroup cols={2}>
  <Card title="Machine users" icon="user" href="/agents/machine-users">
    Create the agent's identity and API key.
  </Card>

  <Card title="Receiving events" icon="webhook" href="/agents/receiving-events">
    Listen for the events that matter to your agent.
  </Card>

  <Card title="Routing to your agent" icon="route" href="/agents/routing">
    Use workflows and assignment to control when the agent acts.
  </Card>

  <Card title="Reading threads" icon="book-open" href="/agents/reading-threads">
    Fetch thread context as LLM-ready text.
  </Card>

  <Card title="Searching knowledge" icon="magnifying-glass" href="/agents/searching-knowledge">
    Ground replies in your help center and indexed documents.
  </Card>

  <Card title="Acting on threads" icon="message-circle" href="/agents/acting-on-threads">
    Reply, suggest replies, post notes, add labels, and reassign threads.
  </Card>
</CardGroup>
