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).
| Reason | HTTP status | Meaning |
|---|---|---|
UNAUTHENTICATED | 401 | Missing or invalid access token |
PERMISSION_DENIED | 403 | Authenticated, but the grant does not allow it |
NOT_FOUND | 404 | Resource does not exist or is not visible to you |
INVALID_ARGUMENT | 400 | Malformed request |
FAILED_PRECONDITION | 412 | Well-formed, but the current state refuses it |
CONFLICT | 409 | Uniqueness collision (duplicate mailbox, consumed token) |
INTERNAL | 500 | A 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:
| Reason | Metadata | Retryable |
|---|---|---|
AUTH_EXPIRED | account_id | No — reconnect the mailbox |
CREDENTIAL_UNREADABLE | account_id | No — the stored credential cannot be read (nothing expired or was refused at the provider); reconnect the mailbox to store a replacement |
CREDENTIALS_REJECTED | account_id | No — the IMAP/SMTP server rejected the username or password; reconnect the mailbox |
AUTHENTICATION_UNSUPPORTED | account_id | No — the IMAP/SMTP server refused the configured authentication method or requires encryption; reconnect the mailbox with changed connection settings |
SERVICE_RESOURCE_CONFLICT | none | No — the authenticated external resource is reserved; no owner identity is disclosed |
SYNC_TOKEN_EXPIRED | account_id, folder? | Yes — a full resync recovers |
UID_VALIDITY_ROLLED | account_id, mailbox | Yes — a scoped full resync recovers |
PROVIDER_UNAVAILABLE | account_id | Yes — back off and retry |
STATE_MISMATCH | expected_version, actual_version | Re-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:
{
"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:
| Code | Meaning |
|---|---|
-32700 | Parse error — invalid JSON |
-32600 | Invalid request — not a valid Request object (includes id-less notifications and oversized batches) |
-32601 | Method not found |
-32602 | Invalid params — also INVALID_ARGUMENT |
-32000 | Uniform 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.