Supermail
API reference

Accounts

Compose, inspect, label, disconnect, and purge service accounts.

An Account is a provider-neutral, user-owned composition of service bindings. It is the ownership, grant-selector, routing, event-sequence, and erasure boundary; provider health and capabilities belong to its bindings. The current public Section-A surface supports mail bindings. Contacts and calendar roles are reserved but not advertised or grantable.

The account and connection methods are available through Gateway's JSON-RPC boundary.

Account resources

FieldTypeNotes
idstringStable Account selector (account_…)
lifecycleAccountLifecycleWhole-account lifecycle only
service_bindingsServiceBinding[]Role-qualified provider, adapter, lifecycle, generation, freshness, capability, and issue state
presentationAccountPresentationGateway-owned nullable personal label plus positive revision and update time
minebooleanWhether the authenticated user owns the Account
provider_agent_optinProviderAgentOptinWhether AI agents may act on the mailbox's provider; absent without a mail read binding. See AI agent access.

Every successful binding result carries a positive binding_generation. Provider, cursor, freshness, and aggregate health fields do not exist on the Account.

A binding whose credential the provider refused carries one issue with the RECONNECT_ACCOUNT action under the refusal's code (AUTH_EXPIRED, CREDENTIALS_REJECTED, AUTHENTICATION_UNSUPPORTED or CREDENTIAL_UNREADABLE). Each binding reports its own role: a password the IMAP server accepts and the SMTP server refuses marks only the sending binding. next_retry_at is when Traummail can next check the credential by itself; it is absent while a check runs, when only a reconnect can repair it, and once the check is due: it then runs with the binding's next work (the next synchronization, or the next message you send). Until then Traummail tries the credential at most once at a time, never once per queued message, and at most six times for a password (eight for OAuth) before it waits for you; starting synchronizations adds no attempts, each is answered with the refusal at once. (One exception: if an account's receiving and sending bindings use different passwords, a check for a message whose delivery is uncertain can add one attempt per such message.) A binding with no stored credential carries credentials_not_found, also with RECONNECT_ACCOUNT. These five codes are the only ones paired with RECONNECT_ACCOUNT in a binding issue or an operation failure; act on the action, whatever the code. Terminal synchronizations, and their operations, are retained for seven days.

Accounts/list and Accounts/get

Both methods require accounts.read and return only Accounts selected by the caller’s grant. The Gateway joins the owner presentation after authorization; admin and app-owner roster views do not expose it.

List request
POST /rpc
Content-Type: application/json
Authorization: Bearer <access_token>

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "Accounts/list",
  "params": {}
}
List response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "accounts": [{
      "id": "account_01H9X…",
      "lifecycle": "ACCOUNT_LIFECYCLE_ACTIVE",
      "mine": true,
      "presentation": {
        "personal_label": "Family mail",
        "revision": "3",
        "updated_at": "2026-08-21T10:15:00Z"
      },
      "service_bindings": [{
        "id": "binding_01H9Y…",
        "account_id": "account_01H9X…",
        "role": "SERVICE_ROLE_MAIL_READ",
        "provider": "gmail",
        "adapter": "gmail",
        "lifecycle": "SERVICE_BINDING_LIFECYCLE_ACTIVE",
        "generation": "1",
        "working_capabilities": ["CAPABILITY_MAIL_READ"]
      }]
    }]
  }
}

Use Accounts/get with { "account_id": "account_…" } for one selected Account. An unknown or foreign Account is not disclosed.

Create or attach service bindings

There is no Accounts/create compatibility method. Every flow uses the current prepare/start contract:

  1. AccountConnections/prepare preallocates a pending owner edge for a new Account or proves ownership of an attach target. Preparation performs no provider request and receives no credential.
  2. AccountConnections/start repeats the prepared target and supplies explicit authorization-input groups plus role-qualified service selections.
  3. OAuth flows continue through the provider callback. Classical flows call AccountConnections/complete_classical once with the selected IMAP origin and, when MAIL_SEND was selected, explicit SMTP coordinates. Gateway never derives an SMTP hostname from the IMAP hostname.
  4. AccountConnections/get returns independent per-role results. A binding ID is always paired with a positive generation.

Creating a new Account requires a global accounts.create grant. Attaching, reconnecting, disconnecting, or purging bindings requires the corresponding bindings.update or bindings.delete grant. Reuse of an authority is explicit and same-account only; matching addresses or usernames never authorize reuse.

