Quickstart
Send your first transactional email, verify it from the CLI, and add a fallback route.
This page takes you from zero to a verified production-ready send path: one adapter, one send() call, a CLI check, and a fallback route.
Install the package
npm install @opencoredev/email-sdkThe SDK and CLI run server-side on Node 20+ and Bun 1.1+. See Install for CLI-only usage.
Create a client
These docs use Resend first — shortest setup, broad field support. Any adapter works the same way; only the factory and credentials change.
Set RESEND_API_KEY, then:
import { createEmailClient } from "@opencoredev/email-sdk";
import { resend } from "@opencoredev/email-sdk/resend";
export const email = createEmailClient({
adapters: [resend({ apiKey: process.env.RESEND_API_KEY! })],
});Send an email
const result = await email.send({
from: "Acme <hello@acme.com>",
to: "user@example.com",
subject: "Welcome to Acme",
text: "Your account is ready.",
});
console.log(result.provider); // "resend" — the adapter that handled the send
console.log(result.id); // provider message id, when the API returns onesend resolves with a normalized response: provider tells you which adapter actually delivered, id/messageId carry the provider's message id, and raw keeps the untouched provider payload.
Verify from the CLI
Check the environment, then validate a full message without sending:
RESEND_API_KEY="re_..." npx email-sdk doctor --adapter resendnpx email-sdk send \
--adapter resend \
--from "Acme <hello@acme.com>" \
--to "user@example.com" \
--subject "Hello" \
--text "It works" \
--dry-run--dry-run validates the message shape and the adapter's field support and prints the message it would send. Drop the flag for one real smoke send.
Add a fallback route
A second adapter keeps simple transactional email flowing when the primary fails. SMTP is built in (no Nodemailer) and pairs well with API providers for plain text/HTML messages:
import { createEmailClient } from "@opencoredev/email-sdk";
import { resend } from "@opencoredev/email-sdk/resend";
import { smtp } from "@opencoredev/email-sdk/smtp";
export const email = createEmailClient({
adapters: [
resend({ apiKey: process.env.RESEND_API_KEY! }),
smtp({
host: process.env.SMTP_HOST!,
port: 587,
auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASS! },
}),
],
fallback: ["smtp"],
});Your application code does not change — email.send(...) now tries Resend, then SMTP.
Fallbacks must match your message shape
A fallback only helps if the backup adapter supports the fields you send. SMTP cannot carry tags, metadata, or attachments — sends using those fields throw instead of silently dropping data. Check field support when picking backup routes.
Next
Production send pipeline
Retries, idempotency keys, observability, and tests around this exact setup.
Fallbacks and retries
How route order, retry backoff, and error classification actually work.
All adapters
20 providers with per-adapter setup, options, and CLI smoke tests.
Test email behavior
Assert sends in unit tests with memory adapters and the capture plugin.
