Skip to content

Context and helpers

Every XMTP agent event handler receives a rich MessageContext object that provides access to the message, conversation, client, and powerful helper methods. This context makes it easy to build responsive agents without repetitive boilerplate code.

MessageContext overview

The MessageContext object contains everything you need to handle messages effectively:

interface MessageContext {
  message: DecodedMessage; // The decoded message object
  conversation: Conversation; // The active conversation
  client: Client; // The underlying XMTP client
 
  // Helper methods
  sendTextReply(text: string): Promise<void>;
  sendReaction(emoji: string): Promise<void>;
  getSenderAddress(): string;
  // ... and more
}

Message object

The message property contains the decoded message with all its metadata:

agent.on('text', async (ctx) => {
  const message = ctx.message;
 
  console.log('Content:', message.content);
  console.log('Sender:', message.senderInboxId);
  console.log('Sent at:', message.sentAt);
  console.log('Message ID:', message.id);
  console.log('Conversation ID:', message.conversationId);
  console.log('Content type:', message.contentType);
});

Access different content types

// Text messages
agent.on('text', async (ctx) => {
  const textContent: string = ctx.message.content;
  console.log('User said:', textContent);
});
 
// Reactions
agent.on('reaction', async (ctx) => {
  const reaction = ctx.message.content;
  console.log('Reaction:', reaction.content);
  console.log('Reference:', reaction.reference);
});
 
// Replies
agent.on('reply', async (ctx) => {
  const reply = ctx.message.content;
  console.log('Reply text:', reply.content);
  console.log('Original message:', reply.reference);
});

Conversation object

The conversation property provides access to the current conversation and its methods:

agent.on('text', async (ctx) => {
  const conversation = ctx.conversation;
 
  console.log('Conversation ID:', conversation.id);
 
  // Send a message
  await ctx.sendText('Hello from the conversation!');
 
  // Get conversation members
  const members = await ctx.conversation.members();
  console.log('Member count:', members.length);
 
  // Check conversation type
  if (conversation.peerInboxId) {
    console.log('This is a DM with:', conversation.peerInboxId);
  } else {
    console.log('This is a group conversation');
  }
});

Client object

The client property gives you access to the underlying XMTP client:

agent.on('text', async (ctx) => {
  const client = ctx.client;
 
  console.log('Agent inbox ID:', client.inboxId);
  console.log('Installation ID:', client.installationId);
 
  // Access conversations manager
  const conversations = client.conversations;
 
  // Create new conversations
  const newDM = await client.conversations.newDm('target-inbox-id');
  const newGroup = await client.conversations.newGroup(['inbox1', 'inbox2'], {
    groupName: 'New Group',
  });
});

Helper methods

The context provides convenient helper methods for common operations:

sendTextReply()

Send a text reply in one line:

agent.on('text', async (ctx) => {
  await ctx.sendTextReply('Thanks for your message!');
 
  // Equivalent to:
  // await ctx.sendText("Thanks for your message!");
});

sendReaction()

Add reactions to messages easily:

agent.on('text', async (ctx) => {
  const content = ctx.message.content.toLowerCase();
 
  if (content.includes('good')) {
    await ctx.sendReaction('👍');
  } else if (content.includes('bad')) {
    await ctx.sendReaction('👎');
  } else {
    await ctx.sendReaction('🤔');
  }
});

getSenderAddress()

Get the Ethereum address of the message sender:

agent.on('text', async (ctx) => {
  const senderAddress = ctx.getSenderAddress();
  console.log('Message from:', senderAddress);
 
  // Use for authorization
  if (senderAddress === '0x1234...') {
    await ctx.sendTextReply('Hello, admin!');
  }
});

Advanced context usage

Member information

Get detailed information about conversation members:

agent.on('text', async (ctx) => {
  const members = await ctx.conversation.members();
 
  for (const member of members) {
    console.log('Member inbox ID:', member.inboxId);
    console.log('Permission level:', member.permissionLevel);
    console.log('Consent state:', member.consentState);
 
    // Get Ethereum address
    const ethIdentifier = member.accountIdentifiers.find(
      (id) => id.identifierKind === IdentifierKind.Ethereum
    );
 
    if (ethIdentifier) {
      console.log('Ethereum address:', ethIdentifier.identifier);
    }
  }
});

Message history

Access conversation message history:

agent.on('text', async (ctx) => {
  // Get all messages
  const messages = await ctx.conversation.messages();
  console.log('Total messages:', messages.length);
 
  // Get recent messages
  const recentMessages = messages.slice(-10); // Last 10 messages
 
  for (const msg of recentMessages) {
    console.log(`${msg.senderInboxId}: ${msg.content}`);
  }
});

Custom context extensions

You can extend the context in middleware:

const databaseMiddleware: AgentMiddleware = async (ctx, next) => {
  // Add database connection to context
  (ctx as any).db = await getDatabaseConnection();
 
  try {
    await next();
  } finally {
    await (ctx as any).db.close();
  }
};
 
agent.use(databaseMiddleware);
 
agent.on('text', async (ctx: any) => {
  // Use the extended context
  const user = await ctx.db.findUser(ctx.getSenderAddress());
  await ctx.sendTextReply(`Hello ${user.name}!`);
});

Error handling with context

Use context information for better error handling:

agent.on('text', async (ctx) => {
  try {
    await processMessage(ctx);
  } catch (error) {
    console.error('Error processing message:', {
      messageId: ctx.message.id,
      conversationId: ctx.conversation.id,
      sender: ctx.getSenderAddress(),
      error: error.message,
    });
 
    await ctx.sendTextReply(
      'Sorry, I encountered an error processing your message.'
    );
  }
});

Context in different event types

New conversation context

agent.on('dm', async (ctx) => {
  // Context for new DM conversations
  console.log('New DM started');
  console.log('Peer:', ctx.conversation.peerInboxId);
 
  await ctx.sendTextReply('Welcome to our DM! How can I help?');
});
 
agent.on('group', async (ctx) => {
  // Context for new group conversations
  console.log('Added to group:', ctx.conversation.name);
 
  const members = await ctx.conversation.members();
  await ctx.sendTextReply(
    `Hello everyone! I see there are ${members.length} members here.`
  );
});

Unknown message context

agent.on('unknownMessage', async (ctx) => {
  console.log('Unknown content type:', ctx.message.contentType);
  await ctx.sendTextReply("I received a message type I don't understand yet.");
});