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-email

v0.6.0

Published

Effect-first email SDK with provider-neutral core APIs and Resend/SMTP support.

Readme

effect-email

Effect-first email SDK with provider-neutral core APIs, trusted-runtime Resend and SMTP support, and an inspectable test adapter.

Install

For an application consuming the package:

bun add effect-email effect

For this repository:

bun install

Examples

Setup

For Resend, create an API key and expose it as RESEND_API_KEY in the trusted runtime that sends email.

RESEND_API_KEY=re_xxxx

effect-email/resend reads RESEND_API_KEY through Effect Config. Keep this adapter out of browser bundles and any runtime where provider secrets can be exposed.

[!NOTE] To send from your own domain, verify that domain in Resend first.

For SMTP, expose host, port, secure mode, username, and password in the trusted runtime.

SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=user
SMTP_PASSWORD=secret

effect-email/smtp reads these values through Effect Config. SMTP_PASSWORD is stored as a redacted Effect value. SMTP_SECURE=true means implicit TLS on connect; SMTP_SECURE=false still allows Nodemailer to use STARTTLS when the server supports it.

Usage

Send your first email. Messages are constructor-only: pass raw caller input to EmailMessage.make, then send the returned trusted message.

import { Effect } from "effect";
import { Email, EmailMessage } from "effect-email";
import * as Resend from "effect-email/resend";

const program = Effect.gen(function* () {
  const email = yield* Email;
  const message = yield* EmailMessage.make({
    from: "Acme <[email protected]>",
    to: "[email protected]",
    subject: "Hello from effect-email",
    text: "Sent with Effect.",
  });

  const receipt = yield* email.send(message);

  yield* Effect.logInfo(`sent ${receipt.provider}:${receipt.messageId}`);
});

await Effect.runPromise(program.pipe(Effect.provide(Resend.defaultLayer)));

To send the same provider-neutral message through SMTP, provide the SMTP layer instead.

import { Effect } from "effect";
import { Email, EmailMessage } from "effect-email";
import * as Smtp from "effect-email/smtp";

const program = Effect.gen(function* () {
  const email = yield* Email;
  const message = yield* EmailMessage.make({
    from: "Acme <[email protected]>",
    to: "[email protected]",
    subject: "Hello over SMTP",
    text: "Sent with effect-email/smtp.",
  });

  const receipt = yield* email.send(message);

  yield* Effect.logInfo(`accepted ${receipt.provider}:${receipt.messageId}`);
});

await Effect.runPromise(program.pipe(Effect.provide(Smtp.defaultLayer)));

SMTP receipts report provider acceptance only. The SMTP message ID is useful for tracing an accepted send, but it is not proof that a recipient inbox received the email and it is not a duplicate-send prevention key.

Send HTML

Pass html for HTML-only email, or pass both text and html for a multipart body.

const program = Effect.gen(function* () {
  const email = yield* Email;
  const message = yield* EmailMessage.make({
    from: "Acme <[email protected]>",
    to: "[email protected]",
    subject: "Welcome",
    text: "Welcome to Acme.",
    html: "<strong>Welcome to Acme.</strong>",
  });

  yield* email.send(message);
});

Send Attachments

Attachments are provider-neutral: pass a filename, media type, and bytes. The Resend adapter encodes them for the Resend API.

const program = Effect.gen(function* () {
  const email = yield* Email;
  const message = yield* EmailMessage.make({
    from: "Acme <[email protected]>",
    to: "[email protected]",
    subject: "Invoice",
    text: "Your invoice is attached.",
    attachments: [
      {
        name: "invoice.txt",
        mediaType: "text/plain",
        content: new TextEncoder().encode("invoice #123"),
      },
    ],
  });

  yield* email.send(message);
});

For inline attachments, add a Content ID and reference it from HTML with cid:. Content IDs are stored without MIME angle brackets and must be ASCII, non-empty, single-line values with exactly one @.

