Radondocs

API reference

Every RadonEmail method, configuration option, message field, and return type — exhaustive.

The complete surface of @radonsdk/email. For narrative guides start with Concepts; this page is the exhaustive reference.

Constructor

new RadonEmail(config: RadonEmailConfig)

Create a client. Registers the built-in adapters and builds the license client if a key is present. Throws InvalidConfigError if providers is empty.

createEmail(config: RadonEmailConfig): RadonEmail

Convenience factory — identical to new RadonEmail(config).

import { RadonEmail, createEmail } from "@radonsdk/email";

const email = new RadonEmail({ providers: { resend: {} }, defaultFrom: "Acme <hi@acme.com>" });
// or: const email = createEmail({ … });

RadonEmailConfig

providersPartial<Record<string, ProviderOptions>>required

Providers to enable, keyed by slug. The value is that provider's options; {} reads everything from RADON_<SLUG>_* env vars.

defaultProviderstring

Provider used when a call names none. Defaults to the sole configured provider; required when several are configured.

defaultFromstring

A default From address ("Acme <hi@acme.com>") applied to every message that doesn't set its own. Required unless every message carries a from.

templateStoreTemplateStorePro

Storage for managed templates. Defaults to an in-memory store.

licenseKeystring

Radon Pro license key. Required for any Pro provider or Pro feature. Falls back to the RADON_LICENSE_KEY env var. Verified in init().

licenseLicenseConfig

Advanced license config — key, verifyUrl, watermark, fetch.

fetchtypeof fetch

Injectable fetch (proxies, instrumentation, tests). Defaults to global fetch.

now() => Date

Injectable clock (tests). Defaults to () => new Date().

onEventError(error: unknown, hook: string) => void

Called when a lifecycle hook throws. Defaults to console.error.

ProviderOptions

Every field is optional and provider-specific; unknown keys are ignored. The one reserved key read uniformly by the webhook layer is webhookSecret. See Providers for each adapter's recognized options.

webhookSecretstring

Secret used to verify this provider's delivery-webhook signatures.

[key: string]unknown

Any provider-specific option (apiKey, domain, region, serverToken, …).

Lifecycle

email.init(): Promise<void>

Verify the Pro license (when a Pro provider is configured or a key is present), then eagerly load and initialize every configured provider. Call at boot to fail fast and to unlock Pro providers and features. Throws LicenseRequiredError if a Pro provider is configured without a key, or LicenseInvalidError if verification fails (fail-closed). A free-only setup using only single send() may skip it.

email.isLicensed: boolean

Getter — true once a valid license was confirmed in init().

email.defaultProvider: string

Getter — the provider used when a call names none. Throws InvalidConfigError if several are configured without a defaultProvider, or if the configured default isn't among the providers.

Sending

email.send(message: EmailMessage, options?: OperationOptions): Promise<SendResult>

Send one message. Runs send middleware, resolves the managed template and free {{var}} interpolation, then fires the onSent (or onError) hook. Throws SendError if no from is resolvable, and provider errors on API failure.

email.sendBatch(messages: EmailMessage[], options?: OperationOptions): Promise<BatchResult>Pro

Send many messages. Uses the provider's native bulk endpoint when available, else sequential. Requires a verified license. See Batch.

OperationOptions

providerstring

Provider slug for this call; defaults to defaultProvider.

EmailMessage

toAddress | Address[]required

Recipient(s).

fromAddress

Sender; overrides defaultFrom. One of the two is required.

ccAddress | Address[]

CC recipient(s), where supported.

bccAddress | Address[]

BCC recipient(s), where supported.

replyToAddress

Reply-To address, where supported.

subjectstring

Subject. Optional only when a managed template supplies it.

htmlstring

HTML body.

textstring

Plain-text body / fallback.

variablesRecord<string, unknown>

Values for free {{var}} interpolation.

templateTemplateRefPro

A managed template { id, variables } to render.

attachmentsAttachment[]

Files to attach.

tagsstring[]

Tags / categories, where supported.

