For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
contact@agentmail.ccDiscord
DocumentationAPI ReferenceKnowledge BaseChangelog
DocumentationAPI ReferenceKnowledge BaseChangelog
  • Get Started
    • Welcome
    • Introduction
    • Quickstart
  • Core Concepts
    • Inboxes
    • Messages
    • Threads
    • Drafts
    • Labels
    • Lists
    • Attachments
    • Pods
    • Permissions
  • Integrations
    • Agent Onboarding
    • Skills
    • MCP
    • CLI
    • Google ADK
    • OpenClaw
    • Replit
    • x402
    • MPP
    • LiveKit
    • Sim.ai
  • Guides
    • Sending & Receiving Email
    • IMAP & SMTP
    • Multi-Tenancy
  • Webhooks
    • Overview
    • Events
    • Setup Guide
    • Verifying Webhooks
  • WebSockets
    • Overview
    • Quickstart
  • Best Practices
    • Email Deliverability
    • Idempotency
  • Examples
    • Github Repo Agent
    • Auto Reply Agent
    • Smart Labeling Agent
    • Sales Agent (WebSocket)
    • Live AgentMail Examples
  • Resources
    • FAQ
    • Talon Reply Extraction
    • Community
    • Support
LogoLogo
contact@agentmail.ccDiscord
On this page
  • Pods = Tenant Isolation
  • Scoped API Keys
  • Pod-scoped keys
  • Inbox-scoped keys
  • Routing Webhook Events
  • Full Onboarding Flow
Guides

Guide: Multi-Tenancy

Pods, scoped keys, and event routing for your customers.
Was this page helpful?
Edit this page
Previous

Using Custom Domains

Strengthen your agent's identity and improve deliverability with your own domain.
Next
Built with

If you’re building a platform where each of your customers needs their own email infrastructure, this is how you set it up. The basic idea: create a Pod per customer, give them a scoped API key, and route webhook events to the right place.

Pods = Tenant Isolation

Every tenant gets their own Pod. All their resources (Inboxes, Domains, Threads, Drafts) live inside it and are completely isolated from other pods. Check out the Pods page for the full breakdown.

1from agentmail import AgentMail
2
3client = AgentMail()
4
5# Use client_id to map to your internal tenant ID so
6# you don't need to maintain a separate mapping table
7pod = client.pods.create(client_id="tenant-acme-123")

Then provision their resources:

1inbox = client.pods.inboxes.create(
2 pod.pod_id,
3 username="support",
4 display_name="Acme Support"
5)
6
7domain = client.pods.domains.create(pod.pod_id, domain="acme.com")

Scoped API Keys

By default, API keys are organization-level and can access everything across all pods. Scoped API keys restrict access to a single pod or a single inbox. If a key is scoped to Acme’s pod, it can only touch Acme’s resources. Nothing else.

This is useful when you want to hand a key to a tenant’s service or agent without exposing your whole org.

Pod-scoped keys

Pod-scoped keys can access all resources within a pod (inboxes, threads, drafts, domains).

1# Create a key that can only access Acme's pod
2scoped_key = client.pods.api_keys.create(
3 pod.pod_id,
4 name="acme-service-key"
5)
6
7# This is the only time you'll see the full key, so store it
8print(scoped_key.api_key)

Inbox-scoped keys

Inbox-scoped keys are even more restrictive: they only grant access to a single inbox and its threads, messages, and drafts. Use these when an agent or integration only needs to operate on one address.

1# Create a key that can only access the support inbox
2inbox_key = client.inboxes.api_keys.create(
3 inbox.inbox_id,
4 name="support-inbox-key"
5)
6
7print(inbox_key.api_key)

The full API key is only returned once at creation. If you lose it, delete it and create a new one.

You can list and delete scoped keys for any pod or inbox:

1# Pod-scoped keys
2keys = client.pods.api_keys.list(pod.pod_id)
3client.pods.api_keys.delete(pod.pod_id, scoped_key.api_key_id)
4
5# Inbox-scoped keys
6inbox_keys = client.inboxes.api_keys.list(inbox.inbox_id)
7client.inboxes.api_keys.delete(inbox.inbox_id, inbox_key.api_key_id)

Routing Webhook Events

You probably don’t want a single webhook catching events for every tenant. When creating a Webhook, you can scope it to specific pod_ids or inbox_ids so events only fire for the resources you care about.

1# Only fires for events in Acme's pod
2webhook = client.webhooks.create(
3 url="https://your-server.com/webhooks/acme",
4 event_types=["message.received", "message.sent"],
5 pod_ids=[pod.pod_id]
6)
7
8# Or narrow it down to specific inboxes
9webhook = client.webhooks.create(
10 url="https://your-server.com/webhooks/acme-support",
11 event_types=["message.received"],
12 inbox_ids=[inbox.inbox_id]
13)

Full Onboarding Flow

Here’s what onboarding a new tenant looks like end to end:

1from agentmail import AgentMail
2
3client = AgentMail()
4
5def onboard_tenant(tenant_id: str, domain_name: str):
6 # Create isolated pod
7 pod = client.pods.create(client_id=tenant_id)
8
9 # Provision inbox + domain
10 inbox = client.pods.inboxes.create(
11 pod.pod_id,
12 username="support",
13 display_name=f"{tenant_id} Support"
14 )
15 domain = client.pods.domains.create(pod.pod_id, domain=domain_name)
16
17 # Pod-scoped key for the tenant
18 key = client.pods.api_keys.create(pod.pod_id, name=f"{tenant_id}-key")
19
20 # Inbox-scoped key for the support inbox
21 inbox_key = client.inboxes.api_keys.create(
22 inbox.inbox_id, name=f"{tenant_id}-support-key"
23 )
24
25 # Webhook for their events
26 webhook = client.webhooks.create(
27 url=f"https://your-server.com/webhooks/{tenant_id}",
28 event_types=["message.received"],
29 pod_ids=[pod.pod_id]
30 )
31
32 return {
33 "pod_id": pod.pod_id,
34 "inbox_id": inbox.inbox_id,
35 "pod_api_key": key.api_key, # deliver securely to tenant
36 "inbox_api_key": inbox_key.api_key,
37 "webhook_id": webhook.webhook_id,
38 }