Radondocs

Providers

All 16 email providers — required credentials, capabilities, and the real quirks Radon absorbs for you. Free (Resend, SendGrid, SMTP) and Pro (the other 13).

Radon Email ships 16 provider adapters. Three are free (Resend, SendGrid, generic SMTP); the other 13 are Pro Pro and unlock with a license. Every adapter reads its credentials from RADON_<PROVIDER>_* environment variables or the inline providers config (which takes precedence), and is built to the same completeness bar.

Credentials, two ways

providers: { resend: {} } reads everything from env vars. providers: { resend: { apiKey: "re_…" } } passes them inline. Inline wins over env. Radon never stores your secrets either way.

Capability matrix

= supported, = not supported. Read any of these at runtime from (await email.provider(slug)).capabilities.

ProviderTierBatchWebhooksSignedSchedulingAttachmentsTagsCC/BCC
resendFree
sendgridFree
smtpFree
postmarkPro
sesPro
mailgunPro
brevoPro
loopsPro
mailjetPro
sparkpostPro
elasticemailPro
mailersendPro
smtp2goPro
pinpointPro
zeptomailPro
termiiPro

Free providers

Resend

The default, modern-DX transport. Native single + batch sends, attachments, tags, scheduling, and Svix-signed webhooks.

.env
RADON_RESEND_API_KEY=re_...
RADON_RESEND_WEBHOOK_SECRET=whsec_...   # Svix signing secret (webhooks only)

Inline options: apiKey, webhookSecret. Batch endpoint (/emails/batch) accepts up to 100 messages. Webhooks are Svix-signed and verified. Has no server-side template system — use free {{var}} interpolation.

SendGrid

The v3 mail-send API. Native dynamic templates via the escape hatch.

.env
RADON_SENDGRID_API_KEY=SG....
RADON_SENDGRID_WEBHOOK_VERIFICATION_KEY=...   # base64 ECDSA public key (webhooks only)

Inline options: apiKey, webhookVerificationKey.

Message id lives in a header

SendGrid returns 202 Accepted with an empty body — the message id is in the X-Message-Id response header, which Radon reads for you into result.id. There's no native batch-of-distinct-messages endpoint, so sendBatch() falls back to sequential. The Event Webhook is ECDSA (P-256) signed and verified with the dashboard's verification key.

Native dynamic templates via providerOptions: { templateId: "d-…", dynamicTemplateData: {…} }.

SMTP (generic)

The one adapter that isn't an HTTP API — a fallback for any SMTP server.

.env
RADON_SMTP_HOST=smtp.example.com
RADON_SMTP_PORT=587
RADON_SMTP_SECURE=false     # true for port 465 (implicit TLS)
RADON_SMTP_USER=...
RADON_SMTP_PASS=...

Inline options: host, port, secure, user, pass, plus transport (inject a pre-built transporter) and transportOptions.

Requires nodemailer

SMTP isn't HTTP, so this adapter needs the optional peer dependency nodemailernpm install nodemailer. Using it without nodemailer throws MissingDependencyError. Every other provider is pure fetch and needs nothing extra. SMTP has no delivery webhooks, no tags, and no scheduling; sends return status: "sent" with the transport's accepted / rejected lists.


Pro providers

Postmark

Transactional-focused, with native batch and stored templates.

.env
RADON_POSTMARK_SERVER_TOKEN=...

Inline options: serverToken, messageStream (default "outbound"). Batch endpoint (/email/batch) accepts up to 500 messages. Supports a single tag (tags[0]), plus metadata and custom headers.

200 OK can still be an error

Postmark can return HTTP 200 with a non-zero ErrorCode (especially in a batch row). Radon raises ProviderApiError for a failed single send, and for batch rows marks the message rejected while preserving the row on result.raw. Webhooks are unsigned — secure the endpoint yourself.

Amazon SES