headersRecord<string, string>

Extra MIME headers, where supported.

metadataRecord<string, unknown>

Arbitrary data echoed back where supported.

sendAtDate

Schedule for a future time, where supported.

providerOptionsRecord<string, unknown>

Escape hatch — provider-specific fields merged into the request.

Address

A string ("ada@example.com" or "Ada <ada@example.com>") or an object { email: string; name?: string }.

Attachment

filenamestringrequired

File name shown to the recipient.

contentstring | Bufferrequired

File bytes — a Buffer, or a base64-encoded string.

contentTypestring

MIME type. Guessed from the filename when omitted.

contentIdstring

Content-ID for inline embedding (cid:<id>).

disposition"attachment" | "inline"default: "attachment"

"inline" for embedded images.

TemplateRef

idstringrequired

Id of a template registered in the store.

variablesRecord<string, unknown>

Values substituted into the template; merged over the message's variables.

SendResult

idstring | undefined

Provider message id, when returned. Correlates to EmailEvent.messageId.

providerstring

The slug that handled the send.

status"sent" | "queued" | "scheduled"

Normalized outcome.

acceptedstring[] | undefined

Recipients the provider accepted, when reported.

rejectedstring[] | undefined

Recipients the provider rejected, when reported.

rawunknown

The untouched provider response.

BatchResult

resultsSendResult[]

One result per input message, in order.

batchedboolean

true if a native bulk endpoint was used; false if sequential.

Templates (Pro)

Managing the store requires a verified license. Full guide in Templates.

email.templates.register(template: EmailTemplate): Promise<void>Pro

Register (or replace) a template. Validates it, then stores it.

email.templates.update(template: EmailTemplate): Promise<void>Pro

Upsert a template by id — identical to register.

email.templates.get(id: string): Promise<EmailTemplate | undefined>Pro

Get one template, or undefined.

email.templates.list(): Promise<EmailTemplate[]>Pro

List every registered template.

email.templates.delete(id: string): Promise<boolean>Pro

Delete by id; true if one was removed.

EmailTemplate

idstringrequired

Stable id referenced by message.template.id.

subjectstring

Subject; may contain {{var}} placeholders.

htmlstring

HTML body; may contain placeholders.

textstring

Text body; may contain placeholders.

descriptionstring

Optional human label, for tooling.

At least one of subject / html / text is required, else TemplateInvalidError.

TemplateStore

Implement all four to back templates with your own storage. Each may be sync or async.

get(id: string) => Awaitable<EmailTemplate | undefined>

Fetch one template.

set(template: EmailTemplate) => Awaitable<void>

Upsert a template.

delete(id: string) => Awaitable<boolean>

Remove a template; return whether one existed.

list() => Awaitable<EmailTemplate[]>

List all templates.

Webhooks (Pro)

email.webhooks.handle(request: WebhookRequest, options?: HandleWebhookOptions): Promise<EmailEvent[]>Pro

Verify and normalize an inbound webhook, then (by default) dispatch its events to hooks. See Webhooks for the full type breakdown of WebhookRequest, HandleWebhookOptions, and EmailEvent.

Providers & escape hatch

email.provider(slug?: string): Promise<EmailProvider>

Lazy-load, construct, init(), and cache a configured provider. Reading .capabilities on the result tells you what it supports. Throws ProviderNotConfiguredError for an unconfigured slug and LicenseRequiredError for a Pro slug before init().

email.native(slug?: string): Promise<unknown>

The escape hatch — the provider's own configured client/context (e.g. an HttpClient for HTTP providers, an { region, endpoint, credentials } object for SES/Pinpoint). Throws if the provider exposes none.

Hooks & middleware

email.on<E>(event: E, handler): () => void

Register a lifecycle hook. Returns an unsubscribe function. Many handlers can be registered per event; failures are isolated via onEventError.

email.use(plugin: Plugin): this

Install a plugin — a named bundle of lifecycle hooks plus optional send middleware.

email.useMiddleware(middleware: SendMiddleware): this

