Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fern/definition/inboxes/__package__.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ navigation:
- threads.yml
- messages.yml
- drafts.yml
- lists.yml
- metrics.yml

imports:
Expand Down
59 changes: 59 additions & 0 deletions fern/definition/inboxes/lists.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json

imports:
global: ../__package__.yml
inboxes: __package__.yml
lists: ../lists.yml

service:
url: Http
base-path: /inboxes/{inbox_id}/lists/{direction}/{type}
path-parameters:
inbox_id: inboxes.InboxId
direction: lists.Direction
type: lists.ListType
auth: true

endpoints:
list:
method: GET
path: ""
display-name: List Entries
request:
name: InboxListListEntriesRequest
query-parameters:
limit: optional<global.Limit>
page_token: optional<global.PageToken>
response: lists.PodListListEntriesResponse

get:
method: GET
path: /{entry}
display-name: Get List Entry
path-parameters:
entry:
type: string
docs: Email address or domain.
response: lists.PodListEntry
errors:
- global.NotFoundError

create:
method: POST
path: ""
display-name: Create List Entry
request: lists.CreateListEntryRequest
response: lists.PodListEntry
errors:
- global.ValidationError

delete:
method: DELETE
path: /{entry}
display-name: Delete List Entry
path-parameters:
entry:
type: string
docs: Email address or domain.
errors:
- global.NotFoundError
1 change: 1 addition & 0 deletions fern/definition/lists.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ types:
enum:
- send
- receive
- reply
docs: Direction of list entry.

ListType:
Expand Down
113 changes: 104 additions & 9 deletions fern/pages/core-concepts/lists.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ description: Learn how to use Lists to control which email addresses and domains

## What are Lists?

`Lists` allow you to filter emails by allowing or blocking specific email addresses or domains. There are four list types based on two dimensions:
`Lists` allow you to filter emails by allowing or blocking specific email addresses or domains. There are six list types based on two dimensions:

- **Direction**: `send` or `receive`
- **Direction**: `send`, `receive`, or `reply`
- **Type**: `allow` or `block`

| List | Description |
Expand All @@ -18,9 +18,30 @@ description: Learn how to use Lists to control which email addresses and domains
| Receive block | Reject emails from these addresses or domains |
| Send allow | Only send emails to these addresses or domains |
| Send block | Prevent sending emails to these addresses or domains |
| Reply allow | Only accept reply emails from these addresses or domains |
| Reply block | Reject reply emails from these addresses or domains |

Each entry can be either a full email address (e.g., `partner@example.com`) or an entire domain (e.g., `example.com`).

## Scoping

Lists can be scoped at three levels. A narrower scope overrides a broader one:

- **Organization**: Applies to all pods and inboxes in your org. Manage with `client.lists`.
- **Pod**: Applies to all inboxes in a pod. Manage with `client.pods.lists`.
- **Inbox**: Applies to a single inbox. Manage with `client.inboxes.lists`.

When evaluating whether to allow or block a message, AgentMail checks the most specific scope first. If an inbox-level list has a match, pod and org lists are not checked.

## Reply lists

The `reply` direction handles inbound emails that are replies to previous outbound messages. When an inbound email arrives, AgentMail checks the `In-Reply-To` header to determine whether it is a reply:

- **If the email is a reply** to a previous outbound message, only the reply lists are checked. The receive lists are skipped entirely.
- **If the email is not a reply**, only the receive lists are checked. The reply lists are skipped entirely.

The two branches are completely separate. By default, when reply lists are empty, all replies are allowed. You can restrict replies by populating reply allow or reply block lists.

## SDK examples

### List entries
Expand Down Expand Up @@ -87,60 +108,134 @@ await client.lists.delete("receive", "allow", "partner@example.com");
```
</CodeBlocks>

### Inbox-scoped lists

Manage lists for a specific inbox. The same operations are available at the inbox level.

<CodeBlocks>
```python title="Python"
# Add to an inbox-level receive allowlist
client.inboxes.lists.create(
"inbox_id", "receive", "allow", entry="vip@example.com"
)

# List inbox-level entries
entries = client.inboxes.lists.list("inbox_id", "receive", "allow")
```

```typescript title="TypeScript"
// Add to an inbox-level receive allowlist
await client.inboxes.lists.create("inbox_id", "receive", "allow", {
entry: "vip@example.com",
});

