Control who can email your agent
Understand how AgentMail classifies inbound mail, narrow who gets through with allow and block lists, and inspect what was filtered.
Understand how inbound mail is classified
Every email runs through these two checks:
- a virus scan
- DMARC, when the message fails it and the sender’s policy asks receivers to enforce it (a policy of
quarantineorreject)
A DMARC failure gets one more check: AgentMail validates the message’s ARC chain, and when a trusted forwarder (google.com, microsoft.com, outlook.com, or icloud.com) sealed it, the original sender’s authentication results replace the failing ones. Forwarding is what breaks SPF and DKIM in transit, so this is what lets mail auto-forwarded from a Gmail or Outlook account pass authentication.
Rejected mail is never stored. No bounce goes back either, so the sender’s mail server may still report the message as delivered.
Everything else is delivered. Mail that passes every check shows up when you list messages, carrying just the received label. The remaining checks each add a label instead of rejecting:
unauthenticatedmeans the sender could not be verified (usually, the sender has no SPF, DKIM, or DMARC set up).spammeans the message failed spam screening.blockedmeans your lists reject the sender. The rest of this page covers these lists.
One message can collect several of these labels.
The spam, unauthenticated, and blocked labels are hidden from message and thread listings by default, so your agent starts with a clean view and you opt into the rest.
The list check picks one pair of lists per message:
- A message that starts a fresh conversation is checked against the inbox’s
receivelists. - A message in a conversation your inbox has already sent into is checked against its
replylists.
Empty reply lists accept every reply. An agent that emails people first keeps getting their answers even when its receive allow list admits almost no one.
Allow or block a sender
By default, an inbox accepts mail from anyone. An entry defines who a list matches. An entry containing @ matches that one address. Anything else is a domain and matches every address on it. What a match does depends on the list type:
- A
blockentry rejects matching senders and changes nothing else. - An
allowentry changes the default. Once an allow list has any entry, only matching senders get through, and everyone else is blocked.
The calls here manage a single inbox. You can also create entries for a whole pod or organization, and they apply to every inbox inside. When several entries match one sender, the most specific wins:
- An inbox entry beats a pod entry, which beats an organization entry.
- Within one scope, an exact address entry beats a domain entry.
So an inbox-level allow for one address carves an exception into an organization-wide block on its domain. The allow default applies across scopes too, and a single organization-wide receive allow entry blocks unmatched new senders for every inbox in the organization.
inbox_id, direction, type, and entry are required, and reason is optional. The full list is below the code.
agentmail inboxes:lists create \
--inbox-id "example@agentmail.to" \
--direction receive \
--type block \
--entry blocked-sender@example.com \
--reason "unwanted newsletter"| Param | Type | What it means |
|---|---|---|
inbox_id | string | The inbox whose list you are editing. |
direction | string | Which traffic the entry filters: receive for new inbound mail, reply for replies, send for outbound recipients. |
type | string | allow or block. |
entry | string | The address or domain to match. |
reason | string | A note of up to 1,024 characters, stored on the entry and returned whenever you read it. |
You get back the stored entry for confirmation.
Check entry_type to confirm how it will match, email for one address or domain for a whole domain. A missing @ turns an address into a domain entry.
scope_key names the exact list the entry landed on (scope, id, direction, and type in one string).
{
"scope_key": "inbox#example@agentmail.to#receive#block",
"organization_id": "1a2b3c4d-5e6f-4a1b-8c2d-3e4f5a6b7c8d",
"pod_id": "1a2b3c4d-5e6f-4a1b-8c2d-3e4f5a6b7c8d",
"inbox_id": "example@agentmail.to",
"direction": "receive",
"list_type": "block",
"entry": "blocked-sender@example.com",
"reason": "unwanted newsletter",
"entry_type": "email", // "email" for one address or "domain" for a whole domain
"created_at": "2026-08-24T15:03:32Z"
}To read or remove the entry later, you will pass entry, direction, and type back exactly as returned here.
List the entries on an inbox
An inbox has six lists, an allow and a block list for each direction:
receive, filtering new inbound mailreply, filtering repliessend, filtering outbound recipients
You read one list at a time, naming its direction and type. An entry lives on the allow or the block list of a direction, never both. AgentMail also keeps a separate layer of read-only suppression entries on the organization’s send block list, added when a recipient hard-bounces, complains of spam, or unsubscribes. They show read_only: true when listed, and the API cannot remove them.
One allow entry is automatic too. CLI sign-up seeds the organization’s send allow list with a single entry, the email address you signed up with, so an unverified organization can email only that address. Completing verification deletes the entry.
Seeing what filtering is active is the first step in debugging why a sender got through or got blocked.
inbox_id, direction, and type are required. Two optional parameters page the results, listed below the code.
agentmail inboxes:lists list \
--inbox-id "example@agentmail.to" \
--direction receive \
--type block| Param | Type | What it means |
|---|---|---|
limit | integer | Number of entries per page. |
page_token | string | Where to resume. Pass the next_page_token from the previous page. |
Entries come back sorted alphabetically by entry, with count for the page and a next_page_token when more remain:
{
"count": 1,
"entries": [
{
"organization_id": "1a2b3c4d-5e6f-4a1b-8c2d-3e4f5a6b7c8d",
"pod_id": "1a2b3c4d-5e6f-4a1b-8c2d-3e4f5a6b7c8d",
"inbox_id": "example@agentmail.to",
"entry": "blocked-sender@example.com",
"reason": "unwanted newsletter",
"direction": "receive",
"list_type": "block",
"entry_type": "email",
"created_at": "2026-08-24T15:03:32Z"
}
]
}Remove an entry
Removing an entry stops it from affecting mail that arrives afterward. Identify it by the same inbox, direction, and type you created it with, plus the entry value itself. Remove an entry at the scope that owns it: an organization-wide entry comes off the organization list, not the inbox list.
agentmail inboxes:lists delete \
--inbox-id "example@agentmail.to" \
--direction receive \
--type block \
--entry blocked-sender@example.comA successful delete returns 204 with an empty body.
Provider-level suppression
AgentMail lists are an application-level decision layer. Amazon SES, the underlying provider, keeps its own suppression layer that can independently decide whether a recipient is eligible for delivery, so a recipient can be absent from every AgentMail block list and still be suppressed by SES. For shared-domain sending, one unsubscribe can create both an AgentMail read-only block entry and an SES suppression entry, and removing or changing an AgentMail list entry does not establish that SES will accept a later send.
A service-created read-only suppression also outranks your own entries: it continues to block the recipient even when an allow entry exists.
AgentMail’s own suppression entries are the record you can inspect: list the organization’s send block entries and look for read_only: true. Removal is a support review, not an API call. Email support@agentmail.cc and the delivery problem is checked before the entry is lifted, which covers the provider-level record in the same review.
See mail that was filtered
Filtered mail is stored, not discarded, so you can audit what your lists and the spam screening caught and rescue false positives. A message listing hides four labels until you ask:
blocked, returned withinclude_blockedspam, returned withinclude_spamunauthenticated, returned withinclude_unauthenticatedtrash, mail moved to trash with thetrashlabel, returned withinclude_trash
The same parameters work when listing threads.
agentmail inboxes:messages list \
--inbox-id "example@agentmail.to" \
--include-blocked=true \
--include-spam=true \
--include-unauthenticated=trueEach flag needs the matching read permission on your API key, like label_blocked_read for include_blocked. Keys created without a permissions restriction hold them all.
Filtered messages appear among the rest, newest first, and the extra label on each says why it was hidden. For a wrongly blocked message, from tells you which entry to remove:
{
"count": 1,
"messages": [
{
"inbox_id": "example@agentmail.to",
"thread_id": "99395ee0-c346-4431-b399-b929294e66bd",
"message_id": "<010001a0344dfda4-32dd7ad5-c3c1-4ec4-b490-231bc3df1bcf-000000@email.amazonses.com>",
"labels": ["received", "unread", "blocked"],
"timestamp": "2026-08-24T15:05:21Z",
"from": "Deals Bot <blocked-sender@example.com>",
"to": ["example@agentmail.to"],
"subject": "Weekly deals blast",
"preview": "This sender is on the block list.",
"size": 4634,
"created_at": "2026-08-24T15:05:21Z",
"updated_at": "2026-08-24T15:05:21Z"
}
]
}Fetch the full body with the message’s message_id before deciding a message was a false positive. And when a message shows up nowhere even with every flag set, it was rejected before delivery rather than filtered (usually a DMARC failure under the sender’s enforcing policy). Rejected mail is not stored, so the fix is on the sender: their domain needs working authentication records.