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.
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
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.
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
| Role | Can |
|---|---|
owner | Everything an admin can, and grant, change or remove the owner role |
admin | Invite people, change roles and remove members |
member | Use 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.
| Method | Path | Does |
|---|---|---|
GET | /v0/organizations/members | Lists 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/invitations | Lists pending invitations |
POST | /v0/organizations/invitations | Invites { "email": "...", "role": "..." } |
POST | /v0/organizations/invitations/{invitation_id}/resend | Sends 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 loginand 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.
| Field | Type | Notes |
|---|---|---|
pod_id | string | pod_ prefix. |
organization_id | string | Owning organization. |
name | string | Defaults to “Untitled pod”. |
is_default | boolean | true for the pod every organization starts with. |
client_id | string | Your idempotency key, if you set one. |
created_at, updated_at | string | ISO 8601 UTC. |
Managing pods
| Method | Path | Permission |
|---|---|---|
GET | /v0/pods | pod_read |
POST | /v0/pods | pod_create |
GET | /v0/pods/{pod_id} | pod_read |
PATCH | /v0/pods/{pod_id} | pod_update |
DELETE | /v0/pods/{pod_id} | pod_delete |
Rules:
- Only organization-scoped credentials can create pods.
client_idmakes creation idempotent. Sending the sameclient_idagain returns the existing pod with status200instead of201.- 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:
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.