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.
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
| Class | code | Thrown when |
|---|---|---|
EmailError | (base) | Base class for every error below — catch this to catch them all. |
InvalidConfigError | invalid_config | Misconfiguration caught before any network call — no providers configured, an unknown defaultProvider, or an ambiguous provider (several configured, none named). |
ProviderNotFoundError | provider_not_found | A provider slug was requested that isn't registered as a built-in or via registerProvider. |
ProviderNotConfiguredError | provider_not_configured | A provider was used that the app never added to its providers config. |
LicenseRequiredError | license_required | A Pro provider, or a Pro feature (sendBatch, template management, webhook normalization), was used without a verified license. |
LicenseInvalidError | license_invalid | The configured license key could not be verified — invalid, revoked, or the license service was unreachable (fail-closed). |
UnsupportedOperationError | unsupported_operation | A provider was asked to do something it genuinely can't (webhooks on SMTP, attachments on Termii, a native batch it lacks). |
AuthenticationError | authentication_failed | The provider rejected the credentials — bad/expired key, wrong region. |
ProviderApiError | provider_error | The provider's API returned an error (rejected recipient, invalid request, 5xx). Carries status, providerCode, and raw. |
NetworkError | network_error | A network/transport failure reaching a provider — DNS, timeout, TLS. |
SendError | send_failed | A 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). |
WebhookSignatureError | webhook_signature_invalid | A webhook signature did not verify — the payload is untrusted. Reject the request (respond 400). |
TemplateNotFoundError | template_not_found | A template.id was referenced that isn't in the template store. |
TemplateInvalidError | template_invalid | A template definition is invalid — missing id, or none of subject/html/text. |
MissingDependencyError | missing_dependency | An 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 | undefinedHTTP status, when the failure came from an HTTP response.
ProviderApiError.providerCodestring | undefinedThe provider's own machine error code, if it returned one.
ProviderApiError.rawunknownThe raw provider response body (parsed JSON or text) — the debugging escape hatch.
ProviderApiError.providerstringThe slug that produced the error. Also on AuthenticationError,
NetworkError, WebhookSignatureError, and UnsupportedOperationError.
UnsupportedOperationError.operationstringThe operation that isn't supported, e.g. "parseWebhook", "sendBatch".
NetworkError.causeunknownThe underlying transport error. Also present on SendError.
SendError.providerstring | undefinedThe 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
}
}