AI Email Agent: Build One That Sends and Receives

9 min read

What an AI email agent is, and how to build one with its own mailbox, inbound webhook, reply loop, and correct threading. With working code.

John Joubert

John Joubert

Founder, Robotomail

AI Email Agent: Build One That Sends and Receives
Table of contents

An AI email agent is a program with its own email address that reads incoming mail, decides what to do, and replies without a human in the loop. The four pieces you need are a real mailbox, an inbound webhook that delivers parsed messages to your code, a reply loop that sends from that same address, and threading metadata so replies land in the original conversation. Everything else, the model, the prompts, the tools, sits behind those four pieces.

This post is for people building that agent. If you want a product that drafts email for a human to review, this is the wrong page. We are talking about an autonomous participant in email conversations.

What an AI email agent actually is

Strip away the marketing and an AI email agent is a small event-driven service:

  • It owns an address, for example shopping-agent@yourdomain.com. That address is the agent's identity to the outside world.
  • Inbound mail arrives as an HTTP POST to your endpoint, already parsed into sender, subject, body, and thread.
  • Your handler feeds that into a model, along with whatever context the agent keeps (order records, CRM rows, prior turns of the thread).
  • The agent sends a reply from the same address, referencing the inbound message so mail clients thread it correctly.

The hard part is not the model. It is that email is asynchronous, stateful, and full of edge cases: bounces, auto-responders, forwarded chains, attachments, replies that arrive three days later. Designing for that is what separates a demo from an agent you can leave running.

Why not just use a human's inbox

The usual first instinct is to hand the agent OAuth access to a personal Gmail or Outlook account. It works until it doesn't. You get a shared identity between human and bot, consent screens and token refresh to babysit, per-account quotas that were designed for one person typing, and no clean way to spin up a hundred addresses when you have a hundred agent instances. We wrote up the tradeoffs in more depth in Gmail API vs a purpose-built agent mailbox. For a builder, the shape you want is: one mailbox per agent, provisioned by API, disposable.

Architecture of an AI email agent

Four components, in the order data flows through them.

1. The mailbox. A real inbox with MX records behind it, so external senders can reach it and it can send back. One per agent instance keeps state clean: the mailbox ID becomes a natural partition key for the agent's conversations. See mailboxes for the model.

2. The inbound webhook. Instead of polling IMAP, you register an HTTPS endpoint. Every received message is POSTed to it as JSON. This is the single biggest architectural decision, because polling costs you latency and duplicate-detection logic you would rather not write. If you are new to webhook ingest, receiving email by webhook covers the basics.

3. The reply loop. Webhook handler acknowledges fast, pushes work to a queue, and a worker calls the model and sends the reply. Do not run inference inside the HTTP handler. Model calls take seconds, webhook deliveries retry, and you will double-reply.

4. Threading. Every outbound reply carries the ID of the message it answers. That is what makes the conversation look like a conversation in the recipient's client rather than a series of unrelated notes.

How to build one, step by step

1. Provision a mailbox

npx @robotomail/cli mailbox create shopping-agent

Or from the API, which is what you will use when agents create their own mailboxes at runtime:

curl -X POST https://api.robotomail.com/v1/mailboxes \
  -H "Authorization: Bearer rm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"address": "shopping-agent"}'

That returns a mailbox ID you store alongside your agent record. If you want the agent to be agent@yourcompany.com instead of a platform domain, add a custom domain and pass its domainId.

2. Register a webhook

Webhooks are a separate resource from mailboxes, so you register an endpoint URL and then point it at the mailboxes you care about. The full request shape is in the webhooks docs. Once registered, inbound mail arrives like this:

{
  "event": "message.received",
  "data": {
    "message_id": "...", "mailbox_id": "...", "mailbox_address": "shopping-agent@robotomail.co",
    "from": "vendor@example.com", "subject": "Order A-4521 delivery update",
    "body_text": "...", "thread_id": "...", "received_at": "2026-04-17T10:00:00.000Z"
  }
}

Note the shape: { event, timestamp, data } at the top level, snake_case inside data. Write your parser against that and nothing else.

3. Acknowledge, deduplicate, enqueue

import express from "express";

const app = express();
app.use(express.json());

