Skip to the documentation
C#

Errors and retries

One exception type for every refusal, a property for every kind, and retries that cannot send twice.

Handling an error

errors.cs
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.

PropertyWhat it holds
StatusThe HTTP status.
ErrorType, CodeThe error type and the code of the API, such as validation_error and invalid_parameter.
ParamThe field that is to blame, when there is one.
RequestIdThe id to quote when you write to support.
DocUrlThe page of the docs that explains the code.
RetryAfterSecondsHow long the API asked you to wait, in seconds.
FieldsThe fields a sign-up form refused, each with a key and an error.
BodyThe decoded response body.

The kinds of failure

PropertyWhen it is true
IsAuthThe key or token was refused, with a 401.
IsPermissionThe credential may not do this, with a 403.
IsScopeMissingThe credential lacks the scope the call needs.
IsStepUpRequiredAn access token has to verify a code first.
IsNotFoundNothing has that id, with a 404.
IsConflictThe change conflicts with the current state, with a 409.
IsValidationA field was refused, with a 422.
IsRateLimitedToo many requests, or an allowance is spent, with a 429.
IsInvalidRequestAny other refusal of the request.
IsServerErrorThe API failed, with a status of 500 or more.
IsRetryableThe status is one the client retries.
IsTimeoutNo 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.

step-up.cs
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 MaxRetries times. 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-After of 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.