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): RadonEmailConvenience 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>>requiredProviders to enable, keyed by slug. The value is that provider's options;
{} reads everything from RADON_<SLUG>_* env vars.
defaultProviderstringProvider used when a call names none. Defaults to the sole configured provider; required when several are configured.
defaultFromstringA default From address ("Acme <hi@acme.com>") applied to every message that
doesn't set its own. Required unless every message carries a from.
templateStoreTemplateStoreProStorage for managed templates. Defaults to an in-memory store.
licenseKeystringRadon Pro license key. Required for any Pro provider or Pro feature. Falls
back to the RADON_LICENSE_KEY env var. Verified in init().
licenseLicenseConfigAdvanced license config — key, verifyUrl, watermark, fetch.
fetchtypeof fetchInjectable fetch (proxies, instrumentation, tests). Defaults to global fetch.
now() => DateInjectable clock (tests). Defaults to () => new Date().
onEventError(error: unknown, hook: string) => voidCalled 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.
webhookSecretstringSecret used to verify this provider's delivery-webhook signatures.
[key: string]unknownAny 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: booleanGetter — true once a valid license was confirmed in init().
email.defaultProvider: stringGetter — 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>ProSend many messages. Uses the provider's native bulk endpoint when available, else sequential. Requires a verified license. See Batch.
OperationOptions
providerstringProvider slug for this call; defaults to defaultProvider.
EmailMessage
toAddress | Address[]requiredRecipient(s).
fromAddressSender; overrides defaultFrom. One of the two is required.
ccAddress | Address[]CC recipient(s), where supported.
bccAddress | Address[]BCC recipient(s), where supported.
replyToAddressReply-To address, where supported.
subjectstringSubject. Optional only when a managed template supplies it.
htmlstringHTML body.
textstringPlain-text body / fallback.
variablesRecord<string, unknown>Values for free {{var}} interpolation.
templateTemplateRefProA 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.
sendAtDateSchedule 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
filenamestringrequiredFile name shown to the recipient.
contentstring | BufferrequiredFile bytes — a Buffer, or a base64-encoded string.
contentTypestringMIME type. Guessed from the filename when omitted.
contentIdstringContent-ID for inline embedding (cid:<id>).
disposition"attachment" | "inline"default: "attachment""inline" for embedded images.
TemplateRef
idstringrequiredId of a template registered in the store.
variablesRecord<string, unknown>Values substituted into the template; merged over the message's variables.
SendResult
idstring | undefinedProvider message id, when returned. Correlates to EmailEvent.messageId.
providerstringThe slug that handled the send.
status"sent" | "queued" | "scheduled"Normalized outcome.
acceptedstring[] | undefinedRecipients the provider accepted, when reported.
rejectedstring[] | undefinedRecipients the provider rejected, when reported.
rawunknownThe untouched provider response.
BatchResult
resultsSendResult[]One result per input message, in order.
batchedbooleantrue 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>ProRegister (or replace) a template. Validates it, then stores it.
email.templates.update(template: EmailTemplate): Promise<void>ProUpsert a template by id — identical to register.
email.templates.get(id: string): Promise<EmailTemplate | undefined>ProGet one template, or undefined.
email.templates.list(): Promise<EmailTemplate[]>ProList every registered template.
email.templates.delete(id: string): Promise<boolean>ProDelete by id; true if one was removed.
EmailTemplate
idstringrequiredStable id referenced by message.template.id.
subjectstringSubject; may contain {{var}} placeholders.
htmlstringHTML body; may contain placeholders.
textstringText body; may contain placeholders.
descriptionstringOptional 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[]>ProVerify 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): () => voidRegister a lifecycle hook. Returns an unsubscribe function. Many handlers can be
registered per event; failures are isolated via onEventError.
email.use(plugin: Plugin): thisInstall a plugin — a named bundle of lifecycle hooks plus optional send middleware.
email.useMiddleware(middleware: SendMiddleware): thisAdd 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.
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): booleanWhether a slug is Pro-gated (anything not in FREE_PROVIDERS).
Interpolation helpers
interpolate(template: string, variables: Record<string, unknown>): stringSubstitute {{var}} placeholders in a string.
interpolateFields(fields: { subject?, html?, text? }, variables): fieldsInterpolate 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): stringFormat as RFC 5322 — "Name <email>" or "email".
addressEmail(address: Address): stringExtract the bare email, dropping any display name.
addressName(address: Address): string | undefinedExtract 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): stringGet an attachment's bytes as base64 (a string is assumed already base64).
guessContentType(filename: string): stringGuess 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): voidRegister a custom adapter. The loader is an async () => ProviderConstructor,
imported on first use.
hasProvider(slug: string): booleanWhether a slug is registered (built-in or custom).
knownProviders(): string[]Every registered slug.
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.