app.post("/hooks/robotomail", async (req, res) => {
  const { event, data } = req.body;

  // Ack immediately. Retries are cheap for the sender, expensive for you.
  res.status(200).end();

  if (event !== "message.received") return;

  // message_id is stable across retries. Use it as your idempotency key.
  if (await alreadyProcessed(data.message_id)) return;
  await markProcessed(data.message_id);

  await queue.add("handle-email", {
    mailboxId: data.mailbox_id,
    threadId: data.thread_id,
    messageId: data.message_id,
    from: data.from,
    subject: data.subject,
    bodyText: data.body_text,
  });
});

app.listen(3000);

4. Run the agent and reply on the thread

In the worker, load the thread history, call the model, and send. The important field is inReplyTo, set to the inbound message_id:

curl -X POST https://api.robotomail.com/v1/mailboxes/mbx_shopping/messages \
  -H "Authorization: Bearer rm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"to": ["vendor@example.com"], "subject": "Order A-4521 delivery update", "bodyText": "Thanks, noted. Can you confirm the carrier?", "inReplyTo": "msg_01hx4k8zq"}'

Keep the subject line the same as the inbound one, prefixed with the conventional reply marker if you want, and let inReplyTo do the structural work. The step-by-step version with full request and response bodies is in receive and reply.

5. Give the model the thread, not the message

The single most common quality bug in email agents is answering the latest message in isolation. Email conversations carry context across turns, and senders assume you remember. Fetch the thread by its thread_id and pass the ordered turns into the prompt. Strip quoted history from the body before you do, or you will feed the model three copies of its own previous replies and burn context on nothing.

Threading rules that matter

  • Set inReplyTo on every reply. Without it, Gmail and Outlook may show your reply as a separate conversation, and the recipient loses the thread.
  • Never change the subject mid-thread unless you intend to fork the conversation.
  • Treat thread_id as the agent's memory key. State, tool results, and any "we already promised them a refund" facts belong on the thread, not on the message.
  • Quote sparingly. Agents that quote the entire history on every turn produce unreadable mail after four exchanges.

More detail on how thread identity is derived lives in threading concepts and in what email threading is.

Failure modes to design for before you ship

Reply loops. Two agents emailing each other, or your agent replying to its own bounce notification, can spin forever. Add a per-thread reply counter and a hard cap. Also skip messages that carry auto-submitted or auto-reply markers.

Duplicate deliveries. Webhook delivery is at-least-once. If your handler is not idempotent on message_id, one network blip becomes two replies to a customer.

Slow models, fast retries. If you do inference inline and it takes twelve seconds, the delivery may retry before you finish. Queue the work.

Bounces and suppressions. Agents that keep writing to a dead address damage your sending reputation. Track hard bounces and stop. See soft bounce vs hard bounce for what to retry and what to drop.

Unbounded autonomy. Decide up front what the agent may send without approval. A research agent asking vendors for pricing is low risk. An agent that can commit to refunds is not. Gate the risky tool calls, not the email transport.

Attachments. Inbound PDFs and images are common in invoice, legal, and order workflows. Handle them explicitly and scan them; do not pass raw bytes into a model without checking type and size first. See attachments.

Where this pattern shows up

The same four components cover most real deployments: invoice chasing agents that follow up until they get a payment date, lead qualification agents that ask two clarifying questions before handing off, customer support agents that resolve tier-one tickets over email, and multi-agent workflows where agents use email as the transport between systems that share no API. If you are wiring this into a specific framework or runtime, start with giving your AI agent an email address.

FAQ

Can an AI email agent use a Gmail account instead of its own mailbox?

Technically yes, through the Gmail API and OAuth. Operationally it gets painful: shared identity with a human, token refresh, per-account quotas, and no clean provisioning story when you scale to many agents. Purpose-built agent mailboxes exist because those problems are structural, not fixable with better code.

Do I need to poll IMAP to receive mail?

No. Register an inbound webhook and messages are POSTed to your endpoint already parsed. Polling adds latency and forces you to build duplicate detection and cursor tracking yourself.

How does the agent keep replies in the same thread?

Send the reply with inReplyTo set to the inbound message_id, and keep the subject line consistent. Group your own state under the thread_id you receive on each inbound message.

How many mailboxes should one agent have?

One address per agent instance or per workflow is the pattern that scales. It keeps conversation state partitioned, makes debugging obvious from the recipient address, and lets you retire an agent by deleting its mailbox.

Ready to build? Create a mailbox, point a webhook at your handler, and your agent is on email in a few minutes. Start at robotomail.com or read the quickstart.

Give your AI agent a real email address

One API call creates a mailbox with full send and receive. Webhooks for inbound, automatic threading, deliverability handled. 30-day money-back guarantee.

Related posts