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.
| Provider | Tier | Batch | Webhooks | Signed | Scheduling | Attachments | Tags | CC/BCC |
|---|---|---|---|---|---|---|---|---|
resend | Free | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
sendgrid | Free | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
smtp | Free | — | — | — | — | ✓ | — | ✓ |
postmark | Pro | ✓ | ✓ | — | — | ✓ | ✓ | ✓ |
ses | Pro | — | ✓ | ✓ | — | ✓ | ✓ | ✓ |
mailgun | Pro | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
brevo | Pro | — | ✓ | — | ✓ | ✓ | ✓ | ✓ |
loops | Pro | — | — | — | — | ✓ | — | — |
mailjet | Pro | ✓ | ✓ | — | — | ✓ | ✓ | ✓ |
sparkpost | Pro | — | ✓ | — | ✓ | ✓ | ✓ | ✓ |
elasticemail | Pro | — | — | — | — | ✓ | ✓ | ✓ |
mailersend | Pro | — | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
smtp2go | Pro | — | — | — | — | ✓ | — | ✓ |
pinpoint | Pro | — | — | — | — | — | — | — |
zeptomail | Pro | — | — | — | — | ✓ | — | ✓ |
termii | Pro | — | — | — | — | — | — | — |
Free providers
Resend
The default, modern-DX transport. Native single + batch sends, attachments, tags, scheduling, and Svix-signed webhooks.
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.
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.
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 nodemailer
— npm 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.
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).
RADON_SES_ACCESS_KEY_ID=...
RADON_SES_SECRET_ACCESS_KEY=...
RADON_SES_REGION=us-east-1Inline 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 SNS — parseWebhook 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.
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 keyInline 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.
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.
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.
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.
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.
RADON_SPARKPOST_API_KEY=...
RADON_SPARKPOST_REGION=us # "eu" → api.eu.sparkpost.comInline 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.
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.
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.
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/*).
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.
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.
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.
await email.send({
to: "ada@example.com",
subject: "Your balance",
variables: { name: "Ada", balance: "5,000" },
providerOptions: { templateId: "your-termii-template-id" },
}, { provider: "termii" });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.