Skip to content

Tutorial: Build an agent that uses XMTP

This quickstart provides a map to building agents that can communicate with humans and other agents in chats built with XMTP.

Building with XMTP gives your agent access to:

  • The most secure, decentralized messaging network
  • The building blocks of AI + money + secure chat
  • Users on apps built with XMTP, including Coinbase Wallet, Convos, and more

Resources to get started

Quickstart video guide

Agent examples

From a simple agent that replies "GM" to one that supports onchain transactions, explore and run a dozen xmtp-agent-examples built with the XMTP Node SDK.

Cursor rules

You can use these Cursor rules to code agents with AI following XMTP development best practices.

llms-full.txt

To make it easier to use AI coding tools to build agents with XMTP, you can find an llms-full.txt file at https://raw.githubusercontent.com/xmtp/docs-xmtp-org/main/llms/llms-full.txt.

This llms-full.txt file includes all XMTP documentation in a single plain text file for easy parsing by AI.

Build an agent

Listen for and send messages

These are the steps to initialize the XMTP listener and send messages.

// import the xmtp sdk
import { Client, type XmtpEnv, type Signer } from "@xmtp/node-sdk";
 
// encryption key, must be consistent across runs
const encryptionKey: Uint8Array = ...;
const signer: Signer = ...;
const env: XmtpEnv = "dev";
 
// create the client
const client = await Client.create(signer, { encryptionKey, env });
// sync the client to get the latest messages
await client.conversations.sync();
 
// listen to all messages
const stream = await client.conversations.streamAllMessages();
for await (const message of stream) {
  // ignore messages from the agent
  if (message?.senderInboxId === client.inboxId) continue;
  // get the conversation by id
  const conversation = await client.conversations.getConversationById(message.conversationId);
  // send a message from the agent
  await conversation.send("gm");
}

To learn more about:

Get the address of a user

Each user has a unique inboxId used to retrieve their associated addresses (identities). One inboxId can have multiple identities, such as EOAs and SCWs.

const inboxState = await client.preferences.inboxStateFromInboxIds([
  message.senderInboxId,
]);
const addressFromInboxId = inboxState[0].identifiers[0].identifier;

To learn more, see View the inbox state.

Support onchain transactions and transaction references

To learn more, see the example xmtp-transactions agent, as well as the Support onchain transactions and Support onchain transaction references (receipts) documentation.

Support attachments

To learn more, see the example xmtp-attachments agent and Support attachments documentation.

Support replies

To learn more, see the example good example for this? agent and Support replies documentation.

Support reactions

To learn more, see the example good example for this? agent and Support reactions documentation.

Observe rate limits

XMTP enforces separate rate limits for read and write operations per client.

To learn more, see Observe rate limits

Handle stream failures

All streaming methods accept a callback as the last argument that will be called when the stream fails. Use this callback to restart the stream.

An example of how to use the callback to restart the stream:

const MAX_RETRIES = 5;
// wait 5 seconds before each retry
const RETRY_INTERVAL = 5000;
 
let retries = MAX_RETRIES;
 
const retry = () => {
  console.log(
    `Retrying in ${RETRY_INTERVAL / 1000}s, ${retries} retries left`,
  );
  if (retries > 0) {
    retries--;
    setTimeout(() => {
      handleStream(client);
    }, RETRY_INTERVAL);
  } else {
    console.log("Max retries reached, ending process");
    process.exit(1);
  }
};
 
const onFail = () => {
  console.log("Stream failed");
  retry();
};
 
const handleStream = async (client) => {
  console.log("Syncing conversations...");
  await client.conversations.sync();
 
  const stream = await client.conversations.streamAllMessages(
    onMessage,
    undefined,
    undefined,
    onFail,
  );
 
  console.log("Waiting for messages...");
 
  for await (const message of stream) {
    // process streammessage
  }
};
 
await handleStream(client);

Follow security best practices

For an agent to function—whether it's answering questions, executing commands, or providing automated responses—it must be able to read the conversation to understand what's being asked, and write messages to respond.

Like any other user, this means your agent holds the cryptographic keys required to decrypt and send messages in the conversation. As an agent developer, it's important to uphold the security of these keys and messages.

Here are some security best practices:

  • Never expose private keys: Use environment variables.
  • Keep messages secure and private: Do not log messages in plaintext. Do not share messages with third parties.
  • Label agents clearly: Clearly identify your agent as an agent and don't have an agent impersonate a human.

Manage agent installations

With XMTP, your agent has an inbox that you use to access its messages. An inbox can have multiple identities associated with it. Your agent's identity has a kind (such as EOA or SCW) and a string, which in the case of an EOA or SCW, is an Ethereum address. All messages associated with your agent's identity flow through the one inbox ID.