// List inbox-level entries
const entries = await client.inboxes.lists.list("inbox_id", "receive", "allow");
```
</CodeBlocks>

### Reply lists

Control which addresses can send replies to an inbox's outbound messages.

<CodeBlocks>
```python title="Python"
# Only allow replies from a specific domain
client.lists.create("reply", "allow", entry="nobu.com")
```

```typescript title="TypeScript"
// Only allow replies from a specific domain
await client.lists.create("reply", "allow", { entry: "nobu.com" });
```
</CodeBlocks>

## Copy for Cursor / Claude

Copy one of the blocks below into Cursor or Claude for complete Lists API knowledge in one shot.

<CodeBlocks>
```python title="Python"
"""
AgentMail Lists copy into Cursor/Claude.
AgentMail Lists, copy into Cursor/Claude.

Filter emails by allow/block for send/receive. Types: receive|send × allow|block.
Filter emails by allow/block for send/receive/reply. 6 types: receive|send|reply x allow|block.
Lists can be scoped to org, pod, or inbox level.

API reference:
API reference (org-level):
- lists.list(direction, type, limit?, page_token?)
- lists.create(direction, type, entry, reason?) reason only for block lists
- lists.create(direction, type, entry, reason?), reason only for block lists
- lists.get(direction, type, entry)
- lists.delete(direction, type, entry)

Pod-level: pods.lists.list(pod_id, direction, type, ...) and same for create/get/delete.
Inbox-level: inboxes.lists.list(inbox_id, direction, type, ...) and same for create/get/delete.

Entry: full email (user@domain.com) or domain (example.com).
Cascade: inbox > pod > org (most specific scope wins).
Reply lists: inbound replies (detected via In-Reply-To) check reply lists, not receive lists.
"""
from agentmail import AgentMail

client = AgentMail(api_key="YOUR_API_KEY")

# Org-level lists
entries = client.lists.list("receive", "allow", limit=10)
client.lists.create("receive", "allow", entry="partner@example.com")
client.lists.create("receive", "block", entry="spam@example.com", reason="spam")
e = client.lists.get("receive", "allow", entry="partner@example.com")
client.lists.delete("receive", "allow", entry="partner@example.com")

# Reply lists
client.lists.create("reply", "allow", entry="nobu.com")

# Inbox-level lists
client.inboxes.lists.create("inbox_id", "receive", "allow", entry="vip@example.com")
inbox_entries = client.inboxes.lists.list("inbox_id", "receive", "allow")
```

```typescript title="TypeScript"
/**
* AgentMail Lists copy into Cursor/Claude.
* AgentMail Lists, copy into Cursor/Claude.
*
* Filter emails by allow/block for send/receive. Types: receive|send × allow|block.
* Filter emails by allow/block for send/receive/reply. 6 types: receive|send|reply x allow|block.
* Lists can be scoped to org, pod, or inbox level.
*
* API reference:
* API reference (org-level):
* - lists.list(direction, type, { limit?, pageToken? })
* - lists.create(direction, type, { entry, reason? }) — reason only for block
* - lists.get(direction, type, entry)
* - lists.delete(direction, type, entry)
*
* Pod-level: pods.lists.list(podId, direction, type, ...) and same for create/get/delete.
* Inbox-level: inboxes.lists.list(inboxId, direction, type, ...) and same for create/get/delete.
*
* Entry: full email or domain.
* Cascade: inbox > pod > org (most specific scope wins).
* Reply lists: inbound replies (detected via In-Reply-To) check reply lists, not receive lists.
*/
import { AgentMailClient } from "agentmail";

const client = new AgentMailClient({ apiKey: "YOUR_API_KEY" });

