Errors and retries
One exception type for every refusal, a property for every kind, and retries that cannot send twice.
Handling an error
try{ await client.Emails.SendAsync(new Body { ["from"] = "[email protected]", ["to"] = "[email protected]", ["subject"] = "Your September invoice", ["text"] = "Invoice attached.", });}catch (OpenEmailApiException error) when (error.IsValidation){ Console.Error.WriteLine($"{error.Code} {error.Param} {error.Message}");}catch (OpenEmailApiException error) when (error.IsRateLimited){ Console.Error.WriteLine($"try again in {error.RetryAfterSeconds} seconds");}catch (OpenEmailNetworkException error) when (error.IsTimeout){ Console.Error.WriteLine("no answer in time");}A refusal from the API throws OpenEmailApiException, and no response at all throws OpenEmailNetworkException. Both derive from OpenEmailException, so one catch takes either, and a when filter picks the kind.
| Property | What it holds |
|---|---|
| Status | The HTTP status. |
| ErrorType, 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, in seconds. |
| Fields | The fields a sign-up form refused, each with a key and an error. |
| Body | The decoded response body. |
The kinds of failure
| Property | When it is true |
|---|---|
| IsAuth | The key or token was refused, with a 401. |
| IsPermission | The credential may not do this, with a 403. |
| IsScopeMissing | The credential lacks the scope the call needs. |
| IsStepUpRequired | An access token has to verify a code first. |
| IsNotFound | Nothing has that id, with a 404. |
| IsConflict | The change conflicts with the current state, with a 409. |
| IsValidation | A field was refused, with a 422. |
| IsRateLimited | Too many requests, or an allowance is spent, with a 429. |
| IsInvalidRequest | Any other refusal of the request. |
| IsServerError | The API failed, with a status of 500 or more. |
| IsRetryable | The status is one the client retries. |
| IsTimeout | No response arrived before the timeout. This one is on OpenEmailNetworkException. |
A mistake in the call itself, such as a malformed key or an empty id, throws ArgumentException before anything is sent. A cancelled token throws OperationCanceledException, and a webhook delivery that fails its check throws OpenEmailWebhookException.
A verification code
An access token acts for a person, so before a sensitive change, such as deleting a domain, it is asked for the same verification code the web app asks for. Ask for a code, check it, then make the request again. API keys are never asked for one.
var domainId = "b3e1f0a4-6c2d-4e8a-9f17-2d5c8a0b4e6f"; try{ await client.Domains.DeleteAsync(domainId);}catch (OpenEmailApiException error) when (error.IsStepUpRequired){ var challenge = await client.Security.BeginStepUpAsync(); Console.WriteLine($"Enter the code sent by {challenge["method"]}"); await client.Security.VerifyStepUpAsync(new Body { ["code"] = Console.ReadLine()?.Trim() }); await client.Domains.DeleteAsync(domainId);}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.