Quickstart
Create a client, send a first transactional email, and verify the CLI path.
Start with the scoped package:
bun add @opencoredev/email-sdkThe installed CLI command is named email-sdk. If you only need the CLI for a quick check, run bunx --yes @opencoredev/email-sdk ... instead. See Install for the package and CLI distinction.
Send through Resend
Set RESEND_API_KEY, then create a client with the Resend adapter.
import { createEmailClient } from "@opencoredev/email-sdk";
import { resend } from "@opencoredev/email-sdk/resend";
const email = createEmailClient({
adapters: [resend({ apiKey: process.env.RESEND_API_KEY! })],
});
await email.send({
from: "Acme <hello@acme.com>",
to: "user@example.com",
subject: "Welcome to Acme",
text: "Your account is ready.",
});Check the send result
send resolves with the normalized adapter response. The provider value tells you which adapter
actually handled the send.
const result = await email.send(message);
console.log(result.provider);
console.log(result.id);If the adapter returns a message ID, Email SDK exposes it as both id and messageId. Provider
responses are still available through raw when an adapter includes them.
Verify from the CLI
Use doctor to check that the selected adapter has the required environment variables.
RESEND_API_KEY="re_..." bunx --yes @opencoredev/email-sdk doctor --adapter resendUse --dry-run to validate the message shape without sending email.
bunx --yes @opencoredev/email-sdk send \
--adapter resend \
--from "Acme <hello@acme.com>" \
--to "user@example.com" \
--subject "Hello" \
--text "It works" \
--dry-runWhen the package is installed in your project, the same command can run through the installed binary:
bun email-sdk doctor --adapter resendAdd SMTP fallback
Add SMTP when you want a second route for the same transactional email. The SMTP adapter is built in and does not require Nodemailer. When SMTP auth is configured on a non-secure connection, Email SDK upgrades with STARTTLS before authenticating.
Only use fallback adapters that can send the same message shape. For example, SMTP supports address fields and headers, but not provider-only fields like tags, metadata, or attachments.
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: "smtp.purelymail.com",
port: 587,
auth: {
user: process.env.SMTP_USER!,
pass: process.env.SMTP_PASS!,
},
}),
],
fallback: ["smtp"],
});Your application still sends the same way:
await email.send({
from: "Acme <hello@acme.com>",
to: "user@example.com",
subject: "Receipt",
text: "Thanks for your order.",
});