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

@structure-ai/mailer

v0.2.1

Published

Transactional email: one Mailer service over pluggable drivers (SMTP, Resend, Brevo, capture), typed templates with previews, transient-only bounded retry, per-driver metrics.

Readme

@structure-ai/mailer

Transactional email for Effect applications behind one port: a Mailer service over pluggable EmailDrivers — SMTP (dependency-free client over node:net/node:tls), Resend and Brevo (HTTP APIs), and an in-memory capture driver for tests and local development.

Messages are schema-validated (effect/Schema) before any driver sees them — including CRLF-injection checks on every header field. Delivery failures are classified transient (SMTP 4yz, timeouts, provider 5xx/429 — retried with bounded jittered backoff) or permanent (SMTP 5yz, provider 4xx — fail fast). Every send is logged and measured without ever logging the subject or body.

Quick start

import { Mailer, makeCaptureDriver } from "@structure-ai/mailer";
import { Effect } from "effect";

const MailerLive = Mailer.layer(makeCaptureDriver(), {
  defaultFrom: { email: "[email protected]", name: "Example" },
});

const program = Effect.gen(function* () {
  const mailer = yield* Mailer;
  yield* mailer.send({
    from: { email: "[email protected]", name: "Example" },
    to: [{ email: "[email protected]" }],
    subject: "Your export is ready",
    text: "Download it from the dashboard.",
    html: "<p>Download it from the dashboard.</p>",
  });
});

Templates and the preview wall

import { defineEmailTemplate, previewEntry, renderPreviews } from "@structure-ai/mailer";

const invite = defineEmailTemplate<{ tenant: string; url: string }>(
  "invite",
  (data) => ({
    subject: `You are invited to ${data.tenant}`,
    html: `<p>Join ${data.tenant}: ${data.url}</p>`,
    text: `Join ${data.tenant}: ${data.url}`,
  }),
  { tenant: "Acme", url: "https://app.example.com/invite/preview" }, // canonical preview data
);

yield* mailer.sendTemplate(invite, {
  data: { tenant: "Globex", url: invitationUrl },
  to: [{ email: "[email protected]" }],
});

// Dev preview wall payload (mount on a review route or dump from a CLI):
const previews = renderPreviews([previewEntry(invite)]);

Drivers

| Driver | Use | Failure classification | | --- | --- | --- | | makeSmtpDriver({ host, port, user, password, tls?, allowPlaintext?, … }) | Direct SMTP relay; STARTTLS required by default (a relay that offers none is refused before AUTH with smtp-tls-required), tls.mode: "implicit" for port 465, AUTH PLAIN/LOGIN. Plaintext only to a loopback relay or with allowPlaintext; validateSmtpOptions(options) reports a refused combination as a MailValidationError value for boot-time checks (driverFromSettings fails through its error channel), and a driver built from one refuses every send before AUTH. One connection per message. | 5yz → permanent, 4yz/timeouts/TLS → transient | | makeResendDriver({ apiKey, baseUrl? }) | Resend HTTP API over fetch (fetchImpl injectable for tests). | 4xx → permanent, 429/408/425/5xx/network → transient | | makeBrevoDriver({ apiKey, baseUrl? }) | Brevo transactional API (POST /v3/smtp/email, api-key header) over fetch (fetchImpl injectable for tests). Metrics label brevo. | 4xx → permanent, 429/408/425/5xx/network → transient | | makeCaptureDriver() | Tests and dev: records every message, never fails. | — |

Attachments ride as base64 EmailAttachments (≤10 MiB each, ≤10 per message); the SMTP driver renders proper multipart/mixed containers, RFC 2047 encoded-words for non-ASCII subjects and display names, dot-stuffed DATA payloads, and base64 transfer encoding for all bodies. Custom headers are emitted once, before the MIME headers, and may never carry a generated name (From, To, Cc, Bcc, Reply-To, Subject, Date, Message-ID, MIME-Version, Content-*): EmailHeaders refuses those keys.

Retry and observability

makeMailer(driver, { retry }) defaults to 3 total attempts with exponential backoff (200ms base, 10s cap, jitter) — transient failures only. Each driver label gets a bounded metric set: mailer_<driver>_sends_total, mailer_<driver>_failures_total, mailer_<driver>_transient_failures_total, mailer_<driver>_duration_ms. Log lines carry mailerDriver, mailerMessageId, mailerTemplate, recipient/attachment counts, and correlation ids — never subject, body, or attachment content.

Settings

mailerSettings (@structure-ai/config) selects the driver and its credentials; secrets load Redacted. MAIL_FROM accepts either [email protected] or a display form such as Platform <[email protected]>. layerFromSettings(settings) parses and validates the sender before it constructs the service. It also checks the selected driver's required setting: MAIL_SMTP_HOST for SMTP, MAIL_RESEND_API_KEY for Resend, or MAIL_BREVO_API_KEY for Brevo. SMTP requires TLS by default: MAIL_SMTP_TLS=starttls upgrades before AUTH and refuses a relay that does not offer it, implicit speaks TLS from the first byte on port 465 unless MAIL_SMTP_PORT says otherwise, and none is accepted only for a loopback relay or with MAIL_SMTP_ALLOW_PLAINTEXT=true, refused at composition otherwise.

| Name | Type | Required | Default | Secret | | --- | --- | --- | --- | --- | | MAIL_DRIVER | "capture" \| "smtp" \| "resend" \| "brevo" | no | capture | | | MAIL_FROM | email or Name <email> | no | [email protected] | | | MAIL_SMTP_HOST | string | when driver=smtp | — | | | MAIL_SMTP_PORT | port | no | 587, or 465 when MAIL_SMTP_TLS=implicit | | | MAIL_SMTP_USER | string | no | — | | | MAIL_SMTP_PASSWORD | secret | no | — | yes | | MAIL_SMTP_TLS | "starttls" \| "implicit" \| "none" | no | starttls | | | MAIL_SMTP_ALLOW_PLAINTEXT | boolean | no | false | | | MAIL_SMTP_TLS_REJECT_UNAUTHORIZED | boolean | no | true | | | MAIL_RESEND_API_KEY | secret | when driver=resend | — | yes | | MAIL_RESEND_BASE_URL | url (https, or http to loopback) | no | provider host | | | MAIL_BREVO_API_KEY | secret | when driver=brevo | — | yes | | MAIL_BREVO_BASE_URL | url (https, or http to loopback) | no | provider host | |

Errors

MailValidationError (permanent — schema rejection, mapped without echoing input values), MailRejected (permanent — driver refusal), MailDeliveryFailed (transient — retried by the mailer, retryAfterSeconds honored when the driver supplies one).