Add around-style send middleware (message, next) => next(message). Composes like an onion — first registered is outermost.

LifecycleHooks

onSent(result: SendResult) => Awaitable<void>

After a successful send() (and once per message from sendBatch()).

onError(error: unknown, message: EmailMessage) => Awaitable<void>

When a send() throws (the error is re-thrown to the caller regardless).

onEmailEvent(event: EmailEvent) => Awaitable<void>

Every normalized webhook event, after type-specific hooks.

onBounced(event: EmailEvent) => Awaitable<void>

A bounced webhook event.

onComplained(event: EmailEvent) => Awaitable<void>

A complained (spam) webhook event.

onUnsubscribed(event: EmailEvent) => Awaitable<void>

An unsubscribed webhook event.

Plugin

Extends LifecycleHooks with a name: string and an optional sendMiddleware: SendMiddleware.

A suppression plugin
email.use({
  name: "suppression",
  sendMiddleware: async (message, next) => {
    if (await isSuppressed(message.to)) throw new Error("suppressed");
    return next(message);
  },
  onBounced: (e) => suppress(e.recipient),
});

Tiers & introspection

FREE_PROVIDERS: ReadonlySet<string>

The provider slugs that work without a license — resend, sendgrid, smtp.

isProProvider(slug: string): boolean

Whether a slug is Pro-gated (anything not in FREE_PROVIDERS).

Interpolation helpers

interpolate(template: string, variables: Record<string, unknown>): string

Substitute {{var}} placeholders in a string.

interpolateFields(fields: { subject?, html?, text? }, variables): fields

Interpolate the subject/html/text of a message-like object.

extractVariables(template: string): string[]

The variable names a template references, deduped and in order.

Address & attachment helpers

formatAddress(address: Address): string

Format as RFC 5322 — "Name <email>" or "email".

addressEmail(address: Address): string

Extract the bare email, dropping any display name.

addressName(address: Address): string | undefined

Extract the display name, if any.

toRecipientArray(recipients?: Recipients): Address[]

Normalize a one-or-many recipients field to an array (empty when absent).

attachmentBase64(content: string | Buffer): string

Get an attachment's bytes as base64 (a string is assumed already base64).

guessContentType(filename: string): string

Guess a MIME type from a filename extension; application/octet-stream fallback.

Bring your own provider

Implement the EmailProvider interface, or extend BaseProvider for the credential/HTTP/error plumbing, then register a loader. Custom slugs are Pro-gated.

registerProvider(slug: string, loader: ProviderLoader): void

Register a custom adapter. The loader is an async () => ProviderConstructor, imported on first use.

hasProvider(slug: string): boolean

Whether a slug is registered (built-in or custom).

knownProviders(): string[]

Every registered slug.

A custom adapter
import { registerProvider, BaseProvider } from "@radonsdk/email";
import type { EmailMessage, SendResult, ProviderCapabilities } from "@radonsdk/email";

class MyEspProvider extends BaseProvider {
  readonly name = "my-esp";
  readonly capabilities: ProviderCapabilities = {
    send: true, batch: false, templates: false, attachments: true,
    webhooks: false, webhookSignature: false, tags: false,
    scheduling: false, cc: true, bcc: true, replyTo: true,
  };

  async send(message: EmailMessage): Promise<SendResult> {
    const from = this.resolveFrom(message);       // throws if no from
    const res = await this.http("https://api.my-esp.com", {
      Authorization: `Bearer ${this.credential("apiKey")}`,   // options → RADON_MY-ESP_API_KEY
    }).request<{ id: string }>("/send", { method: "POST", json: { /* … */ } });
    return { id: res.id, provider: this.name, status: "queued", raw: res };
  }
}

registerProvider("my-esp", async () => MyEspProvider);

BaseProvider gives you credential(key) / optionalCredential(key) (options-then-RADON_<SLUG>_<KEY>), webhookSecret(), http(baseUrl, headers), and resolveFrom(message). Any operation you don't override throws a typed UnsupportedOperationError.

Next steps

On this page