Errors
Every failure throws. Two classes, and a request id on every API error.
Catching one
import { NextmailApiError, NextmailNetworkError, nextmail } from '@nextmail/sdk' try { await nextmail.emails.send(message)} catch (error) { if (error instanceof NextmailApiError) { if (error.isValidation) console.error(error.code, error.param, error.message) if (error.isPermission) console.error(await nextmail.addresses.list()) if (error.isRateLimited) console.error('try again in', error.retryAfterSeconds, 'seconds') console.error(error.status, error.requestId) } if (error instanceof NextmailNetworkError && error.isTimeout) console.error('no answer in time') throw error}A 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 |
|---|---|
| NextmailApiError | The API answered, and not with a success. Carries status, type, code, param, docUrl, requestId and retryAfterSeconds. |
| NextmailNetworkError | No response arrived: DNS, TLS, a dropped connection, the timeout, or your own AbortSignal. Carries cause, and isTimeout is true when the timeout was the reason. |
| Error | Thrown before anything is sent: a missing or malformed key, an unusable baseUrl, a browser, an empty id. |
| Getter | True when |
|---|---|
| isAuth | type is authentication_error, a 401: no key, the wrong kind of credential, or a key we did not issue. |
| isPermission | permission_error, a 403: a real key without the scope or the From address it needs. |
| isScopeMissing | code is insufficient_scope, the 403 that names a missing scope. |
| isInvalidRequest | 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 isValidation is the getter that catches it. |
| isValidation | validation_error, a 422: the schema refused it, and param names the field. |
| isNotFound | not_found_error, a 404: no such resource. |
| isConflict | conflict_error, a 409: the resource is past the point where this could be done to it. |
| isRateLimited | rate_limit_error, a 429. retryAfterSeconds holds the wait when the server named one. |
| isServerError | status is 500 or above. Quote requestId if you contact support. |
| isRetryable | status is 408, 429, 500, 502, 503 or 504. |
| isStepUpRequired | code is step_up_required, the 403 an OAuth access token gets before a sensitive change until the person has verified a code. Added in 0.0.6. |
The getters read type, the frozen half of the envelope. code stays a string, because the API guarantees it is open and additive, so treat one you do not recognise as its type. A closed union 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 NextmailApiError, with type inferred from the status and code set to unrecognised_response. A success whose body is not JSON throws one too.
An abort is an NextmailNetworkError as well, whether it lands during the request or during the wait before a retry, and the abort is kept on cause. Check signal.aborted when you need to tell your own cancellation from a network failure.
requestId
Every NextmailApiError 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 resolves to the parsed body alone, so there is no request id to read on one.