Skip to content
machinemail

Organizations and pods

How MachineMail groups resources. An organization owns everything; pods split it into isolated groups, such as one per end customer.

Every resource in MachineMail belongs to exactly one organization. Inside an organization, inboxes, domains, webhooks, drafts and list entries also belong to a pod. Pods let you partition one account into isolated groups without creating separate accounts.

Organizations

You get an organization when you first sign in to the dashboard. It comes with a default pod and a first API key. An agent that registers itself through auth.md and is later claimed joins the organization of the person who claims it.

Terminal
curl https://api.machinemail.dev/v0/organizations \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response
{
  "organization_id": "org_01m4fy8h140000efvagszh6rpx",
  "name": "Acme Agents",
  "slug": "acme-agents",
  "plan": "free",
  "inbox_count": 2,
  "domain_count": 1,
  "usage": {
    "message_count": 3,
    "storage_bytes": 1049,
    "messages_received_30d": 1,
    "messages_sent_30d": 2
  },
  "created_at": "2026-10-09T09:00:15.780Z",
  "updated_at": "2026-10-09T09:00:15.780Z"
}

GET /v0/organization is an alias. Rename the organization with PATCH /v0/organizations and a body of { "name": "..." }.

Inbox count is never a quota. The only cap on inboxes is on the shared machinemail.dev domain, where one organization can hold at most 1,000 addresses. Inboxes on your own domains are not capped.

Note

Deleting an organization through the API is not yet available.

Who am I?

GET /v0/auth/me tells you what the current credential can reach. It is useful for an agent that was handed a key and needs to know whether it is org-, pod- or inbox-scoped.

Response
{
  "scope_type": "organization",
  "scope_id": "org_01m4fy8h140000efvagszh6rpx",
  "organization_id": "org_01m4fy8h140000efvagszh6rpx",
  "api_key_id": "key_01m4fy8h150001xjwqzx3kasz7"
}

scope_type is organization, pod or inbox. For a pod or inbox key, pod_id and inbox_id are set and scope_id is the narrowest of them.

Members

An organization keeps all product data: inboxes, pods, domains, keys and webhooks. One person can belong to several organizations.

Members sign in with a one-time code, a social provider such as GitHub, or their organization’s single sign-on. Organizations are created when the first member signs up, and others join by invitation. See Authentication for sign-in.

Roles

RoleCan
ownerEverything an admin can, and grant, change or remove the owner role
adminInvite people, change roles and remove members
memberUse the organization, but not manage people

An organization always has at least one owner. A change that would leave none returns 409.

Managing members

These routes need a dashboard session. An API key gets 403, except on GET /v0/organizations/members, which organization-scoped API keys can also call.

MethodPathDoes
GET/v0/organizations/membersLists members
PATCH/v0/organizations/members/{user_id}Changes a member’s role, with { "role": "..." }
DELETE/v0/organizations/members/{user_id}Removes a member
GET/v0/organizations/invitationsLists pending invitations
POST/v0/organizations/invitationsInvites { "email": "...", "role": "..." }
POST/v0/organizations/invitations/{invitation_id}/resendSends the invitation again
DELETE/v0/organizations/invitations/{invitation_id}Revokes an invitation

Deleting your own user_id is leaving the organization. Your session then moves to another organization you belong to.

An invitation defaults to the member role, expires after 7 days and works only for the address it was sent to. Inviting someone who is already a member or already invited returns 409.

The invitation email links to /invite/{token} on MachineMail, where the person signs in (with an email code, a social provider or SSO), which accepts it. See Invitations. On a server without email-code sign-in (self-hosted or development), which signs people in with magic links, the invitation email is a sign-in link, and signing in with that address joins the organization.

Removing a member

Removing a member, from the dashboard or the API, ends their access at once:

  • Their dashboard sessions for the organization stop working, and event streams they have open (/v0/events/stream, /v0/ws) close within about 30 seconds.
  • API keys they created there are revoked, including CLI keys from mm login and keys minted with one of their keys.
  • Apps they connected with OAuth, such as MCP clients, are disconnected.
  • Invitations they sent that are still pending are revoked.

Keys created by other people keep working. Agents they claimed through auth.md are not affected. Keys that record no creator are not revoked: those made before organizations had members, and those minted with a claimed auth.md agent’s token (or with a key that records no creator). Review them under API keys.

Membership changes are recorded in the event log as the audit events member.invited, member.role_changed and member.removed. They are not delivered to webhooks.

Pods

A pod is a named group of resources. Every organization has one pod with is_default: true, called “Default Pod”. Resources you create without naming a pod go there.

FieldTypeNotes
pod_idstringpod_ prefix.
organization_idstringOwning organization.
namestringDefaults to “Untitled pod”.
is_defaultbooleantrue for the pod every organization starts with.
client_idstringYour idempotency key, if you set one.
created_at, updated_atstringISO 8601 UTC.

Managing pods

MethodPathPermission
GET/v0/podspod_read
POST/v0/podspod_create
GET/v0/pods/{pod_id}pod_read
PATCH/v0/pods/{pod_id}pod_update
DELETE/v0/pods/{pod_id}pod_delete
Terminal
curl https://api.machinemail.dev/v0/pods \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Corp", "client_id": "customer-acme"}'
create-pod.ts
const res = await fetch('https://api.machinemail.dev/v0/pods', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MACHINEMAIL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ name: 'Acme Corp', client_id: 'customer-acme' }),
});
const pod = await res.json();
create_pod.py
import os, requests

pod = requests.post(
    "https://api.machinemail.dev/v0/pods",
    headers={"Authorization": f"Bearer {os.environ['MACHINEMAIL_API_KEY']}"},
    json={"name": "Acme Corp", "client_id": "customer-acme"},
).json()
Response
{
  "pod_id": "pod_01m4fy8hme0000t818y6dwskk4",
  "organization_id": "org_01m4fy8h140000efvagszh6rpx",
  "name": "Acme Corp",
  "is_default": false,
  "client_id": "customer-acme",
  "created_at": "2026-10-09T09:00:16.398Z",
  "updated_at": "2026-10-09T09:00:16.398Z"
}

Rules:

  • Only organization-scoped credentials can create pods.
  • client_id makes creation idempotent. Sending the same client_id again returns the existing pod with status 200 instead of 201.
  • The default pod cannot be deleted.
  • A pod that still has inboxes cannot be deleted. Delete its inboxes first, or you get 409 conflict.

Working inside a pod

There are two ways to act within a single pod.

Pod-scoped API keys. Mint a key at POST /v0/pods/{pod_id}/api-keys. Every request made with it is confined to that pod: lists only show the pod’s resources, creates land in the pod, and anything outside it returns 404. See API keys.

Pod-prefixed routes. With an organization key, prefix any resource path with /v0/pods/{pod_id} to narrow the request to that pod:

Terminal
# Inboxes in one pod
curl https://api.machinemail.dev/v0/pods/pod_01m4fy8hme0000t818y6dwskk4/inboxes \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"

The prefix works for inboxes, threads, drafts, webhooks, domains, lists, metrics, events and api-keys. You can also pass pod_id in the body of POST /v0/inboxes to create an inbox in another pod with an organization key.

Scope and 404s

Resources outside a credential’s scope return 404 not_found, never 403. A pod key asking for an inbox in another pod, or in another organization, sees the same response as for an inbox that does not exist. 403 forbidden is reserved for operations the key’s scope or permissions rule out, such as a pod key trying to create a pod.

Next