CLI tool
opencoredev/email-sdk avatar
opencoredev/email-sdk

email-sdk counts its adapters three different ways and gates releases on a checked-in manifest

One simple SDK for transactional email across 23 adapters

517 stars27 forksMDXMIT

At a glance

What is it?
opencoredev/email-sdk is a TypeScript client that normalizes transactional email across provider APIs, with per-adapter retries, fallback routes, fail-fast field checks, test adapters, and a doctor CLI. The interesting engineering is in the release pipeline: nine gated commands, a committed adapter-verification manifest, seven live account checks, and telemetry that is on by default.
Who is it for?
email-sdk is worth adopting if you send transactional mail through more than one provider, or through one you would rather not be locked to, because the value is the normalized message, the per-adapter retry, and the cross-adapter fallback rather than any single integration. It is not worth the dependency if you use a single provider and never plan to change it.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly MDX, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • opencoredev/email-sdk517 ★ · +34
Star history
Repository2026-09-212026-11-13
opencoredev/email-sdk483517

34 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The adapter count is 23, 27, and 27 in three places

The number of providers this SDK claims is not consistent across the repository.

The repository description says one SDK for transactional email across 23 adapters. The feature list says adapters for 26 provider APIs plus SMTP, 27 adapters total. The prose list under the Adapters heading names twenty-six provider services, then SMTP, then a testing adapter, which also makes 27.

So the description is out of date, and the two places that agree count things the description does not. It is worth noticing what the 27 includes: SMTP, which is a protocol rather than an API, and a testing adapter that deliberately never calls a real provider. So an application that skips SMTP and uses the test adapter in CI is running with 25 live providers, not 27.

The named services run from the large to the very small: Resend, Postmark, SendGrid, AWS SES, Mailgun, Brevo, MailerSend, SparkPost, Mailchimp, Iterable, and Cloudflare sit alongside Loops, Plunk, Mailtrap, Unosend, Scaleway, ZeptoMail, MailPace, Sequenzy, JetEmail, Lettermint, Lettr, Primitive, SendHeron, and Helo.

Each one is imported from its own entry point rather than from the package root, so the pattern is a subpath import per provider, and the README suggests starting with `resend` for the fastest first send.

Field support is a build gate, not a runtime surprise

The feature that matters most here is the one about data disappearing silently.

The SDK performs fail-fast field-support checks before a provider drops data. Every provider API accepts a different subset of a message: attachments, scheduled send, custom headers, and some personalization features work on one and are silently ignored on another. Catching that at send time is what the check is for, and the documentation for it lives on a per-adapter field-support page.

What is more interesting is that this claim is enforced by the build. Two files are committed at the repository root, `adapter-verification.json` and `adapter-verification-evidence.json`, and there is a script that validates them with its own test suite. The release pipeline then runs, in order: lint, check types, test, an adapter-verification check, a community-registry check, a documentation-versions check, a documentation check, a build, and a package-tarball check. Publishing runs `changeset publish` and nothing else.

The documentation check is the part that closes the loop. A script called `check-adapter-support-docs` runs inside it, so the support matrix that appears on the website is verified against the same manifest the release gate reads. A separate script checks that the CLI documentation matches the CLI, and the verification script is exercised both from the repository root and from the documentation application's own library test, which is duplication rather than accident.

So the adapter count is not maintained by hand. It is checked by two JSON files and a set of scripts, which is also why the stale 23 in the repository description survived: that string is metadata, not one of the files anything reads.

Nine commands stand between a commit and a publish

The release script is one line and it is the clearest statement of what this project thinks matters.

`release:ci` runs lint, check-types, test, the adapter verification, a community-registry validation, a documentation-versions validation, the documentation checks, the build, and a package-tarball check, in that sequence, and it is a single command that fails on the first error. Nine gates for one publish.

The individual gates are unusually specific. Lint is not a bare linter invocation: it runs oxlint with nested configuration disabled, warnings denied, and unused disable directives reported as errors, and then runs a separate lint-policy script, so the lint configuration itself is a checked artifact. Formatting is oxfmt, and `check` runs lint first and then oxfmt with the write flag, which means that script rewrites files rather than only reporting on them.

