Email SDK
GuidesBuild

Publish a community adapter

Ship a third-party provider adapter as its own npm package and list it in the Email SDK community registry.

This guide takes an adapter you have built — see Create an adapter — to a published npm package with a listing in the community registry. Community adapters stay third-party: you own the package and the provider relationship; the registry makes it discoverable.

SurfaceWho owns itWhere it lives
Official adapterEmail SDK maintainers@opencoredev/email-sdk/<adapter>
Community adapterYouYour npm package, e.g. email-sdk-acme-mail
Registry listingEmail SDK docs, by pull requestapps/fumadocs/content/community/plugins.json

Do not add community adapters to the SDK source, CLI, or exports — that path is for providers the project has agreed to maintain as official.

Name and scaffold the package

Follow the naming conventions so users can predict every identifier from the provider name:

ThingConventionExample
Packageemail-sdk-{provider}email-sdk-acme-mail
Adapterprovider slugacme-mail
Plugin idsame slugacme-mail
Env varuppercase slug + keyACME_MAIL_API_KEY
Exports{provider} and {provider}PluginacmeMail, acmeMailPlugin
email-sdk-acme-mail/
  src/
    index.ts
    acme-mail.ts
    plugin.ts
    acme-mail.test.ts
  package.json
  tsconfig.json
  README.md

Never publish under the @opencoredev scope, and never tell users to import from @opencoredev/email-sdk/{your-provider}.

Export both the adapter and its plugin

Export the plain adapter factory for users who want direct control, and the plugin wrapper for the one-line setup most apps use:

src/index.ts
export { acmeMail } from "./acme-mail";
export { acmeMailPlugin } from "./plugin";
export type { AcmeMailOptions } from "./acme-mail";

Declare @opencoredev/email-sdk as a peer dependency — a regular dependency can give apps two copies of the core types, and the registry's CI audit rejects packages without the peer dependency:

package.json
{
  "name": "email-sdk-acme-mail",
  "type": "module",
  "sideEffects": false,
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  },
  "peerDependencies": {
    "@opencoredev/email-sdk": "^0.6.0"
  }
}

Keep the package boring: no preinstall/install/postinstall scripts, no binaries, minimal runtime dependencies. Verified listings are audited for all three.

Document field support in the README

Field support determines whether your adapter is safe as a fallback route, so the README must state how each EmailMessage field behaves — mapped, or rejected with an error:

FieldSupportNotes
html, textYesSent as provider body fields.
cc, bccYesMapped to provider recipient fields.
replyToYesMapped to reply_to.
headersNoThrows EmailValidationError.
attachmentsNoThrows EmailValidationError.
tagsYesMapped to provider tags.
metadataNoThrows EmailValidationError.
idempotencyKeyNoProvider has no idempotency support.

Round out the README with: install command, a minimal createEmailClient example, required env vars, the test command, and links to the adapter contract and the provider's API docs.

Cover the package with tests

Beyond the adapter unit tests from Create an adapter (payload mapping, error retryability, unsupported-field rejection, AbortSignal forwarding), add one test proving the plugin registers through createEmailClient:

src/acme-mail.test.ts
import { expect, test } from "bun:test";
import { createEmailClient } from "@opencoredev/email-sdk";
import { acmeMailPlugin } from "./index";

test("registers the adapter via the plugin", () => {
  const email = createEmailClient({
    plugins: [acmeMailPlugin({ apiKey: "test" })],
  });

  expect(email.adapters.has("acme-mail")).toBe(true);
  expect(email.defaultAdapter).toBe("acme-mail");
});

Publish to npm

Publish from your own repository. Enable npm provenance or trusted publishing — it is required if you ever want the listing upgraded to verified.

Open the registry PR

Add an entry to apps/fumadocs/content/community/plugins.json in the Email SDK repo:

{
  "name": "Acme Mail",
  "package": "email-sdk-acme-mail",
  "kind": "adapter",
  "status": "community",
  "description": "Adds an Acme Mail provider adapter for Email SDK.",
  "href": "https://www.npmjs.com/package/email-sdk-acme-mail",
  "repo": "https://github.com/acme/email-sdk-acme-mail",
  "maintainer": "acme",
  "pluginId": "acme-mail",
  "adapter": "acme-mail",
  "importName": "acmeMailPlugin"
}

All listed fields except pluginId and importName are required; adapter is required for adapter and hybrid entries, and href/repo must be https URLs. Names, packages, and plugin ids must be unique across the registry.

Validate before pushing:

bun run community:check

CI runs the same check. For verified and official entries it additionally audits the published npm tarball: repository match, the peer dependency, no install scripts, no binaries, and an unchanged runtime dependency count.

Listing statuses

StatusMeaning
communityListed and discoverable, clearly third-party. The default for new packages.
verifiedOne published version passed the verification audit; requires verifiedVersion plus verification metadata in the entry.
officialMaintained by OpenCore or shipped in this repository. Not for third-party packages.

Verification applies to a single version. After a new release, the entry keeps the old verifiedVersion until someone reviews the new one — never mark a listing verified ahead of an actual review.

Next

On this page