Errors
Every failure raises. Two classes, and a request id on every API error.
Catching one
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') raiseA 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
| Class | When |
|---|---|
| OpenEmailApiError | The API answered, and not with a success. Carries message, status, type, code, param, doc_url, request_id, retry_after_seconds, fields and body. |
| OpenEmailNetworkError | No 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. |
| OpenEmailError | The base of both, so one except catches every failure the API or the network caused. WebhookVerificationError, which verify_webhook_signature raises, inherits it too. |
| ValueError | Raised 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.
| Property | True when |
|---|---|
| is_auth | type is authentication_error, a 401: no key, the wrong kind of credential, or a key we did not issue. |
| is_permission | permission_error, a 403: a real key without the scope or the From address it needs. |
| is_scope_missing | code is insufficient_scope, the 403 that names a missing scope. |
| is_invalid_request | 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 is_validation is the property that catches it. |
| is_validation | validation_error, a 422: the schema refused it, and param names the field. |
| is_not_found | not_found_error, a 404: no such resource. |
| is_conflict | conflict_error, a 409: the resource is past the point where this could be done to it. |
| is_rate_limited | rate_limit_error, a 429. retry_after_seconds holds the wait when the server named one. |
| is_server_error | status is 500 or above. Quote request_id if you contact support. |
| is_retryable | status is 408, 429, 500, 502, 503 or 504. |
| is_step_up_required | code 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.