Guide: Sending & Receiving Email

Building your first conversational agent workflow.

This guide walks you through the complete, practical workflow of an agent having a conversation. While the Core Concepts pages detail the individual API calls, this guide shows you how to stitch them together to create a functional conversational loop.

The Foundation: Sending HTML & Text

As a quick reminder from our Messages documentation, it’s a critical best practice to always provide both an html and a text version of your email. This ensures readability on all email clients and significantly improves deliverability.

1# Always provide both html and text when possible
2client.messages.send(
3 inbox_id="outreach@agentmail.to",
4 to=["potential-customer@example.com"],
5 subject="Following up",
6 text="Hi Jane,\n\nThis is a plain-text version of our email.",
7 html="<p>Hi Jane,</p><p>This is a <strong>rich HTML</strong> version of our email.</p>",
8 labels=["outreach-campaign"]
9)

The Conversational Loop

A common task for an agent is to check for replies in an Inbox and then respond to them. While using Webhooks is the most efficient method for this, you can also build a simple polling mechanism.

Here’s the step-by-step logic for a polling-based conversational agent.

1

1. Find a Thread that Needs a Reply

First, you need to identify which conversations have new messages that your agent hasn’t responded to. A great way to manage this is with Labels. You can list Threads in a specific Inbox that have an unreplied Label.

1# Find all threads in this inbox that are marked as unreplied
2threads = client.threads.list(
3 labels=["unreplied"]
4)
5if not threads:
6 print("No threads need a reply.")
7else:
8 # Let's work on the first unreplied thread
9 thread_to_reply_to = threads[0]
2

2. Get the Last Message ID from the Thread

To reply to a conversation, you need to reply to the most recent message in the Thread. You can get a specific Thread by its ID, which will contain a list of all its Messages. You’ll then grab the ID of the last Message in that list.

1# Get the full thread object to access its messages
2thread_details = client.threads.get(thread_to_reply_to.id)
3
4# The last message in the list is the one we want to reply to
5last_message = thread_details.messages[-1]
6message_id_to_reply_to = last_message.id
3

3. Send the Reply and Update Labels

Now that you have the message_id to reply to, you can send your agent’s response. It’s also a best practice to update the Labels on the original Message at the same time, removing the unreplied Label and adding a replied Label to prevent the agent from replying to the same message twice.

1# Send the reply
2client.messages.reply(
3 inbox_id="support@agentmail.to",
4 message_id=message_id_to_reply_to,
5 text="This is our agent's helpful reply!"
6)
7
8# Update the labels on the original message
9client.messages.update(
10 inbox_id="support@agentmail.to",
11 message_id=message_id_to_reply_to,
12 add_labels=["replied"],
13 remove_labels=["unreplied"]
14)
Real-Time Processing with Webhooks

For production applications, polling is inefficient. The best way to handle incoming replies is to use Webhooks. This allows AgentMail to notify your agent instantly when a new Message arrives, so you can reply in real-time.

Learn how to set up Webhooks