Email SDK
Getting started

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-sdk

The 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:

lib/email.ts
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 one

send 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 resend
npx 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:

lib/email.ts
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

On this page