Skip to content
Plain Help Center home
Plain Help Center home

Using your own agent

This guide explains how to integrate your own AI agent with Plain to automatically respond to customer threads while maintaining clear separation between AI and human agent activity.

Overview

This guide aims to explain how we recommend you can use our API to bring your own Agent into Plain.

Integration

The complete overview of how your agent handles threads will probably look something like the below. We'll guide you through the exact setup steps to achieve this in the next section.

1. Customer reaches out → Plain creates a thread via your email, slack, chat or any other channel integration.

2. Listen to webhooks to trigger your agent

There are a few different webhooks you can use to start your agent flow, depending on your use case:

  • thread.thread_created - Fired when a thread is created, if you you can listen to this webhook if you need logic to decide if to assign your agent

  • thread.thread_assignment_transitioned - This is fired when a thread's assignment changes, this can be useful for when you want to use assignment to filter threads with your own logic, or via a Workflow rule.

  • Channel events (thread.email_received, thread.slack_message_received, etc.)

3. Example automation flow:

  • thread.thread_created webhook is received

    • Here you have the opportunity to handle all threads via your agent, or filter threads the Agent will handle based on some conditions (for example: you could check the thread's tier, and skip handling Enterprise Tier threads)

  • Update thread agent status to In progress (see below for more info) -

    • updateThreadAgentStatus(IN_PROGRESS)

  • Call your agent, with the context from the newly created thread

  • If agent can reply:

    • replyToThread

    • updateThreadAgentStatus(HANDLED)

    • markAsDone

  • If the agent can't reply

    • replyToThread with a message you will hand off

    • updateThreadAgentStatus(HANDED_OFF)

  • Error handling:

    • Generally here we reccomend a flow like:

    • createNote with what happened

    • updateThreadAgentStatus(HANDED_OFF)

    • unassignThread

    • markThreadAsTodo this will mean the thread will go back into the Todo queues for a human to pickup

Setup

Create a machine user

First, create a machine user to represent your AI agent. You can do this from Settings > Machine users & API keys. You can also set a custom avatar to distinguish your agent.

Subscribe to webhooks

You'll want to listen out for events that should require your agent to take action. At a minimum, this will probably look like the following (depending on which channels you have enabled):

  • thread.thread_created

  • thread.thread_status_transitioned

  • Channel events (for message bodies): thread.email_received, thread.slack_message_received, thread.chat_received, etc.

    Complete details can be found in the webhook events documentation.

Responding to threads

Your agent can respond to threads using standard reply mutations for the required channel. For channel agnostic thread replies, you can use the replyToThread mutation:

mutation ReplyToThread($input: ReplyToThreadInput!) { replyToThread(input: $input) { chat { id text } error { message code } } } # Input { "input": { "threadId": "th_01H8H46YPB2S4MAJM382FG9423", "textContent": "The plain text version of your reply goes here.", "markdownContent": "The markdown **version** of your _reply_ goes here." } }

Complete mutation reference can be found here.

Managing agent status

An important step is managing the Agent Status of a thread. This should be done as part of your integration, so that in Plain, we can display threads in the correct views.

Agent status has 3 states, defined below.

Status

When to use

IN_PROGRESS

Your agent is actively working on a thread.

HANDLED

Your agent has successfully resolved the thread.

You should mark the thread as done.

HANDED_OFF

Your agent needs human assistance or couldn't resolve the issue.

Make sure to also unassign the thread, and optionally move it back to TODO.

Only threads that have an agent status of HANDED_OFF will appear in your First Response, Next Response and Investigating queues.

This is intentional so that work your agent is handling is kept out of view, and only threads that need human interaction are visible.

To update Agent status you can use the API like so:

mutation UpdateThreadAgentStatus($input: UpdateThreadAgentStatusInput!) { updateThreadAgentStatus(input: $input) { thread { id agentStatus agentStatusDetail { type ... on AgentStatusDetailHandedOff { reason } } agentStatusUpdatedAt } error { message code } } } # Input { "input": { "threadId": "th_01J9X3Z8M0ABCDEFGHIJK", "agentStatus": "IN_PROGRESS" } }

As Plain keeps threads actively being handled by your agent out of the Todo queues you can use the Agent activity view to see all threads where an agent status has been set.

This can be found by going to Plain → AI → Agent Activity.

Caveats

Automatic handoff behaviour

When a human agent replies to a thread that your AI marked as HANDLED or IN_PROGRESS, Plain automatically moves it to HANDED_OFF status. This signals collaboration between your AI and human agents.

Why this matters: If your AI responds to a thread but the customer replies again, and then a human agent jumps in, the thread will now appear in your Todo queues.