Inboxes · September 29, 2026
Why Your Agent's Email Lands in Spam
Why does my AI agent email go to spam? The three agent-specific causes, what the platform reports back, and the part no sender can see.
A customer tells you the agent’s follow-up “went to spam”. You check, and every signal you
have is green: the send returned 202, the message reached accepted, and the recipient
shows delivered. Search “why does my AI agent email go to spam” and the pages that
rank for the question will tell you to publish SPF, DKIM and DMARC, warm up the domain,
and keep your complaint rate under 0.3%. That is advice for a marketer who owns a domain
and sends one campaign to a list. It barely touches an agent.
This article settles three things: what you control and what you do not when an agent sends from a platform address, the three ways agent traffic differs from the traffic spam filters were tuned on, and exactly what this product reports back (which, for spam-folder placement, is nothing, for reasons no provider can fix). If the message bounced rather than arriving in spam, that is a different problem, covered in agent email bounced 550 5.7.1.
Why does my AI agent email go to spam? The short answer
Spam filters decide from two kinds of evidence: who is sending, and how this sender’s mail has been received before. Authentication settles the first. The second is reputation, and reputation is a pattern learned over time: similar messages, at a steady rate, to people who mostly want them. Agent mail breaks every part of that pattern at once.
- The content is different every time. A model composes each message fresh, so there is no stable template for a filter to learn is harmless.
- Volume arrives in bursts. An agent sends when a trigger fires, not on a warmup curve.
- The reply ratio is inverted. A person’s outbox is mostly replies to people who wrote first. An agent’s is mostly first contact.
None of these is a misconfiguration you can fix with a DNS record. They are properties of the workload. And one fact frames everything else: no sender, on any provider, can guarantee inbox placement. Google says so about providers directly, in the passage where it declines allowlist requests: “We can’t guarantee messages sent by email providers will pass Gmail’s spam filters” (Email sender guidelines, read 29 September 2026).
What you control on a platform address, and what you do not
Live mail on this product is sent from agents.emailforagents.ai, a domain the
platform operates. You do not publish its SPF, DKIM or DMARC records and cannot change
them. You can read them, which is worth doing once so you stop suspecting them. As of
29 September 2026:
dig +short TXT _dmarc.agents.emailforagents.ai
# "v=DMARC1; p=none; rua=mailto:support@agents.emailforagents.ai; adkim=r; aspf=r; ri=86400"
dig +short TXT send.agents.emailforagents.ai
# "v=spf1 include:amazonses.com ~all"
dig +short TXT resend._domainkey.agents.emailforagents.ai
# "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDj..."
That is a DKIM key on the sending domain, an SPF record on the bounce subdomain, and a
DMARC record in monitoring mode with relaxed alignment. That p=none suits a shared
platform domain; why it is the wrong starting point for a fresh agent subdomain of your
own is covered in DMARC for autonomous senders.
Published records are configuration evidence. They are not a statement about where any message lands.
Two consequences of a shared domain matter more than the records.
Reputation is shared. Every live platform inbox sends from the same domain, and Google documents that “Gmail tracks volume, feedback, and limits per domain and IP address”. So the domain reputation your agent inherits was built by every agent on the domain, and your agent’s behaviour contributes to everyone else’s. That cuts both ways: a new agent does not start from zero, and it cannot fully insulate itself either.
The dashboard the 0.3% refers to is not yours to open. The spam-rate thresholds every
guide quotes are measured in Google Postmaster Tools, which shows data only for domains
you have verified you own. Its
setup page says it “won’t display
information about your email until your domain is verified” (read 29 September 2026). You cannot verify
agents.emailforagents.ai, so you cannot see the number you are being told to keep
low. The operator of the domain can.
The way out of shared reputation is your own sending domain, set up as described in
agent email on a subdomain and in the
domains documentation. Custom domains
require a paid plan, and paid plans are not purchasable yet. Builder and Team are
proposed plans with checkout disabled, and adding a domain without a paid plan returns
HTTP 402 payment_required. When that changes, a domain of your own buys you a
reputation that is only yours and a Postmaster Tools dashboard you can read. It does not
buy placement.
Cause 1: content that never repeats
Filters learn senders by their mail. A billing system sends the same receipt template ten thousand times, and after the first few hundred are received without complaint, the ten-thousand-and-first is unremarkable. An agent never sends the same message twice. Every body is novel text, often with a different subject shape, a different length and a different set of links.
Google’s guidance for bulk senders assumes the opposite. It says: “If you change the format of your bulk emails, gradually increase the sending volume of messages with the new format.” An agent is, in that framing, changing format on every send. Nothing fixes that without making the agent less useful, but three habits make agent content look less like the mail filters exist to stop.
Send a real plain-text part. The send endpoint accepts text, html, or both, and
requires at least one. If you send only html, the platform does not derive a
plain-text version: the sanitised HTML string itself is handed to the sending provider
as the message’s text field, so recipients on text-only clients can see raw markup.
Always send text, and send html as well only if the agent actually needs formatting.
Do not fake a thread. Google’s guidelines say not to “send messages with subject
lines starting with Re: or Fwd: unless the messages are actual replies or forwards.”
Models often write “Re:” on a follow-up that is really a new message. If the
message is a reply, send it as one: pass reply_to_message_id, and the platform sets
In-Reply-To and References from the parent, so the subject and the headers agree.
If it is not a reply, the subject should not claim to be.
Keep the sender identity boring. Google asks that display names “reflect a
consistent, clear, and accurate statement of the sender’s identity”. On this product
the From display name is the inbox’s display_name, a property of the inbox rather
than something the model supplies per message. The send schema is strict and has no
field for arbitrary headers, so the agent cannot add its own. Pick a display name that names
the agent or the team behind it, and leave it alone.
What does not work is the advice written for AI outreach tools: make each message look less templated. For an agent the wording already varies. Adding deliberate variation to get past a filter is filter evasion, and it is the same behaviour as the reword-and-resend loop that the 550 5.7.1 article tells you to forbid.
Cause 2: bursts where filters expect a curve
Warmup advice describes a curve: a small volume on day one, rising slowly. Google’s version reads “Send email at a consistent rate. Avoid sending email in bursts,” and “Avoid introducing sudden volume spikes if you do not have a history of sending large volumes.”
Agents do not produce curves. They produce whatever the trigger produces. A CRM export lands and the agent emails forty leads in two minutes. A support queue backs up overnight and the agent answers ninety tickets at 06:00. A retry loop with a bug sends the same recipient list three times. From the receiving side each of these is a spike from a sender with no history of spikes.
You cannot warm up a workload whose volume you do not decide. You can cap it, and the
cap should be a platform control rather than an instruction in the prompt, because the
prompt is the thing that failed when a loop runs away. Inbox policy has a
daily_recipient_limit (an integer from 1 to 1,000,000, or null for none), set by an
owner or admin with PUT /v1/projects/{project_id}/inboxes/{inbox_id}/policy. Once it
is reached, further sends fail with 403 policy_denied and the message “The daily
recipient limit has been reached.” The bucket is keyed by UTC date. A cap does not
smooth a burst inside the day, so if the agent is fed batches, meter them in your own
queue: a batch of forty sent over an afternoon looks nothing like forty sent in a
minute.
Separate the two kinds of agent traffic while you are at it. Replies to people who wrote in are the safest mail an agent sends. Unsolicited first contact is the riskiest. If one inbox does both, the outreach sets the pattern the replies are judged by.
Cause 3: a reply ratio no person has
Google’s guidance keys on engagement throughout. It recommends starting “with a low sending volume to engaged users”, warns that people who did not sign up for your mail “might mark your messages as spam”, and suggests “unsubscribing recipients who don’t open or read your messages.” It also says Google does not track open rates, and this product has no open or click tracking either. So the one engagement signal you can measure yourself is the plainest one: did anyone write back?
A person’s sent folder is dominated by replies. An agent’s often is not: it starts
threads, and many of them never hear back. That ratio is visible in the data you
already have. Every message in the listing carries a direction of inbound or
outbound and a thread_id, so you can compute, per inbox, how many threads the agent
started and how many of those got any answer:
const API = "https://api.emailforagents.ai/v1";
async function replyRate(projectId: string, inboxId: string, since: Date) {
// thread_id -> direction of the oldest message seen, and inbound count
const threads = new Map<string, { first: string; inbound: number }>();
const cutoff = since.toISOString();
let cursor: string | undefined;
let done = false;
while (!done) {
const url = new URL(`${API}/projects/${projectId}/inboxes/${inboxId}/messages`);
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.EFA_API_KEY}` },
});
if (!res.ok) throw new Error(`list failed: ${res.status}`);
const page = await res.json();
for (const m of page.data) {
if (m.created_at < cutoff) { done = true; break; }
// an outbound send that was canceled or failed never reached anyone
if (m.direction === "outbound" && ["canceled", "failed"].includes(m.state)) continue;
const t = threads.get(m.thread_id) ?? { first: m.direction, inbound: 0 };
t.first = m.direction; // newest first, so the last one seen is the oldest
if (m.direction === "inbound") t.inbound += 1;
threads.set(m.thread_id, t);
}
if (!page.has_more) done = true;
cursor = page.next_cursor;
}
const started = [...threads.values()].filter((t) => t.first === "outbound");
const answered = started.filter((t) => t.inbound > 0).length;
const rate = started.length ? answered / started.length : null;
return { started: started.length, answered, rate };
}
The key needs messages:read. The listing is newest first, which is why the loop stops
at the first message older than since; a thread that began before the window is
counted from its first message inside it, so choose a window of a few weeks. Auto-replies
and out-of-office notices arrive as ordinary inbound messages and inflate the rate, so
treat the number as a trend, not a score.
There is no universal good value and this article will not invent one. What the number is good for is comparison: between inboxes, between the week before and after a prompt change, between the agent that answers tickets and the agent that does outreach. An inbox whose started-thread reply rate falls sharply is sending mail its recipients did not want, and that is the pattern that produces spam reports, long before anyone tells you.
What you are told, and what nobody can tell you
The honest answer to “did it land in spam” is that the sender cannot know. A receiving
server that accepts a message and files it in a spam folder reports the same thing to
the sender as one that files it in the inbox: accepted. On this product that surfaces
as message.delivery_updated with a status of delivered, and nothing after it. The
ladder of what each status proves is laid out in
accepted vs delivered email.
Here is what each outcome looks like from your side:
| What happened at the recipient | What you receive here | What else changes |
|---|---|---|
| Accepted, filed in inbox | delivered on message.delivery_updated | Nothing |
| Accepted, filed in spam | delivered on message.delivery_updated | Nothing. Indistinguishable from the row above |
| Recipient reports spam, provider sends a complaint report | complained on message.delivery_updated | Address suppressed at workspace scope |
| Gmail user reports spam | Nothing per message | Counted in an aggregate spam rate only the domain owner sees |
| Provider suppresses the address | suppressed on message.delivery_updated | Address suppressed, recipient unit reversed |
| Permanent bounce | bounced on message.delivery_updated | Address suppressed |
Two rows need explaining.
Gmail does not send complaint reports for individual messages. Its Feedback Loop is
aggregate by design: reports are “generated only if a given Identifier is present in a
certain volume of mails as well as in distinct user spam reports”, and the data sits in
Postmaster Tools for the verified owner of the signing domain
(Feedback Loop, read 29 September
2026). AWS states it more bluntly for its own email service: “Gmail does not send spam
complaint events because their priority is to protect the privacy of their users”
(AWS Messaging blog,
19 July 2024). So a complained status reaches you only from mailbox providers that
send per-message reports. The absence of complaints tells you very little about Gmail
recipients.
The spam rate can look better as things get worse. Google defines the Postmaster Tools spam rate as “the percent of your messages that are delivered to engaged recipient’s Inbox and then marked as spam by the recipient,” and adds: “If Gmail automatically sends a significant number of your messages to spam, the rate shown in the dashboard might seem low, because recipients get fewer of your messages in their Inbox.” It also warns that dashboards “might not include all data on days when your outgoing email volume is low” (Postmaster Tools dashboards, read 29 September 2026). A low-volume agent whose mail is already being filed as spam can therefore look clean on the one metric every guide tells you to watch. This is the detail the ranking pages leave out, and it is why the reply rate above is a better early signal for agent mail than any complaint count.
When an address is suppressed, you are not sent a separate suppression event. The
change shows up on your next send to that address, which fails at request time with
422 recipient_suppressed, marked non-retryable. There is no automatic complaint-rate
threshold on a workspace in this build: a complaint suppresses one address, and the
workspace keeps sending. Two declared event types sound relevant and are not emitted by
this build: project.sending_paused and message.quarantined appear in the event
vocabulary but nothing fires them, and message.quarantined concerns inbound
processing in any case. If sending is stopped at the workspace level, you learn it from
a 403 permission_denied on the send (“Sending is paused or the resource is
inactive.”), not from an event. And message.failed is not a spam signal at all: it fires when the
provider refuses a submission, or with a reason of provider_retry_window_expired or
reply_parent_headers_not_available.
A diagnostic order when someone says it went to spam
Work in this order, cheapest check first.
- Confirm it was sent live and delivered. Check
environmentandsimulatedon the message and the recipient’sdelivery_status. A test inbox cannot reach an internet recipient, so “it went to spam” about a test send means it went nowhere. My agent says it sent it, nobody received it covers this branch in detail. - Get the headers from the recipient. Ask them to open the message in their spam
folder and view the original. The
Authentication-Resultsheader shows whether SPF, DKIM and DMARC passed at their server. On a platform address they should; if they did not, report it to support@agents.emailforagents.ai with the message ID, because it is not a record you can change. - Ask them to mark it not spam. Google’s own guidance says recipients “can mark valid messages as not spam, so future messages from the sender should be delivered to their inbox.” For a recipient who wants the agent’s mail, this is the step most likely to help.
- Read the message the agent wrote. Check for a missing
textpart, a fake “Re:”, links whose visible text does not match their target, and claims of urgency. Google asks that links “be visible and easy to understand” and that content not be hidden with HTML and CSS. - Check the shape of the day. Was it one of forty messages sent in a minute? Was the recipient cold? Compare the inbox’s started-thread reply rate with its recent history.
- Change the workload, not the wording. Split outreach from replies, set a
daily_recipient_limit, or set the inbox’sapproval_modeto"always"so a person reads every send from that inbox before it goes. Do not have the agent resend a reworded copy to the same person to see if it gets through.
The inboxes overview lists the controls this relies on. None of them places mail in an inbox. What they do is keep an agent’s traffic close to the shape receiving systems were built to trust: consistent, expected, and answered.
Keep reading
Accepted vs Delivered: What an Email Status Proves
Queued, accepted, delivered, bounced, unknown: what each email status actually proves, what it does not, and the correct next action for every one of them.
Agent Email Bounced 550 5.7.1: What It Means
Agent email bounced 550 5.7.1? Which variant you hit, why it is rarely your DNS on a shared domain, and why the agent must stop, not reword and resend.
My Agent Says It Sent It. Nobody Received It
Your AI agent says it sent an email and nobody received it. Work the states in order, learn which ones emit no event at all, and never retry one.