Email SDK
Reference

Errors

Every Email SDK error class and code, the retryable flag, and how to branch on failures in application code.

Every error the SDK throws is an EmailSdkError (or subclass) with a machine-readable code and a retryable flag. Three subclasses narrow the common cases:

import {
  EmailSdkError, // base class
  EmailProviderError, // a provider call failed
  EmailValidationError, // bad message or client config
  EmailProviderNotFoundError, // unregistered routing name
  isRetryableEmailError,
} from "@opencoredev/email-sdk";

Properties

All four classes carry the same fields (plus the standard Error message and cause):

Prop

Type

Classes and codes

ClassCodeThrown when
EmailValidationErrorvalidation_errorThe message fails validation, an adapter rejects unsupported fields, or client config is invalid (duplicate adapter/plugin, no adapters). Never retryable.
EmailProviderNotFoundErrorprovider_not_foundA routing name is not registered — Email provider "x" is not registered. Never retryable.
EmailProviderErrorprovider_errorA provider request fails: non-2xx response or network error. status and details carry the provider's answer.
EmailSdkErrorall_providers_failedTwo or more routes were attempted and all failed. details holds one error per adapter, in route order.
EmailSdkErrorretry_loop_exitedInternal guard for an impossible retry-loop exit. Treat as a bug if seen.

When only one route was attempted, its error is rethrown as-is — all_providers_failed only appears after a multi-adapter route exhausts every option. Errors adapters throw that are not already EmailProviderError are wrapped into one, keeping the original as cause (or in details for non-Error values).

What counts as retryable

retryable: true is set for failures that may succeed on a re-attempt:

  • HTTP 408, 409, 425, 429, and any 5xx
  • Network failures: ECONNRESET-family error codes, fetch failed, socket and timeout errors

Everything else is non-retryable: 4xx like 401/422, validation errors, unknown routing names, and aborted requests (AbortError). This classification drives the default retry policy — see fallbacks and retries.

isRetryableEmailError(error)

isRetryableEmailError(error: unknown): boolean

Returns true only for an EmailSdkError with retryable: true; any other value returns false. It is the default shouldRetry predicate, and safe to call on anything a catch block hands you.

Handling errors in app code

Branch on class first, then on code and retryable:

import {
  EmailProviderError,
  EmailSdkError,
  EmailValidationError,
} from "@opencoredev/email-sdk";

try {
  await email.send(message);
} catch (error) {
  if (error instanceof EmailValidationError) {
    // Bad message shape or unsupported field — fix the code path, do not retry.
    throw error;
  }

  if (error instanceof EmailProviderError) {
    console.error(`${error.provider} failed`, error.status, error.details);
    if (error.retryable) {
      await queue.retryLater(message); // transient — re-enqueue
      return;
    }
    throw error; // 401/422-style failure: needs a config or account fix
  }

  if (error instanceof EmailSdkError && error.code === "all_providers_failed") {
    // Every route failed; details is one error per adapter, in route order.
    console.error(error.details);
  }

  throw error;
}

Inside a send, you rarely handle retries yourself — configure the client's retry and fallback options and let the SDK exhaust them before anything reaches your catch.

On this page