Radondocs

Errors

Every error Radon Email throws, its stable code, and what causes it. Branch on error.code — never on message text.

Every error extends EmailError and carries a stable code string. Branch on the code, never on the message — messages may change, codes won't. This is the same contract as @radonsdk/auth and @radonsdk/payments.

Branch on code
import { EmailError } from "@radonsdk/email";

try {
  await email.send(message);
} catch (err) {
  if (err instanceof EmailError) {
    switch (err.code) {
      case "send_failed":      /* no `from`, or a non-API send failure */ break;
      case "provider_error":   /* the provider's API rejected it */ break;
      case "license_required": /* set licenseKey + call init() */ break;
      default:                 /* … */ break;
    }
  }
  throw err;
}

Every error

ClasscodeThrown when
EmailError(base)Base class for every error below — catch this to catch them all.
InvalidConfigErrorinvalid_configMisconfiguration caught before any network call — no providers configured, an unknown defaultProvider, or an ambiguous provider (several configured, none named).
ProviderNotFoundErrorprovider_not_foundA provider slug was requested that isn't registered as a built-in or via registerProvider.
ProviderNotConfiguredErrorprovider_not_configuredA provider was used that the app never added to its providers config.
LicenseRequiredErrorlicense_requiredA Pro provider, or a Pro feature (sendBatch, template management, webhook normalization), was used without a verified license.
LicenseInvalidErrorlicense_invalidThe configured license key could not be verified — invalid, revoked, or the license service was unreachable (fail-closed).
UnsupportedOperationErrorunsupported_operationA provider was asked to do something it genuinely can't (webhooks on SMTP, attachments on Termii, a native batch it lacks).
AuthenticationErrorauthentication_failedThe provider rejected the credentials — bad/expired key, wrong region.
ProviderApiErrorprovider_errorThe provider's API returned an error (rejected recipient, invalid request, 5xx). Carries status, providerCode, and raw.
NetworkErrornetwork_errorA network/transport failure reaching a provider — DNS, timeout, TLS.
SendErrorsend_failedA send failed for a reason other than a provider API rejection — most often no from, or a provider's own precondition (Loops without a transactionalId, Termii without a template id or code).
WebhookSignatureErrorwebhook_signature_invalidA webhook signature did not verify — the payload is untrusted. Reject the request (respond 400).
TemplateNotFoundErrortemplate_not_foundA template.id was referenced that isn't in the template store.
TemplateInvalidErrortemplate_invalidA template definition is invalid — missing id, or none of subject/html/text.
MissingDependencyErrormissing_dependencyAn optional peer dependency isn't installed — currently only nodemailer, for the generic SMTP provider.

The EmailErrorCode union

code is typed as EmailErrorCode. Beyond the codes above it also includes batch_failed, which ProviderApiError may carry when a batch request fails as a whole.

Extra fields

Several errors carry structured detail beyond code and message:

ProviderApiError.statusnumber | undefined

HTTP status, when the failure came from an HTTP response.

ProviderApiError.providerCodestring | undefined

The provider's own machine error code, if it returned one.

ProviderApiError.rawunknown

The raw provider response body (parsed JSON or text) — the debugging escape hatch.

ProviderApiError.providerstring

The slug that produced the error. Also on AuthenticationError, NetworkError, WebhookSignatureError, and UnsupportedOperationError.

UnsupportedOperationError.operationstring

The operation that isn't supported, e.g. "parseWebhook", "sendBatch".

NetworkError.causeunknown

The underlying transport error. Also present on SendError.

SendError.providerstring | undefined

The provider slug, when the failure is provider-specific.

Common cases

No from address

// ❌ throws SendError("No `from` address configured…")
await email.send({ to: "ada@example.com", subject: "Hi", text: "…" });

// ✅ set defaultFrom on the client, or from on the message
await email.send({ from: "Acme <hi@acme.com>", to: "ada@example.com", subject: "Hi", text: "…" });

Pro feature without a license

// ❌ throws LicenseRequiredError — sendBatch is Pro even on a free provider
const email = new RadonEmail({ providers: { resend: {} } });
await email.sendBatch(messages);

// ✅ set a key and verify it first
const email = new RadonEmail({ providers: { resend: {} }, licenseKey: process.env.RADON_LICENSE_KEY });
await email.init();               // verifies the license
await email.sendBatch(messages);

Pro providers need init() too

Configuring a Pro provider (anything outside Resend/SendGrid/SMTP) and calling send() before await email.init() throws LicenseRequiredError — the adapter won't even resolve until the license is verified. A bad or unreachable key throws LicenseInvalidError (fail-closed).

Guarding before you call

Most UnsupportedOperationErrors are avoidable — check capabilities first:

const provider = await email.provider("termii");
if (!provider.capabilities.attachments) {
  // route this message elsewhere instead of attaching a file
}

Rejecting a bad webhook

try {
  const events = await email.webhooks.handle({ body, headers }, { provider: "resend" });
  // respond 200
} catch (err) {
  if (err instanceof EmailError && err.code === "webhook_signature_invalid") {
    // respond 400 — the payload is untrusted
  }
}

Next steps

On this page