Types and tests are both routed through a task runner with a workspace declaration of `apps/*` and `packages/*`, so the root package is private and orchestrates rather than builds. The tarball check is a script of its own, which is the step that would catch a file that should have been in the published package and was not.

None of this is visible in the README, which documents installation, the send call, and the CLI. Everything a maintainer relies on to make the thing trustworthy lives in package.json and the scripts directory.

Seven live account checks, and one provider the README never lists

Some of the release work talks to real providers, and the list of which ones is revealing.

There are seven scripts named after live account checks: `live:resend`, `live:sequenzy`, `live:lettermint`, `live:lettr`, `live:sendheron`, `live:helo`, and `live:graph`. Two more sit alongside them: `live:plan` runs a script that works out which live adapters changed, and `live:run` executes them. So the live set is computed per change rather than run wholesale.

Read the names and a pattern appears. Resend is the only large provider in that group. The rest are Sequenzy, LetterMint, Lettr, SendHeron, and Helo, which are small send-only services, and they are the kind of provider where a hand-written integration is most likely to drift out of date silently. That is a defensible priority order, and it is not the order of the adapter list, where the big names come first.

One name does not fit the documented set at all. There is a live check for a Graph account, and no Graph adapter appears in the list of twenty-six providers plus SMTP plus the test adapter. Either the adapter exists and the README has not caught up, or the check script is left over from something else. The repository does not say which, and `adapter-verification.json` would be the file to ask about.

There is a user-facing counterpart to all of this. The doctor command discovers adapters and validates setup, and `doctor --live` authenticates against a provider without sending anything, which is the safe way to find out whether a key is valid before you rely on it.

Telemetry is on by default and names your CI provider

The telemetry section is longer than the telemetry of most SDKs, and the list of what is sent is specific enough to audit.

It is enabled by default. The first run prints a notice with opt-out instructions, and everything is tied to a random anonymous identifier stored in a file at `~/.config/email-sdk/telemetry.json`.

What leaves your machine: built-in adapter names, with custom adapters reported only as `custom`, and CLI command names; logical send outcomes, adapter attempts, submitted and explicitly accepted message and recipient counts, uncertain outcomes, error codes, and send duration; total recipient counts summed across to, cc, and bcc, and whether a message carries attachments as a boolean rather than the files; whether scheduling was requested; SDK version, operating system, Node.js version, whether the run is happening in CI and which CI provider, and whether the usage came from the library or the bundled CLI; and redacted error reports consisting of the error type, the SDK error code, and stack traces with file paths reduced to package-relative names, with error messages scrubbed of addresses, URLs, quoted text, long tokens, and home directories before upload.

What never leaves it: email content, subjects, addresses, headers, attachments, API keys, and any other message data.

There are three ways to turn it off, and they are not equivalent. Two are environment variables.

bash
export EMAIL_SDK_TELEMETRY=0   # or DO_NOT_TRACK=1

One is per client.

ts
const client = createEmailClient({ adapters: [resend({ apiKey })], telemetry: false });

And one is automatic: telemetry is disabled when `NODE_ENV=test`. That last one is worth thinking about before you deploy, because a staging environment running with `NODE_ENV=test` reports nothing at all, silently.

Provider acceptance is not delivery, and the SDK counts the difference

One paragraph in the telemetry section is the most honest thing in the README.

Provider acceptance is not proof of delivery, and telemetry is best-effort, so the counts describe observed SDK usage rather than every email sent. The reader is pointed at a telemetry reference page for the counting rules.

That caveat has consequences for how the counters should be read, and the collection list explains why. The SDK distinguishes submitted messages and recipients from explicitly accepted ones, and it tracks uncertain outcomes as their own category rather than folding them into success or failure. So the three numbers are separate: what you handed over, what the provider said yes to, and what nobody knows.

This is the difference between an HTTP 200 from a provider API and a delivered message, and most send wrappers collapse it. Keeping the distinction in the counters is what lets you notice a provider accepting everything while delivering nothing, which is a real failure mode with some domains and some recipient servers.

