npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@effect-kit/emails

v0.1.0

Published

Typed email sending for Effect with provider Layers, React rendering, and bounded concurrency.

Readme

@effect-kit/emails

An email library built for Effect. Typed messages, provider Layers, React rendering and bounded batches share one API across 16 providers.

Adapters use provider SDKs as optional peer dependencies. Core contracts and the testing Layer require only Effect.

Install

pnpm add @effect-kit/emails effect resend

Requires ESM and Node.js 24.22.0+. Install the SDK for each selected provider; React dependencies are required only for rendering. Supported peer versions are declared in package.json.

Quick start

Provide a Resend Layer, then access the Email service. Configure RESEND_API_KEY in the process environment:

export RESEND_API_KEY="your-resend-api-key"

Pass the API key configuration explicitly to Resend.layerConfig:

import { Config, ConfigProvider, Effect, Layer } from "effect";

import { Email } from "@effect-kit/emails";
import * as Resend from "@effect-kit/emails/resend";

const EmailLive = Resend.layerConfig(
  Config.all({
    apiKey: Config.Redacted("RESEND_API_KEY"),
  }),
).pipe(Layer.provide(ConfigProvider.layer(ConfigProvider.fromEnv())));

const program = Effect.gen(function* () {
  const email = yield* Email;

  return yield* email.send({
    from: "[email protected]",
    to: "[email protected]",
    subject: "Welcome",
    text: "Your account is ready.",
    html: "<p>Your account is ready.</p>",
  });
});

const receipt = await Effect.runPromise(program.pipe(Effect.provide(EmailLive)));

Config.all maps RESEND_API_KEY to the adapter's apiKey option. Config.Redacted wraps the credential in Redacted, hiding it from normal inspection. The supplied ConfigProvider reads process environment variables; a missing key fails Layer acquisition. .env loading is application-owned. Use a sender address configured with the provider.

Layer construction

All adapters expose the same constructors:

  • layerConfig(config) resolves an Effect Config value into adapter options.
  • layer(options) accepts resolved options with Redacted credentials.
  • layerClient(client) borrows a caller-owned SDK client without closing it.

Adapter-owned resources are scoped to their Layer. Replace the provider Layer without changing the Email service contract.

Messages and custom adapters

EmailMessage supports structured mailboxes, TO/CC/BCC, reply-to, text/HTML bodies, custom headers and attachments. Attachments use Uint8Array bytes, filename, contentType and optional contentId; filesystem paths and remote URLs are not fetched. Provider capabilities are checked before submission, with unsupported fields reported as typed errors.

Custom providers implement EmailAdapter. Email.make(adapter) applies message validation, capability checks, receipt validation and tracing. Supply the resulting service with Layer.succeed(Email, service).

Providers

Each adapter has an explicit @effect-kit/emails/<provider> subpath and optional SDK peers. Unused adapters do not load their dependencies.

| Import subpath | Additional packages | Configuration / limits | | -------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | /resend | resend | RESEND_API_KEY; idempotency keys can replay a previous response | | /nodemailer | nodemailer | SMTP transport; no idempotency | | /ses | @aws-sdk/client-sesv2 | AWS_REGION and AWS credentials | | /azure | @azure/communication-email | AZURE_EMAIL_CONNECTION_STRING; sender display name configured on the Azure resource | | /postmark | postmark | POSTMARK_SERVER_TOKEN; optional messageStream | | /sendgrid | @sendgrid/mail | SENDGRID_API_KEY; one reply-to | | /mailgun | mailgun.js, form-data | MAILGUN_API_KEY, MAILGUN_DOMAIN, MAILGUN_REGION; no inline images | | /mailersend | mailersend | MAILERSEND_API_KEY; one reply-to; attachments unsupported through the shared API | | /brevo | @getbrevo/brevo | BREVO_API_KEY; one reply-to; no attachments through the shared API; UUID idempotency keys reject duplicates | | /mailtrap | mailtrap | Explicit production/bulk/sandbox configuration; one reply-to | | /scaleway | @scaleway/sdk-tem, @scaleway/sdk-client | Project and credentials; fr-par region; no inline images | | /lettermint | lettermint | LETTERMINT_SENDING_TOKEN; inline images need qualified content IDs; idempotency replay | | /eusend | @eusend_dev/sdk | EUSEND_API_KEY; idempotency keys; retention window unspecified | | /zeptomail | zeptomail | ZEPTOMAIL_TOKEN and explicit ZEPTOMAIL_URL | | /mailchimp-transactional | @mailchimp/mailchimp_transactional | MAILCHIMP_TRANSACTIONAL_API_KEY; no inline images | | /plunk | @plunk/node | PLUNK_API_KEY; deprecated SDK: HTML and one TO address without a name |

