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 agentthread.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_createdwebhook is receivedHere 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:
replyToThreadupdateThreadAgentStatus(HANDLED)markAsDone
If the agent can't reply
replyToThreadwith a message you will hand offupdateThreadAgentStatus(HANDED_OFF)
Error handling:
Generally here we reccomend a flow like:
createNotewith what happenedupdateThreadAgentStatus(HANDED_OFF)unassignThreadmarkThreadAsTodothis 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_createdthread.thread_status_transitionedChannel 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 |
|---|---|
| Your agent is actively working on a thread. |
| Your agent has successfully resolved the thread. You should mark the thread as done. |
| 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.