Prepare a new Account
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "AccountConnections/prepare",
  "params": {
    "idempotency_key": "7d770bf5-012d-4698-a608-02cdef931df8",
    "create_account": { "personal_label": "Family mail" }
  }
}
Start explicit mail bindings
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "AccountConnections/start",
  "params": {
    "connection_id": "connection_01H9Z…",
    "idempotency_key": "7d770bf5-012d-4698-a608-02cdef931df8",
    "target": { "create_account_id": "account_01H9X…" },
    "authorization_inputs": [{
      "id": "mail-auth",
      "new_authorization": { "provider_or_realm": "classical" }
    }],
    "services": [
      {
        "authorization_input_id": "mail-auth",
        "role": "SERVICE_ROLE_MAIL_READ",
        "adapter": "imap",
        "requested_capabilities": ["CAPABILITY_MAIL_READ", "CAPABILITY_MAIL_MODIFY"]
      },
      {
        "authorization_input_id": "mail-auth",
        "role": "SERVICE_ROLE_MAIL_SEND",
        "adapter": "smtp",
        "requested_capabilities": ["CAPABILITY_MAIL_SEND"]
      }
    ]
  }
}
Complete a classical authorization input
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "AccountConnections/complete_classical",
  "params": {
    "connection_id": "connection_01H9Z…",
    "authorization_input_id": "mail-auth",
    "endpoint": {
      "host": "imap.fastmail.com",
      "port": 993,
      "username": "me@example.com",
      "password": "<app-password>",
      "security": "TRANSPORT_SECURITY_TLS"
    },
    "smtp_endpoint": {
      "host": "smtp.fastmail.com",
      "port": 465,
      "security": "TRANSPORT_SECURITY_TLS"
    }
  }
}

The secret transits the completion request once, is forwarded to Sync’s sealed staging/vault path, and is never persisted by Gateway. Supplying SMTP coordinates without a MAIL_SEND selection, or selecting MAIL_SEND without explicit SMTP coordinates, is rejected.

A proved external service resource remains reserved through disconnect and is released only after purge closure. Cross-account conflicts return SERVICE_RESOURCE_CONFLICT without another owner’s Account ID or metadata.

Personal presentation

Accounts/get_presentation requires selector-qualified accounts.read. Accounts/update requires both selector-qualified accounts.read and accounts.update; it is a Gateway-local compare-and-set and makes no Sync or provider call.

Set a personal label
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "Accounts/update",
  "params": {
    "account_id": "account_01H9X…",
    "expected_presentation_revision": "3",
    "set_personal_label": "Family mail"
  }
}

Use clear_personal_label: {} to clear it. Repeating the already-current desired value is idempotent. A stale revision returns the distinct concurrent write conflict. Labels are presentation only: they never affect routing, authorization, provider identity, ordering, or search.

Disconnect and purge

Accounts/disconnect stops all bindings while preserving retained history and external-resource reservations. Accounts/purge is the erasure operation: it fences the Account, scrubs the personal label at purge admission, destroys Supermail-owned service state, and releases reservations only after closure. These are separate operations; there is no predecessor purge flag.

Repair of a disconnected binding uses a new current service-connection flow. It proves the retained external identity and cannot fall forward to another provider resource.

AI agent access

Some providers require every request an AI agent makes to use a separate OAuth client, which the mailbox owner authorizes once per mailbox. Gmail is one: an agent request (see Declaring the actor) reaches Gmail only over the owner's agent authorization, never over the mailbox's own connection. Account.provider_agent_optin says where an account stands:

ValueMeaning
not_requiredThe provider needs no separate authorization; agent requests use the mailbox's connection.
activeThe owner authorized agent access and it is current.
authorization_requiredNot authorized yet. Agent requests that reach the provider fail.
reauthorization_requiredThe provider revoked or refused the authorization, or it no longer covers the mailbox's permissions.
unavailableThis deployment offers no agent client for the provider (or the provider refuses it).

An agent request to a mailbox that is not active (or not_required) fails before any provider contact, with SyncError.CredentialsNotFound (unavailable: SyncError.NotSupported); it never falls back to the mailbox's own connection. Reads that reach the provider (on-demand message bodies, mailbox lists) are covered like changes; background synchronization is the owner's own work and is unaffected.

Accounts/start_provider_agent_optin

Requires accounts.read and accounts.update and a user actor: an agent never changes its own authority.

Request params
{ "account_id": "account_01H9X…" }
Result
{
  "authorize_url": "https://accounts.google.com/o/oauth2/v2/auth?…",
  "expires_at": "2026-10-14T18:10:00Z"
}

Send the owner's browser to authorize_url. The consent asks for the same permissions as the mailbox's connection and must be given by the same provider account. The provider returns to the Gateway, which returns the browser to the Gateway account page (or, for the paired Inbox, to the Inbox) with provider_agent_optin_result: active, consent_denied, expired, rejected (another provider account, no offline access or too few permissions) or failed. The consent is valid for ten minutes and resolves once.

Accounts/remove_provider_agent_optin

Same authorization as start. Forgets the agent authorization and returns the account's new provider_agent_optin. The provider's revoke endpoint is deliberately not called: one revocation can end every grant under the deployment's shared consent, including the mailbox's own connection. Owners can also revoke the agent client in their provider account settings.

On this page