Errors and retries
An exception class for every kind of refusal, and retries that cannot send twice.
Handling an error
try { client.templates().send("order-shipped", Body.of( "from", "[email protected]", "to", "[email protected]", "props", Body.of("orderId", "AC-4192") ));} catch (ValidationException error) { System.err.println(error.param() + " " + error.getMessage());} catch (RateLimitException error) { System.err.println("Wait " + error.retryAfterSeconds() + " seconds");} catch (ApiException error) { System.err.println(error.status() + " " + error.code() + " " + error.requestId());} catch (NetworkException error) { System.err.println(error.isTimeout() ? "Timed out" : "No response");}A refusal from the API throws an ApiException, or the subclass for its type, with what the API said about it. Every exception the client throws is an unchecked OpenEmailException, so one catch covers them all.
| Method | What it holds |
|---|---|
| status() | The HTTP status, or 0 when no response arrived. |
| type(), code() | The error type and the code of the API, such as validation_error and invalid_parameter. |
| param() | The field that is to blame, when there is one. |
| requestId() | The id to quote when you write to support. |
| docUrl() | The page of the docs that explains the code. |
| retryAfterSeconds() | How long the API asked you to wait. |
| fields() | The fields a sign-up form refused, each with a key and an error. |
| body() | The decoded response body. |
The kinds of failure
| Exception | When it is thrown |
|---|---|
| AuthenticationException | The key or token was refused, with a 401. |
| PermissionException | The credential may not do this, with a 403. |
| NotFoundException | Nothing has that id, with a 404. |
| ConflictException | The change conflicts with the current state, with a 409. |
| ValidationException | A field was refused, with a 422. |
| RateLimitException | Too many requests, or an allowance is spent, with a 429. |
| InvalidRequestException | Any other refusal of the request. |
| ApiException | The API failed, with a status of 500 or more. |
| NetworkException | No response arrived. |
| IllegalArgumentException | The call itself was wrong, and nothing was sent. |
| WebhookSignatureException | A webhook delivery failed its check. |
An ApiException also answers questions about itself: isValidation(), isNotFound(), isRateLimited(), isServerError() and isRetryable(), with isScopeMissing() when the credential lacks the scope the call needs and isStepUpRequired() when an access token has to verify a code first. NetworkException.isTimeout() says the deadline passed.
What is tried again
- Reads, sends and every write that is safe to repeat are tried again, up to
maxRetriestimes. Any other write is sent once. - The statuses 408, 500, 502, 503 and 504 are retried with a backoff that starts at half a second and doubles up to eight seconds.
- A 429 is retried only when it carries a
Retry-Afterof a minute or less, and the client waits that long. - A connection that fails or times out is retried the same way.
A send that is retried carries the same idempotency key every time, so the API replays the first message instead of sending a second one.