The raw cloud primitive — SES v2 over fetch with a hand-rolled AWS Signature V4 signer (zero @aws-sdk/* dependency).

.env
RADON_SES_ACCESS_KEY_ID=...
RADON_SES_SECRET_ACCESS_KEY=...
RADON_SES_REGION=us-east-1

Inline options: accessKeyId, secretAccessKey, sessionToken, region. Credentials fall back to the standard AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN / AWS_REGION env vars.

Attachments become raw MIME; webhooks arrive via SNS

A simple send uses Content.Simple; the moment a message has attachments, Radon assembles a raw MIME message and sends Content.Raw (SES's simple content can't carry files). Delivery events come through SNSparseWebhook verifies the SNS message signature against the RSA certificate at SigningCertURL (restricted to *.amazonaws.com) and handles the subscription-confirmation handshake. No scheduling, and no native batch (SES bulk needs a template).

Mailgun

Form-encoded HTTP API with HMAC-signed webhooks and EU-region support.

.env
RADON_MAILGUN_API_KEY=...
RADON_MAILGUN_DOMAIN=mg.yourdomain.com
RADON_MAILGUN_REGION=us              # "eu" → api.eu.mailgun.net
RADON_MAILGUN_WEBHOOK_SECRET=...     # HTTP webhook signing key

Inline options: apiKey, domain, region, webhookSecret. Scheduling maps to o:deliverytime; webhooks are HMAC-SHA256 signed and verified.

Multiple tags need the multipart path

A plain (url-encoded) send can carry a single o:tag. When a message has attachments, Radon switches to multipart/form-data and can attach multiple tags. Native stored templates are reached via the template field in providerOptions.

Brevo

Transactional API (formerly Sendinblue) with templates and scheduling.

.env
RADON_BREVO_API_KEY=...

Inline options: apiKey. Native templates via providerOptions: { templateId, params }. Webhooks are unsigned — secure the endpoint yourself.

Loops

A transactional-template platform — no arbitrary-body send.

.env
RADON_LOOPS_API_KEY=...

Inline options: apiKey.

Requires a transactional template id

Loops sends by targeting a transactionalId you create in its dashboard, with your message variables becoming dataVariables. Pass the id via providerOptions: { transactionalId: "…" } — a send without one throws a clear SendError. Sends to exactly one recipient per request; no CC/BCC, no tags, no delivery webhooks.

Loops send
await email.send({
  to: "ada@example.com",
  variables: { firstName: "Ada" },      // → dataVariables
  providerOptions: { transactionalId: "clfxxxx" },
}, { provider: "loops" });

Mailjet

The Send API v3.1 — natively batch-shaped, so sendBatch() is one request.

.env
RADON_MAILJET_API_KEY=...
RADON_MAILJET_SECRET_KEY=...

Inline options: apiKey, secretKey (Basic auth). Every send is a Messages array, so capabilities.batch is true and sendBatch() maps directly. Supports a single tag (as CustomID) and native templates (TemplateID + Variables). Webhooks are unsigned and arrive as an array of events.

SparkPost

The Transmissions API with scheduling and EU-region support.

.env
RADON_SPARKPOST_API_KEY=...
RADON_SPARKPOST_REGION=us            # "eu" → api.eu.sparkpost.com

Inline options: apiKey, region. Scheduling maps to options.start_time; a single tag becomes campaign_id. Webhooks are unsigned and arrive as an array of nested msys envelopes, normalized for you.

Elastic Email

The v4 HTTP API with attachments and stored templates.

.env
RADON_ELASTICEMAIL_API_KEY=...

Inline options: apiKey. Native templates via Content.TemplateName in providerOptions.

No webhook normalization

Elastic Email's event notifications are unsigned and coarse, so Radon does not normalize them (webhooks is false) rather than fabricate a scheme.

MailerSend

HTTP API with HMAC-signed webhooks and scheduling.

.env
RADON_MAILERSEND_API_KEY=...
RADON_MAILERSEND_WEBHOOK_SECRET=...

Inline options: apiKey, webhookSecret. Like SendGrid, a successful send is a 202 with the id in the X-Message-Id header. Scheduling maps to send_at; native templates via providerOptions: { template_id, personalization }. Webhooks are HMAC-SHA256 signed and verified. The async /bulk-email endpoint has different semantics, so sendBatch() falls back to sequential.

SMTP2GO

A JSON HTTP API — despite the name, no nodemailer required.

.env
RADON_SMTP2GO_API_KEY=...

Inline options: apiKey (sent in the request body, as SMTP2GO expects). Native templates via providerOptions: { template_id, template_data }. No tags, no scheduling, no delivery webhooks.

Amazon Pinpoint

Transactional email through Pinpoint's SendMessages API, SigV4-signed (mobiletargeting service, no @aws-sdk/*).

.env
RADON_PINPOINT_ACCESS_KEY_ID=...
RADON_PINPOINT_SECRET_ACCESS_KEY=...
RADON_PINPOINT_REGION=us-east-1
RADON_PINPOINT_APPLICATION_ID=...

Inline options: accessKeyId, secretAccessKey, sessionToken, region, applicationId. Credentials fall back to AWS_* env vars.

SimpleEmail is minimal — honestly so

Pinpoint's SimpleEmail carries no attachments, CC, or BCC — those capabilities are false, not stubbed. replyTo is supported. applicationId is required. Pinpoint streams delivery events via Kinesis/SNS rather than simple signed webhooks, so webhooks is false.

Zoho ZeptoMail

The ZeptoMail transactional API, authenticated with a send-mail token.

.env
RADON_ZEPTOMAIL_TOKEN=...

Inline options: token, baseUrl. The Zoho-enczapikey prefix is added for you if you omit it. For non-US data centers, set baseUrl (e.g. https://api.zeptomail.eu); default is api.zeptomail.com. Native templates via providerOptions: { template_key }. No tags, no scheduling, no webhooks.


Africa, first-class

Termii

Termii is a leading African (Nigeria-first) messaging platform, and a core reason Radon isn't just another Western-only SDK. It's built to Termii's real API, which is transactional-template / OTP driven — there's no arbitrary-body send.

.env
RADON_TERMII_API_KEY=...
RADON_TERMII_EMAIL_CONFIGURATION_ID=...

Inline options: apiKey, emailConfigurationId, baseUrl (default https://api.ng.termii.com). Every send is keyed by an email_configuration_id (the SMTP configuration from your Termii dashboard).

Template or OTP, one recipient, and a config id

A Termii send needs an email_configuration_id and one of: providerOptions.templateId (a Termii template) or providerOptions.code (an OTP-code email). With neither, it throws a clear SendError — no silent stub. Sends to exactly one recipient per request. No attachments, no CC/BCC, no tags, no delivery webhooks.

Termii template email
await email.send({
  to: "ada@example.com",
  subject: "Your balance",
  variables: { name: "Ada", balance: "5,000" },
  providerOptions: { templateId: "your-termii-template-id" },
}, { provider: "termii" });
Termii OTP email
await email.send({
  to: "ada@example.com",
  providerOptions: { code: "123456" },
}, { provider: "termii" });

Bring your own provider

Implement the EmailProvider interface (or extend BaseProvider) and register it — see API reference. Custom slugs are Pro-gated like any non-free provider.

Next steps

On this page