Go
Errors and retries
One error type for every failure, a sentinel for every kind, and retries that cannot send twice.
Handling an error
_, err := client.Emails.Send(ctx, openemail.Body{ "from": "[email protected]", "to": "[email protected]", "subject": "Your September invoice", "text": "Invoice attached.",}) var failure *openemail.Error switch {case err == nil: fmt.Println("sent")case errors.Is(err, openemail.ErrValidation): errors.As(err, &failure) fmt.Println(failure.Code, failure.Param, failure.Message)case errors.Is(err, openemail.ErrRateLimited): errors.As(err, &failure) fmt.Println("try again in", failure.RetryAfter)case errors.Is(err, openemail.ErrTimeout): fmt.Println("no answer in time")default: return err}Every failure is an *openemail.Error. errors.Is tells which kind it is, and errors.As gives the error itself, with what the API said about it.
| Field | 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. |
| RetryAfter | 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
| Sentinel | When it matches |
|---|---|
| ErrAuthentication | The key or token was refused, with a 401. |
| ErrPermission | The credential may not do this, with a 403. |
| ErrScopeMissing | The credential lacks the scope the call needs. |
| ErrStepUpRequired | An access token has to verify a code first. |
| ErrNotFound | Nothing has that id, with a 404. |
| ErrConflict | The change conflicts with the current state, with a 409. |
| ErrValidation | A field was refused, with a 422. |
| ErrRateLimited | Too many requests, or an allowance is spent, with a 429. |
| ErrInvalidRequest | Any other refusal of the request. |
| ErrServer | The API failed, with a status of 500 or more. |
| ErrRetryable | The status is one the client retries. |
| ErrNetwork | No response arrived. |
| ErrTimeout | No response arrived before the timeout. |
| ErrInvalidArgument | The call itself was wrong, and nothing was sent. |
| ErrWebhookSignature | A webhook delivery failed its check. |
A cancelled context still matches context.Canceled, and a deadline that passed matches context.DeadlineExceeded.
What is tried again
- Reads, sends and every write that is safe to repeat are tried again, up to
WithMaxRetriestimes. 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.