When you deploy your agent to a platform, you create an XMTP client for your agent. The client creates an inbox ID and installation ID associated with your agent's identity. Each time you deploy your agent to a new platform, it creates a new installation for the same inbox ID. The installation represents your agent running on that specific platform.

For example, consider deploying your agent to these platforms:

  • Local development: Creates an installation
  • Railway: Creates another installation
  • Production server: Creates another installation

Your agent can have up to 5 active installations before you need to revoke one to add another. Installations only accumulate for agent deployments using the same XMTP network environment, such as local, dev, or production.

For example, if you deploy your agent across these network environments, you will have 3 inboxes, each with 1 installation:

  • Local development: local network
  • Railway: dev network
  • Production server: production network

If you deploy your agent to this same network environment, you have 1 inbox with 3 installations:

  • Local development: production network
  • Railway: production network
  • Production server: production network

Here are some best practices for agent installation management:

Common agent installation scenarios

Deploy to a new platform

When you deploy your agent to a new platform (e.g., from Railway to Fly.io):

  • Your agent creates a new installation for the new platform
  • If you already have 5 installations, you'll need to revoke one first
  • The new installation will start fresh without conversation history

Update an existing deployment

When you redeploy your agent to the same platform:

  • If the platform preserves the database, the same installation continues working
  • If the platform doesn't preserve the database, a new installation is created

Revoke agent installations

When you revoke an agent installation, it can no longer send or receive messages. However, you can still access the local database.

Your agent can still run from any active installations on other deployment platforms.

You can revoke specific installations and even revoke an installation you can't log into.

To learn more, see Manage XMTP inboxes, identities, and installations.

Test and debug an agent

You can test and interact with your agent using xmtp.chat, the official web inbox for developers.

Be sure to point xmtp.chat to the XMTP environment set in your agent's .env file.

You can use this code to get your agent's client information, which provides useful values for testing and debugging:

const inboxState = await client.preferences.inboxState();
const address = inboxState.identifiers[0].identifier;
const inboxId = client.inboxId;
const installationId = client.installationId;
const conversations = await client.conversations.list();
 
console.log(`
XMTP Client:
  • InboxId: ${inboxId}
  • Address: ${address}
  • Conversations: ${conversations.length}
  • Installations: ${inboxState.installations.length}
  • InstallationId: ${installationId}
  • Network: ${process.env.XMTP_ENV}
`);

Deploy an agent

This section covers how to deploy an agent using Railway—a platform many developers prefer for quickly and easily deploying agents. While this tutorial focuses on Railway, you can use any hosting provider that supports Node.js and environment variables.

Alternative platforms include:

  • Heroku
  • Fly.io
  • Render
  • Vercel

Want to contribute a deployment guide for another platform? We welcome pull requests!

1. Create a Railway account

Sign up for an account at Railway if you don't already have one.

2. Start a new project

From your Railway dashboard, click New Project and select Empty Project.

Railway New Project Screen

3. Import your agent's GitHub repository

Click Deploy from GitHub repo and select the repository that contains your agent code.

Import GitHub Repository

4. Configure volume storage

Your XMTP agent will need persistent storage. Add a volume to your container:

  1. Navigate to your service settings.

  2. Select the Volumes tab.

  3. Add a new volume and specify the mount path.

    Adding a Volume

Use this code in your agent to properly connect to the Railway volume:

export const getDbPath = (env: string, suffix: string = "xmtp") => {
  //Checks if the environment is a Railway deployment
  const volumePath = process.env.RAILWAY_VOLUME_MOUNT_PATH ?? ".data/xmtp";
  // Create database directory if it doesn't exist
  if (!fs.existsSync(volumePath)) {
    fs.mkdirSync(volumePath, { recursive: true });
  }
  const dbPath = `${volumePath}/${env}-${suffix}.db3`;
 
  return dbPath;
};

Then, specify dbPath in your client options:

const receiverClient = await Client.create(signer, {
    dbEncryptionKey,
    env: XMTP_ENV as XmtpEnv,
    dbPath: getDbPath(XMTP_ENV),
  });

5. Configure environment variables

  1. Get the connection string for your database.

    Get Redis Connection String

  2. Add the connection string and any other required environment variables to your service.

    Environment Variables Editor

6. Deploy your agent

Once all configurations are set, Railway will automatically deploy your agent. You can monitor the deployment process on the Deployments tab.

7. Share your agent (optional)

Consider registering an ENS domain for your agent to make it easy to share and access.

Example Railway deployment

For reference, here's an example Railway deployment of a gm-bot agent.