Developers · September 28, 2026
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.
Your agent emailed a supplier, the send returned HTTP 202, the message reached
accepted, and twenty minutes later the recipient’s server refused it with
550 5.7.1. When an agent email bounced 550 5.7.1, nearly every page you will find
assumes you own the sending domain and tells you to fix SPF, DKIM and DMARC. That
advice is written for a marketer with a misconfigured domain. It is mostly the wrong
advice for an autonomous sender on a shared, managed domain, and one piece of it,
“change the content and try again”, is actively dangerous when the content was written
by a model.
This article settles three things: which kind of 5.7.1 you hit and whether you can act on it at all, exactly what this product tells you when a message bounces (less than you might expect, and on purpose), and the rule your agent needs: stop, surface it to a human, never reword and resend. If you are still working out whether the message went out in the first place, start with my agent says it sent it, nobody received it.
What 550 5.7.1 means, and what it does not
550 is the SMTP reply code for a refused action. 5.7.1 is the enhanced status code
from RFC 3463, and it carries more information than the 550 does. The first digit, 5,
is the class for a permanent failure, which the RFC defines as one “not likely to be
resolved by resending the message in the current form”. The middle 7 is the security
or policy subject. The final 1 is the detail: “Delivery not authorized, message
refused”. The RFC’s own description is that “the sender is not authorized to send to
the destination”, that this “can be the result of per-host or per-recipient
filtering”, and that the code “is useful only as a permanent error”
(RFC 3463, read 28 September 2026).
So 5.7.1 says who decided (the receiving system, by policy) and how final it is (permanent). It does not say which policy. Relay rules, recipient restrictions, spam filtering, sender reputation and header checks all report through the same three digits. The diagnosis is in the free text after the code, which each receiving system writes differently.
Two things it is not. It is not a statement that the address does not exist; that is normally 5.1.1. And it is not a transport problem that a retry might clear. A 4xx code is the receiving server asking you to come back later. A 5xx code is the receiving server telling you not to.
Agent email bounced 550 5.7.1: the five variants and who can act
The same code covers five different decisions, and the question that matters for an agent operator is not “what is wrong” but “whose setting is it”. The bounce texts below are quoted from Google’s and Microsoft’s own documentation as of 28 September 2026.
| Variant | Example wording from the receiver’s docs | Whose setting | What the agent operator can do |
|---|---|---|---|
| Relay denied | Gmail: “The IP you’re using to send email is not authorized to send email directly to our servers.” | The sending infrastructure | Nothing. Report it with the message ID |
| Sender reputation or authentication | Microsoft 365: “Client host [xxx.xxx.xxx.xxx] blocked using Blocklist 1”. Gmail: “very low reputation of the sending IP address” | Whoever operates the sending domain and IPs | Nothing directly on a managed domain. Report it |
| Recipient policy | Gmail: “The user or domain that you are sending to (or from) has a policy that prohibits the email that you sent.” | The recipient’s administrator | Ask the recipient, out of band, to allow the sender |
| Content or spam policy | Gmail: “This message is likely unsolicited email.” | The recipient’s filter, reacting to your message | Have a human decide whether to send at all |
| Custom admin rule | Gmail: “This message violates example.com email policy. - gcdp” | The recipient’s administrator | Same as recipient policy |
The Gmail wording comes from
Gmail SMTP errors and codes
(last updated 2026-09-24 UTC on the page). That page lists more than a dozen distinct
550 5.7.1 messages, including several for RFC 5322 header violations and one that
reads “This email has been rate limited”, which is a rare case of a rate limit expressed
as a permanent code. Google also documents that it appends gcdp when the rejection
came from a rule a Google Workspace administrator created, which is the most useful
single token in a Gmail 5.7.1: it tells you a person at the recipient organisation made
this decision.
The Microsoft wording comes from
Fix NDR error 550 5.7.1 in Exchange Online,
which lists the typical causes as: you do not have permission to send to the recipient,
the recipient is a group you are not allowed to send to, you are not allowed to send
through a server between you and the recipient, or the message was routed to the wrong
server. Its instruction to senders is blunt: “Typically, you can’t fix the problem
yourself. You’ll need the recipient or the recipient’s email admin to fix the
configuration on their end.” It also documents
550 5.7.1 RESOLVER.RST.AuthRequired; authentication required for a public folder that
only accepts authenticated senders, which is a recipient setting, despite the word
“authentication” in it.
Why it is usually recipient policy or content, not your DNS
For agent mail on this product, the three rows at the bottom of that table are the likely ones, and the reason is structural rather than statistical.
The authentication records are not yours to change. Live mail is sent from
agents.emailforagents.ai, a domain the platform operates. Custom domains are not
available to buy today, so there is no SPF record, DKIM key or DMARC policy of your own
anywhere in this path, and none of the “add this TXT record” advice on the first page
of results applies to you. You can see what is published with an ordinary lookup. As
of 28 September 2026, dig TXT _dmarc.agents.emailforagents.ai returns a DMARC record
with p=none, and the bounce-handling subdomain send.agents.emailforagents.ai
publishes an SPF record and an MX record pointing at the sending provider’s feedback
host. Those are platform facts that you can observe, not settings you can adjust, and a
correct record is evidence of configuration, not a promise about where any message
lands.
Gmail reports SPF, DKIM and DMARC failures under other codes. This is the detail
most generic pages get wrong. On Google’s list, a message rejected because the sender
is unauthenticated, because SPF hard-failed, or because of the sending domain’s DMARC
policy comes back as 550 5.7.26, and a bulk sender failing SPF comes back as
550 5.7.27. A literal 5.7.1 from Gmail is almost always policy, spam
classification, reputation, header format, relay or rate. The one exception on the
page is a message that “does not meet IPv6 sending guidelines regarding PTR records and
authentication”, which is about the sending infrastructure, not a record you publish.
If you are reading a Gmail 5.7.1 and reaching for DNS, you are almost certainly reading
the wrong row.
Agent mail has the profile that recipient policy exists to stop. An agent typically writes to someone who has never received mail from its address, often a role mailbox or a distribution group at a company, often once. Organisations restrict exactly that: groups that accept only internal senders, mailboxes that accept only allow-listed external senders, gateway rules against unfamiliar domains. None of that is visible to the sender until the bounce.
The content was composed by a model. Content filters score what they see. A model writing its twentieth near-identical outreach email of the day, with a confident subject line and a link, produces precisely the text those filters were trained on. A content-class 5.7.1 on agent mail is information about the message, and that is the case where the temptation to retry is strongest.
What you actually receive when a message bounces here
Nothing in this product says “550”. The receiving server’s rejection reaches the sending provider, the provider reports it back as a callback, and what reaches you is a per-recipient status change. You get the fact of the bounce, not the reason.
The event is message.delivery_updated. Provider callback names such as
email.bounced are internal inputs and never reach your webhook or the events API. The
event’s data carries exactly three fields:
{
"id": "evt_...",
"type": "message.delivery_updated",
"sequence": 4182,
"occurred_at": "2026-09-28T14:03:11.402Z",
"data": {
"message_id": "msg_...",
"recipient": "procurement@supplier.example",
"status": "bounced"
},
"environment": "live",
"api_version": "2026-09-19"
}
There is no SMTP code, no enhanced status, no diagnostic string and no bounce subtype
on it, and there is none on the message resource either. That is not an omission in the
event shape. The sending provider is Resend (it is the provider this product’s mail
code integrates with, and it appears on the published subprocessor list), and it does
send a diagnostic: its
bounce webhook documents a
bounce.diagnosticCode array containing the receiving server’s response, alongside
message, subType and type. When that callback is ingested here, it is reduced to a
redacted record that keeps the event type, the provider’s email ID, the recipient list,
the timestamp and one field from the bounce object, bounce.type. The raw body is
kept only as a SHA-256 digest. The free text a receiving server chose to put in its
rejection, which can include your recipient’s internal hostnames and addresses, is not
stored.
Three consequences follow, and they are worth designing around:
- The message stays
accepted. A bounce is a per-recipient outcome. Readrecipients[]on the message listing, where each entry carries its owndelivery_status; over MCP,list_messagesreturns the same recipient metadata. The top-level message state does not becomefailed. message.failedis not the bounce signal. It fires when the provider refuses a submission synchronously, or with areasonofprovider_retry_window_expiredorreply_parent_headers_not_available. A 5.7.1 from a receiving server happens after acceptance, so it arrives only asmessage.delivery_updated.- Whether the address is suppressed depends on a classification you cannot see.
The recipient is added to the workspace suppression list when the bounce type is
Permanent, and not otherwise. Resend’s own bounce types page, as of 28 September 2026, listsContentRejectedas a subtype ofTransient, notPermanent. So a content-filter 5.7.1 can mark the recipientbouncedand leave the address fully sendable. The only way you find out a suppression happened is that the next send to that address fails at request time with422 recipient_suppressed, which is marked non-retryable.
Read the third point again, because it is what makes the retry rule below necessary. The platform’s automatic protection against resending to a bouncing address applies to permanent bounces, complaints and provider suppressions. A policy or content rejection may not trigger it, which leaves your own code as the only thing between the agent and a second attempt.
Why the accept told you nothing about this
The 202 and the accepted state were true. They established that your request passed
policy and that the sending provider took the message. They said nothing about the
recipient’s server, because at that moment the recipient’s server had not been asked.
What each email status actually proves walks the
whole ladder; the short version for this case is that the bounce is the
authoritative signal, and the accept is not.
Agent frameworks get this wrong in a characteristic way. The tool call returns, the
model reads a success, and it tells the user “I’ve emailed the supplier” in the same
turn. By the time the bounce arrives, the conversation has moved on and nothing is
listening. Two changes fix it. Have the send tool report what is true at that moment
(“queued for delivery, outcome pending”), and consume delivery outcomes from the
durable event cursor on GET /v1/projects/{project_id}/events, not only from webhook
arrivals, so a bounce that lands while your process is restarting still reaches the
task that caused it. Events and webhooks covers the cursor
and the signed webhook.
One more detail that keeps handlers honest. The event is emitted for each callback that
names a single recipient, but the stored status only moves forward: bounced outranks
delivered and delayed, and an out-of-order older callback does not overwrite a newer
one. If two events for the same recipient disagree, recipients[] on the message is the
record.
The rule: stop, surface to a human, never reword and resend
A 5xx is permanent. Do not retry it, do not retry it later, and above all do not let the agent “try again with different wording”. A model that rewrites a message until a filter accepts it is doing filter evasion, whatever its intent. The recipient’s system made a policy decision about your sender or your content. Iterating on the content until it passes turns one refused email into a probe of someone else’s security controls, sends several variants of an unwanted message, and trains your agent that a refusal is an obstacle to route around. The same split between errors a retry can fix and errors only a person can is laid out for every send-time error in retry, backoff and error handling for agent email; a delivery-time 5xx belongs firmly in the second group.
It also looks, to the receiving system, like what spammers do: the same recipient, the same sender, a series of lightly varied bodies. Receiving systems are built to notice that pattern.
The handler is short. Poll the event stream, and when a recipient bounces, mark the task blocked, record the address as off-limits for the agent, and hand it to a person:
const API = "https://api.emailforagents.ai/v1";
async function drainDeliveryOutcomes(projectId: string, cursor: string | null) {
const url = new URL(`${API}/projects/${projectId}/events`);
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.EFA_API_KEY}` },
});
if (res.status === 410) return { cursor: null }; // cursor_expired: rescan from start
if (!res.ok) throw new Error(`events poll failed: ${res.status}`);
const page = await res.json();
for (const event of page.data) {
if (event.type !== "message.delivery_updated") continue;
const { message_id, recipient, status } = event.data;
if (!["bounced", "complained", "suppressed"].includes(status)) continue;
// Idempotent on event.id: the same outcome may be seen twice.
await tasks.markBlocked({ messageId: message_id, recipient, status, eventId: event.id });
await agentMemory.forbidRecipient(recipient, { until: "human_review" });
await notifyOperator(
`Mail to ${recipient} was refused by the receiving server (${status}). ` +
`The agent has stopped. Contact the recipient directly before any resend.`,
);
}
return { cursor: page.next_cursor ?? cursor };
}
tasks, agentMemory and notifyOperator are yours. The events endpoint, the
message.delivery_updated type, the three data fields and the next_cursor field are
the product’s. The key needs the events:read scope.
forbidRecipient is the load-bearing line. The agent’s own send tool should refuse an
address on that list before it calls the API, and the model should be told why in the
tool result, in plain words: “this recipient’s server refused our mail; a human is
handling it; do not contact them”. If you want the platform to enforce it as well,
an owner or admin can add the address to the inbox policy’s block_addresses through
PUT /v1/projects/{project_id}/inboxes/{inbox_id}/policy. A send to a blocked address
is then denied as 403 policy_denied with the reason code blocked_recipient,
regardless of what the model decides, and blocks always win over allow entries.
Restricting which domains an agent can email
covers the full policy write, including the expected_version guard.
Do not treat a delayed status as a bounce. It is the soft signal that the provider is
still retrying, and it ranks below delivered. It is information for a human, not a
reason to send anything.
What the human does with a 5.7.1
The person you surfaced it to has three real options, in this order.
Find out which variant it was. You do not have the bounce text, so ask the
recipient through another channel, which is also what Microsoft’s documentation tells
senders to do. “Our message to your procurement address was refused by your mail
system” is usually enough for their administrator to find the rule. If the recipient
uses Google Workspace and their administrator sees gcdp on the rejection, it was their
own rule.
If it was recipient policy, the fix is on their side. Their administrator allows the sending address, adds it to a group’s permitted senders, or relaxes an external-sender restriction. Nothing about your agent, your prompt or DNS changes. Once they confirm, a person sends again, deliberately, with a new idempotency key, because this is a new decision and not a retry of the old one. Idempotency keys explain why reusing the old key would replay the stored response rather than send.
If it was content, a person decides whether the email should exist. Sometimes the
filter was wrong and the human rewrites and sends it. Often the filter was right, and
the correct outcome is that this email is not sent. Either way the rewrite is a human
act. If the agent is producing mail that filters reject, the durable fix is upstream: set the
inbox’s approval_mode to "always" so every outbound message waits for a person, as
human approval for agent email describes, or keep
an allowlist of the domains the agent has any business emailing.
If the recipient reports a relay, IP-blocklist or reputation rejection, that concerns the shared sending infrastructure and is not something a customer can fix. Report it to support@agents.emailforagents.ai with the message ID and the recipient domain, and hold further mail to that domain until it is resolved. There is no delivery guarantee on any provider, this one included, and nobody can promise that a given receiving system will accept mail from a given sender.
You cannot rehearse any of this in a test project. Test inboxes use a simulated transport and cannot reach an internet recipient, so a real receiving server never gets the chance to refuse anything. Build the handler against the event shape above, and check it the first time a real bounce arrives in live. The developer surface lists the endpoints, scopes and events this relies on.
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.
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.
Restrict Which Domains Your Agent Can Email
Restrict which domains your agent can email with an allowlist it cannot argue with: the policy call, how recipients match, and what a denied send returns.