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 because it has the shortest setup and 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 and messageId carry the provider's message id, and raw keeps the untouched provider payload.

The same call handles more than one-off sends: recipientVariables turns one send into a personalized batch, and sendAt schedules delivery on providers with a scheduling API.

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 into the SDK (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, so sends using those fields throw instead of silently dropping data. Check field support when picking backup routes.

Next

On this page