Skip to the documentation
SDK

Errors

Every failure throws. Two classes, and a request id on every API error.

Catching one

catch-errors.ts
import { OpenEmailApiError, OpenEmailNetworkError, openemail } from '@openemail/sdk' try {  await openemail.emails.send(message)} catch (error) {  if (error instanceof OpenEmailApiError) {    if (error.isValidation) console.error(error.code, error.param, error.message)    if (error.isPermission) console.error(await openemail.addresses.list())    if (error.isRateLimited) console.error('try again in', error.retryAfterSeconds, 'seconds')     console.error(error.status, error.requestId)  }   if (error instanceof OpenEmailNetworkError && error.isTimeout) console.error('no answer in time')   throw error}

A permission_error on a send is usually the KEY’s send scope, a domain or an address it was not given, rather than the workspace, which is why the sample prints what addresses.list() says this key may send as.

The classes

ClassWhen
`OpenEmailApiError`The API answered, and not with a success. Carries status, type, code, param, docUrl, requestId and retryAfterSeconds.
`OpenEmailNetworkError`No response arrived: DNS, TLS, a dropped connection, the timeout, or your own AbortSignal. Carries cause, and isTimeout is true when the timeout was the reason.
`Error`Thrown before anything is sent: a missing or malformed key, an unusable baseUrl, a browser, an empty id.
GetterTrue when
`isAuth`type is authentication_error, a 401: no key, the wrong kind of credential, or a key we did not issue.
`isPermission`permission_error, a 403: a real key without the scope or the From address it needs.
`isScopeMissing`code is insufficient_scope, the 403 that names a missing scope.
`isInvalidRequest`invalid_request_error, a 400: a request that could not be understood. A message over the size ceiling comes back as a 422 message_too_large, so isValidation is the getter that catches it.
`isValidation`validation_error, a 422: the schema refused it, and param names the field.
`isNotFound`not_found_error, a 404: no such resource.
`isConflict`conflict_error, a 409: the resource is past the point where this could be done to it.
`isRateLimited`rate_limit_error, a 429. retryAfterSeconds holds the wait when the server named one.
`isServerError`status is 500 or above. Quote requestId if you contact support.
`isRetryable`status is 408, 429, 500, 502, 503 or 504.

The getters read type, the frozen half of the envelope. code stays a string, because the API guarantees it is open and additive, so treat one you do not recognise as its type. A closed union would make an SDK upgrade the price of reading a new failure mode.

A body that is not the API’s error envelope still becomes an OpenEmailApiError, with type inferred from the status and code set to unrecognised_response. A success whose body is not JSON throws one too.

An abort is an OpenEmailNetworkError as well, whether it lands during the request or during the wait before a retry, and the abort is kept on cause. Check signal.aborted when you need to tell your own cancellation from a network failure.

requestId

Every OpenEmailApiError carries the request id the server sent, from the error body or the x-request-id header, and it is the only thing tying your failure to a line in the server's log. A success resolves to the parsed body alone, so there is no request id to read on one.