Skip to the documentation
Python

Errors

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

Catching one

catch_errors.py
from openemail import OpenEmailApiError, OpenEmailNetworkError, openemail try:    openemail.emails.send({'from': sender, 'to': recipient, 'subject': subject, 'text': text})except OpenEmailApiError as error:    if error.is_validation:        print(error.code, error.param, error.message)     if error.is_permission:        print(openemail.addresses.list())     if error.is_rate_limited:        print('try again in', error.retry_after_seconds, 'seconds')     print(error.status, error.request_id)     raiseexcept OpenEmailNetworkError as error:    if error.is_timeout:        print('no answer in time')     raise

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
OpenEmailApiErrorThe API answered, and not with a success. Carries message, status, type, code, param, doc_url, request_id, retry_after_seconds, fields and body.
OpenEmailNetworkErrorNo response arrived: DNS, TLS, a dropped connection or the timeout. Carries cause, the httpx exception underneath, and is_timeout is True when the timeout was the reason.
OpenEmailErrorThe base of both, so one except catches every failure the API or the network caused. WebhookVerificationError, which verify_webhook_signature raises, inherits it too.
ValueErrorRaised before anything is sent: a missing or malformed key, an unusable base_url, a credential bound for plain http, an empty id. A wrong http_client, or a body JSON cannot carry, raises TypeError instead.

fields lists each answer a sign-up form refused, as key and error, and is None on every other error. body keeps the JSON the API sent, or None when the body was not JSON.

PropertyTrue when
is_authtype is authentication_error, a 401: no key, the wrong kind of credential, or a key we did not issue.
is_permissionpermission_error, a 403: a real key without the scope or the From address it needs.
is_scope_missingcode is insufficient_scope, the 403 that names a missing scope.
is_invalid_requestinvalid_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 is_validation is the property that catches it.
is_validationvalidation_error, a 422: the schema refused it, and param names the field.
is_not_foundnot_found_error, a 404: no such resource.
is_conflictconflict_error, a 409: the resource is past the point where this could be done to it.
is_rate_limitedrate_limit_error, a 429. retry_after_seconds holds the wait when the server named one.
is_server_errorstatus is 500 or above. Quote request_id if you contact support.
is_retryablestatus is 408, 429, 500, 502, 503 or 504.
is_step_up_requiredcode is step_up_required, the 403 an OAuth access token gets before a sensitive change until the person has verified a code.

Most properties read type, the frozen half of the envelope. code stays a str, because the API guarantees it is open and additive, so treat one you do not recognise as its type. A closed Literal 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 raises one too.

Cancelling an AsyncOpenEmail call raises no OpenEmailError. The cancellation itself propagates, whether it lands during the request or during the wait before a retry, and nothing is retried after it.

request_id

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 returns the parsed body alone, so there is no request id to read on one.

str(error) ends with the status, the code and the request id, so a log line that prints the exception keeps all three. An OpenEmailApiError also survives pickling with every field, so one raised in a worker process reaches the parent intact.