Supermail
Concepts

Errors

The one error model projected consistently onto JSON-RPC and plain HTTP.

Every failure carries the same information regardless of which transport you use, so the surfaces never disagree on an error's class. An error has a stable, machine-readable reason, a domain it belongs to (gateway.traummail.dev for API errors), and optional metadata.

Reason codes

The reason is the discriminator you branch on (modeled on google.rpc.ErrorInfo).

ReasonHTTP statusMeaning
UNAUTHENTICATED401Missing or invalid access token
PERMISSION_DENIED403Authenticated, but the grant does not allow it
NOT_FOUND404Resource does not exist or is not visible to you
INVALID_ARGUMENT400Malformed request
FAILED_PRECONDITION412Well-formed, but the current state refuses it
CONFLICT409Uniqueness collision (duplicate mailbox, consumed token)
INTERNAL500A bug or infrastructure failure — internals never appear on the wire

Errors that originate deeper in the pipeline (against a provider) pass their own reason through unchanged — for example UPLOAD_REF_STALE on a draft or submission. Always branch on reason as a string rather than assuming a fixed set.

Upstream reason codes

Calls that reach a provider can surface reasons from the sync layer's registry (domain sync.traummail.dev), forwarded unchanged. The pinned set and their remediation:

ReasonMetadataRetryable
AUTH_EXPIREDaccount_idNo — reconnect the mailbox
CREDENTIAL_UNREADABLEaccount_idNo — the stored credential cannot be read (nothing expired or was refused at the provider); reconnect the mailbox to store a replacement
CREDENTIALS_REJECTEDaccount_idNo — the IMAP/SMTP server rejected the username or password; reconnect the mailbox
AUTHENTICATION_UNSUPPORTEDaccount_idNo — the IMAP/SMTP server refused the configured authentication method or requires encryption; reconnect the mailbox with changed connection settings
SERVICE_RESOURCE_CONFLICTnoneNo — the authenticated external resource is reserved; no owner identity is disclosed
SYNC_TOKEN_EXPIREDaccount_id, folder?Yes — a full resync recovers
UID_VALIDITY_ROLLEDaccount_id, mailboxYes — a scoped full resync recovers
PROVIDER_UNAVAILABLEaccount_idYes — back off and retry
STATE_MISMATCHexpected_version, actual_versionRe-read and retry

These three account-credential reasons carry the action RECONNECT_ACCOUNT: an RPC refusal reports ERROR_ACTION_RECONNECT_ACCOUNT with account_reconnect = {account_id, cause} under the public code SyncError.AuthExpired, SyncError.CredentialsRejected or SyncError.AuthenticationUnsupported, and a durable failure reports FAILURE_ACTION_RECONNECT_ACCOUNT. REAUTHENTICATE concerns only the caller's own Gateway session.

Two more actions also name the mailbox owner. FAILURE_ACTION_DECIDE marks an outcome Supermail could not establish by itself; the resource's typed owner decision says what to decide (see owner decisions). FAILURE_ACTION_REPAIR_ACCOUNT marks an account whose local copy automatic re-syncs could not heal; Accounts/repair rebuilds it from the provider. No failure ever routes anyone to an operator or a support desk: every other action is resolved by retrying, re-reading or fixing the request.

Build remediation on reason + metadata; treat any unknown reason as non-retryable.

Error shapes by transport

The same safe public error information is used by JSON-RPC and plain HTTP; only the framing differs.

The transport response is always 200 OK; success or failure rides the envelope. INVALID_ARGUMENT uses the JSON-RPC spec code -32602; every other error uses the uniform -32000, with data carrying the detail:

Error response
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32000,
    "message": "permission denied",
    "data": {
      "reason": "PERMISSION_DENIED",
      "domain": "gateway.traummail.dev",
      "metadata": {}
    }
  }
}

Envelope-level failures (before any method runs) use the standard JSON-RPC codes:

CodeMeaning
-32700Parse error — invalid JSON
-32600Invalid request — not a valid Request object (includes id-less notifications and oversized batches)
-32601Method not found
-32602Invalid params — also INVALID_ARGUMENT
-32000Uniform error; data.reason distinguishes

Plain-HTTP projection

The staged content upload, authentication failures on the content lane, and the OAuth endpoints return an HTTP status from the table above, with a JSON body carrying the same reason / domain / metadata shape.

On this page