Feature support depends on the adapter and provider account. SDK compatibility is covered by controlled transports and isolated consumers; live acceptance and inbox delivery remain separate provider guarantees.

React rendering

pnpm add @react-email/render react react-dom
pnpm add -D @types/react
import { Effect } from "effect";

import * as EmailReact from "@effect-kit/emails/react";

const Welcome = ({ name }: { name: string }) => <p>Hello {name}!</p>;

const content = await Effect.runPromise(EmailReact.render(<Welcome name="Ada" />));
// Spread content into email.send({ from, to, subject, ...content }).

render evaluates the component once and returns { html, text } for use in email.send. { mode: "html" } and { mode: "text" } select a single body. Expected rendering failures use RenderError; component defects preserve their Effect causes. Rendering requires trusted templates and does not sanitize untrusted HTML.

Batch sending

import { Email } from "@effect-kit/emails";

const program = Email.sendMany(
  [
    {
      message: {
        from: "[email protected]",
        to: "[email protected]",
        subject: "Welcome",
        text: "Your account is ready.",
      },
      options: { idempotencyKey: "welcome/account-123" },
    },
  ],
  { concurrency: 5 },
);

Provide an Email Layer to execute the batch. Email.sendMany accepts up to 1,000 requests and defaults to 5 concurrent submissions. Results preserve input order as Result<SendReceipt, EmailError>; expected failures remain per-request.

Explicit idempotency keys are preserved. Providers without idempotency support reject them. Invalid batch settings fail with BatchError before I/O. Batches are in-memory and non-atomic; defects and interruption preserve normal Effect semantics.

Testing

EmailTest.layer() provides Email and a recording EmailTest service without network access:

import { Effect } from "effect";

import { Email } from "@effect-kit/emails";
import { EmailTest } from "@effect-kit/emails/testing";

const captured = await Effect.runPromise(
  Effect.gen(function* () {
    const email = yield* Email;
    const recorder = yield* EmailTest;
    yield* email.send({
      from: "[email protected]",
      to: "[email protected]",
      subject: "Hello",
      text: "Offline",
    });
    return yield* recorder.messages;
  }).pipe(Effect.provide(EmailTest.layer())),
);

Errors and reliability

EmailError carries a tagged reason and submission acceptance evidence. Handle specific reasons with Effect.catchReason("EmailError", "RateLimitedError", handler). Reasons distinguish message validation, unsupported features, authentication, permissions, quotas, rejection, idempotency conflicts and transport failures.

The acceptance field records what is known about submission:

| Value | Meaning | | -------------- | ------------------------------------------------------------ | | notSubmitted | The message was not submitted to the provider. | | rejected | The provider explicitly rejected the submission. | | unknown | The provider may have accepted it; the outcome is uncertain. |

Receipts describe accepted or partially accepted submission, not inbox delivery. There are no automatic retries or provider failover. Timeouts, interruption and lost responses can follow remote acceptance; reconcile uncertain outcomes before resubmission.

Native cancellation depends on the SDK. SMTP cleanup uses inactivity timers without a fixed shutdown deadline. Caller-owned clients retain their native retry, timeout and logging policies.

Each Email.send span records operational attributes without message content or credentials. Expected errors omit raw SDK causes and unrecognized diagnostics. Exporters, logging and durable retry policy are application-owned. Programmer defects retain their original causes and require application-level handling when exported.

License

Apache-2.0. Copyright 2026 Effect Kit Contributors.