Attachments are counted the same way, as a boolean per message, and the recipient total sums to, cc, and bcc together. So a single API call with four recipients reports four, not one, and a message with a 20MB attachment reports one attachment flag and no file size.

None of this is a delivery guarantee and none of it is presented as one. It is a measurement of what the SDK observed, labelled honestly as such.

The repository publishes more than the SDK, and bills four providers as sponsors

Two things in the tree are outside the story the README tells.

First, this is a monorepo whose root package is marked private, with workspaces declared as `apps/*` and `packages/*` and a task runner at the top of every build script. The releases confirm it: the most recent list contains `@opencoredev/[email protected]` and `@opencoredev/[email protected]` published three seconds apart on the same day, along with an earlier `@opencoredev/[email protected]`. So the repository ships at least one Convex integration alongside the email SDK, and a single release pass bumps several independently versioned packages, which is what changesets in the tree are for.

The rest of the layout is a documentation site and its tooling rather than the SDK. There is an `apps/` workspace holding a documentation application built with a framework whose name appears in the check scripts, a `cloud/` directory, a `vercel.json`, a `docs/` directory, a `Formula/` directory that indicates a Homebrew tap, and `skills/` and `tools/`. Two files at the root, `AGENTS.md` and `CLAUDE.md`, sit alongside a `preflight.yml` and a `bts.jsonc` whose purpose is not explained anywhere in the README.

Second, the sponsor block is not a static image. An HTML comment in the README explains that the logos are fetched live from a badge service, that new sponsors appear on their own, and that the top tier is pinned separately at a hundred dollars a month or more. The image URLs carry the sponsor handles as query parameters, and four of them are email providers: Resend, Status, Primitive, and LetterMint. Three of those four also have live account check scripts in package.json. The projects that fund the SDK are the projects whose integrations get checked against real accounts.

Editorial conclusion

email-sdk is worth adopting if you send transactional mail through more than one provider, or through one you would rather not be locked to, because the value is the normalized message, the per-adapter retry, and the cross-adapter fallback rather than any single integration. It is not worth the dependency if you use a single provider and never plan to change it. Three things to check first. Adapter coverage is unevenly verified: the release pipeline runs a verification script against a checked-in manifest before it will publish, but only seven providers have live account checks, and one of them names a Graph account that the README's list of 27 does not include. Telemetry is on by default and reports your CI provider by name, so choose between the environment variable and the per-client flag before your first production send. And provider acceptance is not delivery, which the project states itself, so do not build delivery reporting on the counts this SDK reports.

Frequently asked questions

How do I install the email-sdk package and what does it need to run?

Run npm install @opencoredev/email-sdk. The SDK is server-side only and needs Node 20 or newer, or Bun, and the documentation is explicit about keeping provider API keys out of client code.

How many email providers does email-sdk support?

The README says adapters for 26 provider APIs plus SMTP, 27 adapters in total, and names them in a prose list that also includes a testing adapter. The repository description says 23 adapters, which does not match either number.

What does the email-sdk doctor command do?

It discovers adapters and validates your setup. You can run it without installing anything with npx --package @opencoredev/email-sdk email-sdk doctor --adapter resend, and the live flag authenticates against the provider without sending anything. There are also dry-run smoke sends.

Does email-sdk send telemetry, and how do I turn it off?

Yes, by default, and the first run prints a notice. Opt out with EMAIL_SDK_TELEMETRY=0 or DO_NOT_TRACK=1 in the environment, or per client with the telemetry option set to false, and it is disabled automatically when NODE_ENV=test. Data is tied to a random anonymous ID in a file under your config directory.

What does email-sdk do when a provider does not support a message field?

It runs fail-fast field-support checks before a provider can drop data, and the per-adapter support pages document what each provider accepts. Those pages are verified against a committed adapter-verification manifest by a script that the release pipeline runs before publishing.

Official sources

  1. License: MIT
  2. opencoredev/email-sdk on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/opencoredev-email-sdk.svg)](https://hysenlabs.com/projects/opencoredev-email-sdk)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/opencoredev-email-sdk.svg)](https://hysenlabs.com/projects/opencoredev-email-sdk)

Was this useful?