Agent Identity
← GUIDES · EMAIL

How should an AI agent handle email bounces and spam complaints?

By Naol Ketema, founder · · Markdown

SHORT ANSWER

Treat a bounce or a spam complaint as a signal to stop mailing that address, not as an error to retry. Hard bounces and complaints should suppress the recipient immediately; soft bounces can be retried a few times before you give up. With Agent Identity, delivery outcomes arrive as mail.bounced and mail.complained events, so an agent can react to them the same way it reacts to replies, and block the address with a contact rule.

Why this matters more for agents

A person who sends to a dead address notices the bounce and stops. An agent running a loop does not, unless you build that in. It will keep mailing a list with invalid addresses, keep following up with someone who marked it as spam, and quietly drag down the sending domain's reputation until mail to everyone lands in spam.

Mailbox providers judge a sender on bounce and complaint rates. Gmail and Yahoo's bulk sender requirements, for example, ask senders to keep spam complaint rates very low and honour unsubscribes. An agent that ignores these signals can damage the domain for every agent and person sending from it.

The three signals, defined

  • Hard bounce: a permanent failure. The address does not exist, the domain has no mail server, or the recipient server rejected the message outright. Retrying will not help.
  • Soft bounce: a temporary failure, such as a full mailbox or a server that is briefly unavailable. A later retry may succeed.
  • Spam complaint: the recipient clicked report spam, and their provider passed that back through a feedback loop. The address is valid, but the person does not want the mail.

What to do for each

  • Hard bounce: stop sending to that address now. Mark the contact as invalid in your own records and, if the address came from a lead source, flag the source.
  • Soft bounce: allow a small number of retries spread over hours or days, then treat it as a hard bounce.
  • Spam complaint: stop all mail to that address, including follow-ups already scheduled. Do not try to explain yourself by email.
  • In every case: stop any wait or follow-up the agent had pending on that thread, and record why, so a person can review it later.

How delivery outcomes reach the agent in Agent Identity

A send returns 202 Accepted. That means queued, not delivered. The outbound message then moves from queued to sent, and finally to delivered, bounced or complained as the provider reports back. Each transition appends an event:

  • mail.sent: the provider accepted the message.
  • mail.failed: the provider refused it or could not be reached, with the reason in detail.
  • mail.delivered: the recipient's server accepted it.
  • mail.bounced: it bounced. The payload has messageId, threadId and an optional detail with the reason.
  • mail.complained: the recipient reported it as spam. Same payload shape.

The last three come from the provider's delivery-status webhook and are matched to the outbound message by the provider's message id. On these events, identity_id is filled in only when the sending mailbox can still be resolved, so do not require it. Filter by the message instead, or subscribe at the organization level.

Reacting automatically

The payload names the message, not the recipient, so keep a record of who you sent each message to. Then react to bounces and complaints by blocking the address with a contact rule. A blocked recipient stops any later send before it is queued, which protects you from a forgotten follow-up job.

// Run with an org key: contact rules need one.
for await (const event of client.events.subscribe({ type: "mail.complained" })) {
  const sent = await db.sentMessages.get(event.payload.messageId); // what you stored at send time
  if (!sent) continue;

  await client.contactRules.create({
    identityId: sent.identityId,     // omit to block for the whole organization
    matchAddress: sent.to,
    action: "block",
  });
  await db.followUps.cancel(event.payload.threadId);
}

Run a second loop for mail.bounced, and use detail to decide between permanent and temporary failures before you block. Contact rules can be scoped to one agent with identityId or apply organization-wide when you omit it. For a complaint, organization-wide is usually right.

To confirm a single important message, wait on its outcome directly. Capture since before the send so a fast callback is not missed.

const since = new Date();
const message = await client.mail.send({ identityId, to: ["customer@example.com"], subject: "Your order", text: "Shipped." });

const { event, timedOut } = await client.events.wait({
  type: "mail.bounced",
  aggregateId: message.id,
  since,
  timeoutMs: 55_000,
});

Prevent bounces before they happen

  • Send from a domain with SPF, DKIM and DMARC set up. Unauthenticated mail is more likely to be rejected or filtered.
  • Do not let an agent guess addresses. Pattern-guessed addresses are the most common source of hard bounces in outbound agents.
  • Keep send volume proportional to how many people actually want the mail. The daily quota is a ceiling, not a target.
  • Give each agent its own address so you can see which agent caused a bounce or complaint, and suspend just that one if it misbehaves.
  • Warm up a new custom domain slowly rather than starting at full volume.

We run our own sales agent on Agent Identity, and handling these events is part of what keeps an outbound agent from hurting its own domain. The mechanics are small. The discipline of honouring every signal is what matters.

FAQ

Should an AI agent retry a bounced email?

Not a hard bounce: the address is invalid and retrying only hurts reputation. A soft bounce can be retried a few times over hours or days before treating it as permanent.

How do I know an email bounced in Agent Identity?

A mail.bounced event is appended with the messageId, threadId and an optional detail. You can wait on it, subscribe to it, or receive it by webhook.

Does a 202 from the send endpoint mean the email was delivered?

No. 202 means the message was accepted and queued. Delivery is confirmed by a mail.delivered event, or refuted by mail.bounced, mail.complained or mail.failed.

How do I stop an agent from emailing someone who complained?

Create a contact rule with action block for that address. A blocked recipient is refused before anything is queued.

Related guides

Give your agent its own identity in a couple of minutes. Get started free → · Docs · All guides