async function main() {
// Org-level lists
const entries = await client.lists.list("receive", "allow", { limit: 10 });
await client.lists.create("receive", "allow", { entry: "partner@example.com" });
await client.lists.create("receive", "block", { entry: "spam@example.com", reason: "spam" });
const e = await client.lists.get("receive", "allow", "partner@example.com");
await client.lists.delete("receive", "allow", "partner@example.com");

// Reply lists
await client.lists.create("reply", "allow", { entry: "nobu.com" });

// Inbox-level lists
await client.inboxes.lists.create("inbox_id", "receive", "allow", {
entry: "vip@example.com",
});
const inboxEntries = await client.inboxes.lists.list(
"inbox_id", "receive", "allow"
);
}
main();
```
Expand Down
55 changes: 54 additions & 1 deletion fern/pages/knowledge-base/allowlists-blocklists.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ Allowlists and blocklists let you control who your AI agent can communicate with

## How Lists work

AgentMail provides four list types based on two dimensions, **direction** (send or receive) and **type** (allow or block):
AgentMail provides six list types based on two dimensions, **direction** (send, receive, or reply) and **type** (allow or block):

| List | What it does |
| --- | --- |
| Receive allow | Only accept emails from these addresses or domains |
| Receive block | Reject emails from these addresses or domains |
| Send allow | Only send emails to these addresses or domains |
| Send block | Prevent sending emails to these addresses or domains |
| Reply allow | Only accept reply emails from these addresses or domains |
| Reply block | Reject reply emails from these addresses or domains |

Each entry can be either a full email address (e.g., `partner@example.com`) or an entire domain (e.g., `example.com`).

Expand Down Expand Up @@ -54,6 +56,40 @@ entries = client.lists.list("receive", "allow")
client.lists.delete("receive", "block", entry="spam@example.com")
```

### Inbox-scoped lists

Lists can be applied at the inbox level for per-inbox filtering. For example, one inbox might only accept emails from `meta.com`, while another inbox in the same pod accepts from `partner.com`.

```python title="Python"
# This inbox only accepts emails from meta.com
client.inboxes.lists.create(
"support@yourdomain.com", "receive", "allow", entry="meta.com"
)

# A different inbox accepts from partner.com
client.inboxes.lists.create(
"sales@yourdomain.com", "receive", "allow", entry="partner.com"
)
```

Inbox-level lists override pod-level and org-level lists. If the inbox-level list has a match, pod and org lists are not checked.

### Reply lists

Reply lists control filtering for inbound emails that are replies to previous outbound messages. When an inbound email arrives, AgentMail checks the `In-Reply-To` header:

- If the email **is a reply** to a previous outbound message, only the reply lists are checked. Receive lists are skipped.
- If the email **is not a reply**, only the receive lists are checked. Reply lists are skipped.

By default, when reply lists are empty, all replies are allowed. This is useful for agents that initiate outbound emails (such as making reservations or sending inquiries) and need to receive the responses.

```python title="Python"
# Block replies from a specific sender
client.inboxes.lists.create(
"agent@yourdomain.com", "reply", "block", entry="spam-restaurant.com"
)
```

## Common patterns for agents

**Outreach agent:** Use a send allowlist to restrict your agent to only email verified prospects. This prevents the agent from accidentally emailing the wrong people.
Expand All @@ -79,13 +115,30 @@ client.lists.create("receive", "allow", entry="trusted-partner.com")
client.lists.create("receive", "block", entry="spam-domain.com", reason="spam")
```

**Task-oriented agent (making reservations, bookings, etc.):** Use a receive allowlist to restrict inbound to your organization's domain, but leave reply lists open (default) so replies to agent-initiated outbound emails come through.

```python title="Python"
# Only accept unsolicited emails from your org
client.inboxes.lists.create(
"agent@yourdomain.com", "receive", "allow", entry="yourdomain.com"
)

# Replies to emails the agent sends (e.g., restaurant reservations)
# are allowed by default, no reply list configuration needed.
# Optionally block specific reply senders:
client.inboxes.lists.create(
"agent@yourdomain.com", "reply", "block", entry="spam-restaurant.com"
)
```

## Why this matters for agents

Without guardrails, an autonomous agent could email the wrong people, respond to phishing attempts, or get caught in infinite email loops with another bot. Lists are your safety rails. They are especially important for:

- **Production agents** operating with minimal human oversight
- **Outreach agents** that should only contact approved recipients
- **Support agents** that should only respond to known customers
- **Task-oriented agents** that send outbound emails and need replies to come through
- **Any agent** that needs protection from spam, phishing, or abuse

For more details on the Lists API, see the [Lists core concept](/lists) documentation.
Loading