const program = Effect.gen(function* () {
  const email = yield* Email;
  const message = yield* EmailMessage.make({
    from: "Acme <[email protected]>",
    to: "[email protected]",
    subject: "Welcome",
    html: '<img src="cid:[email protected]" alt="Acme">',
    attachments: [
      {
        name: "logo.png",
        mediaType: "image/png",
        content: logoBytes,
        contentId: "[email protected]",
      },
    ],
  });

  yield* email.send(message);
});

Send Headers

Email Headers are provider-neutral custom fields on the message. Use a record for common cases:

const message =
  yield *
  EmailMessage.make({
    from: "Acme <[email protected]>",
    to: "[email protected]",
    subject: "Hello",
    text: "Plain",
    headers: { "X-Campaign-ID": "spring-2026" },
  });

Use an ordered list when order, casing, or spacing matters. Header names are trimmed and casing is preserved. Header values are preserved exactly, except blank, multiline, and control-character values are rejected. Parsed messages store headers as an EmailHeaders collection; use EmailHeaders.toReadonlyArray(message.headers) when test or adapter code needs ordered inspection.

const message =
  yield *
  EmailMessage.make({
    from: "Acme <[email protected]>",
    to: "[email protected]",
    subject: "Hello",
    text: "Plain",
    headers: [{ name: " X-Trace-ID ", value: "  keep spacing  " }],
  });

Email Headers are not raw MIME escape hatches. Structured fields such as From, To, Reply-To, and Subject; MIME, authentication, and delivery fields such as Content-Type, Message-ID, DKIM-Signature, and Received; duplicate names; and provider-reserved Resend-* / X-Resend-* names are rejected before send.

Every adapter also enforces local header limits through SendPolicy: 20 headers, 128 bytes per header name, 998 bytes per header value, and 8000 total header bytes by default. The Resend adapter maps accepted headers to Resend's headers object and omits it when no headers are present.

Test Adapter

Use effect-email/test when application code should send email but tests need inspection instead of network I/O.

import { assert, describe, it } from "@effect/vitest";
import { Effect } from "effect";
import { Email, EmailMessage } from "effect-email";
import * as TestEmail from "effect-email/test";

describe("email", () => {
  it.effect("records sent messages", () =>
    Effect.gen(function* () {
      const email = yield* Email;
      const message = yield* EmailMessage.make({
        from: "Acme <[email protected]>",
        to: "[email protected]",
        subject: "Hello",
        text: "World",
      });

      yield* email.send(message);

      const inspection = yield* TestEmail.TestEmailInspection;
      const sent = yield* inspection.takeSent;

      assert.strictEqual(sent.length, 1);
    }).pipe(Effect.provide(TestEmail.defaultLayer)),
  );
});

Custom Policy

Every adapter validates messages once through SendPolicy before provider/test adapter internals run. Override limits by providing your own policy layer. Limits must be positive finite integers; invalid limits fail at policy construction instead of at send time.

import { Layer } from "effect";
import { SendPolicy } from "effect-email";
import * as Resend from "effect-email/resend";
import { FetchHttpClient } from "effect/unstable/http";

const EmailLive = Resend.layer.pipe(
  Layer.provide(Resend.clientLayer),
  Layer.provide(
    Layer.succeed(SendPolicy)(
      SendPolicy.layer({
        ...Resend.policyConfig,
        maxRecipients: 10,
      }),
    ),
  ),
  Layer.provide(FetchHttpClient.layer),
);

Development

bun run build
bun run check
bun run test

Contributing

Issues and PRs are welcome.

For non-trivial features, new providers, or API changes, please open an issue first. PRs that do not fit the project direction, maintenance budget, or current scope may be closed even if the implementation is correct.

Small bug fixes, docs fixes, examples, tests, and clearly scoped improvements are the easiest to review and merge. See CONTRIBUTING.md.

Security

Please do not open public issues for suspected vulnerabilities. Report them privately through GitHub security advisories for this repository. See SECURITY.md.