From 59ec29a17e8f14c31ee5a26bc9e826990a3ad4a4 Mon Sep 17 00:00:00 2001 From: duharry0915 Date: Mon, 16 Mar 2026 12:56:20 -0700 Subject: [PATCH 1/2] adding reply allow list and inbox level allow list --- fern/definition/inboxes/__package__.yml | 1 + fern/definition/inboxes/lists.yml | 59 +++++++++ fern/definition/lists.yml | 1 + fern/pages/core-concepts/lists.mdx | 113 ++++++++++++++++-- .../knowledge-base/allowlists-blocklists.mdx | 55 ++++++++- 5 files changed, 219 insertions(+), 10 deletions(-) create mode 100644 fern/definition/inboxes/lists.yml diff --git a/fern/definition/inboxes/__package__.yml b/fern/definition/inboxes/__package__.yml index 53c2c60..fdab38c 100644 --- a/fern/definition/inboxes/__package__.yml +++ b/fern/definition/inboxes/__package__.yml @@ -4,6 +4,7 @@ navigation: - threads.yml - messages.yml - drafts.yml + - lists.yml - metrics.yml imports: diff --git a/fern/definition/inboxes/lists.yml b/fern/definition/inboxes/lists.yml new file mode 100644 index 0000000..2296982 --- /dev/null +++ b/fern/definition/inboxes/lists.yml @@ -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 + page_token: optional + 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 diff --git a/fern/definition/lists.yml b/fern/definition/lists.yml index 7e56c27..aceed0c 100644 --- a/fern/definition/lists.yml +++ b/fern/definition/lists.yml @@ -8,6 +8,7 @@ types: enum: - send - receive + - reply docs: Direction of list entry. ListType: diff --git a/fern/pages/core-concepts/lists.mdx b/fern/pages/core-concepts/lists.mdx index b1fd474..b19a919 100644 --- a/fern/pages/core-concepts/lists.mdx +++ b/fern/pages/core-concepts/lists.mdx @@ -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 | @@ -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 @@ -87,6 +108,48 @@ await client.lists.delete("receive", "allow", "partner@example.com"); ``` +### Inbox-scoped lists + +Manage lists for a specific inbox. The same operations are available at the inbox level. + + +```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"); +``` + + +### Reply lists + +Control which addresses can send replies to an inbox's outbound messages. + + +```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" }); +``` + + ## Copy for Cursor / Claude Copy one of the blocks below into Cursor or Claude for complete Lists API knowledge in one shot. @@ -94,53 +157,85 @@ Copy one of the blocks below into Cursor or Claude for complete Lists API knowle ```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(); ``` diff --git a/fern/pages/knowledge-base/allowlists-blocklists.mdx b/fern/pages/knowledge-base/allowlists-blocklists.mdx index 4552365..459966f 100644 --- a/fern/pages/knowledge-base/allowlists-blocklists.mdx +++ b/fern/pages/knowledge-base/allowlists-blocklists.mdx @@ -8,7 +8,7 @@ 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 | | --- | --- | @@ -16,6 +16,8 @@ AgentMail provides four list types based on two dimensions, **direction** (send | 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`). @@ -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. @@ -79,6 +115,22 @@ 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="meta.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: @@ -86,6 +138,7 @@ Without guardrails, an autonomous agent could email the wrong people, respond to - **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. From b137727da768919224c9aa6f124b6d7ce9cef2d2 Mon Sep 17 00:00:00 2001 From: duharry0915 Date: Mon, 16 Mar 2026 13:20:57 -0700 Subject: [PATCH 2/2] change the example name --- fern/pages/knowledge-base/allowlists-blocklists.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fern/pages/knowledge-base/allowlists-blocklists.mdx b/fern/pages/knowledge-base/allowlists-blocklists.mdx index 459966f..c79a9f9 100644 --- a/fern/pages/knowledge-base/allowlists-blocklists.mdx +++ b/fern/pages/knowledge-base/allowlists-blocklists.mdx @@ -120,7 +120,7 @@ client.lists.create("receive", "block", entry="spam-domain.com", reason="spam") ```python title="Python" # Only accept unsolicited emails from your org client.inboxes.lists.create( - "agent@yourdomain.com", "receive", "allow", entry="meta.com" + "agent@yourdomain.com", "receive", "allow", entry="yourdomain.com" ) # Replies to emails the agent sends (e.g., restaurant reservations)