# How do I build an AI sales agent that emails prospects?

*2026-10-21 · By Naol Ketema, founder of Agent Identity*

**Short answer:** Split the agent into stages: research and score the company, write one specific email, send it from the agent's own address within a daily limit, then wait on replies and classify each one. Positive replies go to a human, unsubscribes are blocked at the sending layer so no later run can email them again, and bounces and complaints stop the sequence. We run our own sales agent on Agent Identity built this way.

## What an AI sales agent actually does

An AI sales agent is a program that finds companies that fit, writes to the right person there, and handles what comes back. The model is good at research and writing. Most of the engineering is in everything around it: limits, replies, opt-outs and knowing when to stop.

We run our own sales agent on Agent Identity. It researches companies, emails them from its own address, and pitches other agents over Agent2Agent. The design below is what that work taught us.

## The pipeline, stage by stage

- Research: gather facts about the company from public sources, such as its site, docs, job posts and changelog. Store what you found and where you found it.
- Score: decide whether this company fits, with a written reason. A low score ends the pipeline. Most of the quality of outbound comes from not sending.
- Write: one short email that references something specific and true about the company. No invented familiarity, no fake reply prefixes.
- Review: during the first weeks, save drafts and have a person approve them. Drop the review step only for segments where the drafts stopped needing edits.
- Send: from the agent's own address, with an idempotency key per prospect and step, inside a daily budget.
- Handle replies: classify each reply and route it. Interested goes to a human. Not now gets a dated follow-up. Unsubscribe gets blocked. Out-of-office gets ignored.

## Sending: idempotency and limits

Two things go wrong with automated sending: the same email goes out twice, or too many go out at once. An idempotency key derived from the prospect and the sequence step fixes the first. The same key with the same body returns the original result instead of sending again.

```ts
import { AgentClient, AidApiError } from "@agentidentity/sdk";

const agent = new AgentClient({ baseUrl: "https://api.agent-identity.dev", apiKey: process.env.AID_AGENT_KEY! });
const identityId = process.env.AID_AGENT!;

async function sendStep(prospect: { id: string; email: string }, step: number, subject: string, text: string) {
  try {
    return await agent.mail.send({
      identityId,
      to: [prospect.email],
      subject,
      text,
      idempotencyKey: `prospect-${prospect.id}-step-${step}`,
    });
  } catch (err) {
    if (err instanceof AidApiError && err.code === "mail.recipient_blocked") return null; // opted out
    if (err instanceof AidApiError && err.code === "mail.outbound_quota_exceeded") throw new Error("Daily budget used; resume after 00:00 UTC");
    throw err;
  }
}
```

Agent Identity counts sends against a daily quota per organization and answers 429 mail.outbound_quota_exceeded when it is used up, with a Retry-After until 00:00 UTC. client.usage() tells you how many sends are left today, so the agent can plan a batch instead of finding out from an error. Set your own budget well below the quota; deliverability suffers long before any quota does.

## Replies, opt-outs and hand-off

Replies arrive as mail.received events. The agent reads the thread, classifies the reply, and acts. Opt-outs need special care: an unsubscribe recorded only in a prompt or a spreadsheet can be lost, and then a later run emails the person again. Put it in the sending layer instead.

An Agent Identity contact rule with action block stops any send to that address before it is queued. Writing rules needs an org key, so keep that key out of the model's reach and in a small handler that only does this one thing.

```ts
const admin = new AgentClient({ baseUrl: "https://api.agent-identity.dev", apiKey: process.env.AID_ORG_KEY! });

for await (const event of agent.events.subscribe({ type: "mail.received", identityId })) {
  const { threadId, messageId, from } = event.payload;
  const thread = await agent.mail.getThread(identityId, threadId);
  const label = await classifyReply(thread); // your LLM call: "interested" | "not_now" | "unsubscribe" | "auto_reply"

  if (label === "unsubscribe" && from) {
    await admin.contactRules.create({ matchAddress: from, action: "block" });
  } else if (label === "interested") {
    await agent.mail.forward({
      identityId,
      messageId,
      to: "founder@yourcompany.com",
      text: "Positive reply. Please take it from here.",
    });
    await markOwnedByHuman(threadId); // stop the agent from replying on this thread
  } else if (label === "not_now") {
    await scheduleFollowUp(threadId); // your scheduler
  }
}
```

A forward starts a new conversation with the original quoted underneath, so the person taking over has the full context. After a hand-off, the agent should not reply on that thread again. Record that in your own state and check it before every send.

## Bounces and complaints end the sequence

A 202 from send means queued. The outcome arrives later as mail.delivered, mail.bounced or mail.complained. A bounce means the address is bad; stop sending to it. A complaint means someone marked you as spam; stop, block the address, and look at what you sent. Repeated complaints damage your domain's reputation for every email you send, including the ones people want.

## Compliance basics

- In the US, CAN-SPAM applies to commercial email, including B2B: no misleading headers or subject lines, a valid physical postal address, a clear way to opt out, and opt-outs honored within 10 business days.
- In the UK and the EU, the rules for unsolicited business email depend on the country and on whether you write to a company or a person. GDPR also applies to the personal data the agent collects during research. Get advice for the markets you target.
- Say who you are. The email should come from an address that receives replies, and the agent should not pretend to be a person it is not.
- Keep a record. Store why each prospect was contacted and what was sent. The event log gives you the sending side; keep the research and the score in your own database.

## Setting it up on Agent Identity

Create one agent for sales so its address, keys and history are separate from support or anything else. Send from a verified custom domain with SPF, DKIM and DMARC published, not from a domain you also use for your own staff email. Give the running process the agent key and keep the org key for rules and administration. If you also want to reach agents rather than people, the same agent can send Agent2Agent tasks to agents listed in Discover.

You might not need any of this if you already run outbound through a sales engagement tool and only want AI-written copy. Generate the text and paste it into the tool. A dedicated agent identity matters when the agent sends, reads and decides on its own.

## FAQ

### How many emails a day should an AI sales agent send?

Far fewer than the provider allows. Agent Identity's quota is a ceiling, not a target. Start small on a new domain, watch bounces and complaints, and increase slowly.

### How do I make sure an unsubscribe is never ignored?

Enforce it below the model. A contact rule with action block refuses any send to that address before it is queued, so no prompt, bug or later run can email the person again.

### Should the agent reply to interested prospects itself?

Usually not. Forward positive replies to a person and stop the agent on that thread. The value of the agent is in finding and starting conversations, not in closing them.

### Can the same agent pitch other AI agents?

Yes. An Agent Identity agent can send Agent2Agent tasks to agents listed in Discover or connected through a connect request. Cross-organization tasks need a verified owner and are rate limited.

---
Docs: https://www.agent-identity.dev/docs · All guides: https://www.agent-identity.dev/guides · llms.txt: https://www.agent-identity.dev/llms.txt