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

mailweaver

v1.0.1

Published

Library for sending emails

Readme

Mailweaver

npm version License: ISC GitHub stars

✉️  mailweaver
━━━━━━━━━━━━━━━━━━━━━━━━━━
  sendgrid v3, but:
   • typed
   • validated
   • production-safe

A reusable TypeScript library for sending emails via the SendGrid v3 API. Includes input validation against SendGrid limits, full type safety, production-grade error handling, and structured logging.

Installation

npm install mailweaver

Requirements: Node.js 18+ (uses native fetch)

Quick Start

import { SendGridClient } from "mailweaver";

const client = new SendGridClient({ apiKey: process.env.SENDGRID_API_KEY! });
await client.send({
  to: "[email protected]",
  from: "[email protected]",
  subject: "Hello",
  text: "Plain text body",
  html: "<p>HTML body</p>",
});

Setup

Create a SendGrid API key from the SendGrid dashboard and pass it to the client:

import { SendGridClient, createConsoleLogger } from "mailweaver";

// From environment variable (recommended)
const client = new SendGridClient({ apiKey: process.env.SENDGRID_API_KEY! });

// With optional config
const clientWithOptions = new SendGridClient({
  apiKey: process.env.SENDGRID_API_KEY!,
  baseUrl: "https://api.eu.sendgrid.com",  // EU region
  timeoutMs: 10000,                         // Request timeout
  logger: createConsoleLogger({ minLevel: "info" }),  // Structured logging
});

Ensure your from address is a verified sender in your SendGrid account.

Test send (development)

When developing or cloning the repo, you can verify your SendGrid setup by sending a real test email:

SENDGRID_API_KEY=your_key [email protected] npm run test:send -- [email protected]

Required environment variables:

| Variable | Description | |----------|-------------| | SENDGRID_API_KEY | Your SendGrid API key | | SENDGRID_FROM_EMAIL | A verified sender address from your SendGrid account |

The -- passes the recipient email to the script. You can also load env vars from .env.local or similar if your tooling supports it.

Integration tests against SendGrid

This repo includes an optional integration test suite that can hit the real SendGrid API in sandbox mode:

SENDGRID_API_KEY=your_key [email protected] npm test -- tests/integration.test.ts

By default, when SENDGRID_API_KEY is not set, the integration tests are skipped and only fast unit tests run. Use this sparingly in CI to avoid rate limits.

Basic Usage

Simple send (string addresses)

await client.send({
  to: "[email protected]",
  from: "[email protected]",
  subject: "Welcome",
  text: "Thanks for signing up!",
});

HTML and plain text

await client.send({
  to: "[email protected]",
  from: "[email protected]",
  subject: "Order Confirmation",
  text: "Your order #123 has been confirmed.",
  html: "<p>Your order <strong>#123</strong> has been confirmed.</p>",
});

Named sender and recipients

await client.send({
  to: [{ email: "[email protected]", name: "Alice" }],
  from: { email: "[email protected]", name: "Example Store" },
  subject: "Your Order",
  html: "<p>Hello Alice!</p>",
});

Advanced Usage

CC and BCC

await client.send({
  to: "[email protected]",
  cc: ["[email protected]"],
  bcc: [{ email: "[email protected]", name: "Archive" }],
  from: "[email protected]",
  subject: "Report",
  text: "See attached.",
});

Dynamic templates

await client.send({
  to: "[email protected]",
  from: "[email protected]",
  templateId: "d-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  dynamicTemplateData: {
    customerName: "Alice",
    orderId: "12345",
    items: ["Item A", "Item B"],
  },
});

Attachments

import { readFileSync } from "fs";

const pdf = readFileSync("invoice.pdf").toString("base64");

await client.send({
  to: "[email protected]",
  from: "[email protected]",
  subject: "Your Invoice",
  text: "Please find your invoice attached.",
  attachments: [
    {
      content: pdf,
      filename: "invoice.pdf",
      type: "application/pdf",
    },
  ],
});

Scheduled send

// Send 1 hour from now (max 72 hours ahead)
const sendAt = Math.floor(Date.now() / 1000) + 3600;

await client.send({
  to: "[email protected]",
  from: "[email protected]",
  subject: "Reminder",
  text: "Don't forget!",
  sendAt,
});

Sandbox mode

Validate your request without actually sending:

await client.send({
  to: "[email protected]",
  from: "[email protected]",
  subject: "Test",
  text: "This won't be delivered",
  sandboxMode: true,
});

Error Handling

The library uses typed errors with error codes for programmatic handling. All errors extend EmailerError and include a code property.

Error types

| Error | Code | When | |-------|------|------| | ValidationError | VALIDATION_ERROR | Input violates SendGrid limits or format rules | | ConfigurationError | CONFIGURATION_ERROR | Invalid client config (e.g. missing apiKey) | | SendGridError | SENDGRID_API_ERROR | SendGrid API returns 4xx/5xx | | TransportError | NETWORK_ERROR | Network failure (DNS, connection refused) | | TimeoutError | TIMEOUT_ERROR | Request timed out | | SerializationError | SERIALIZATION_ERROR | Request body could not be serialized to JSON |

ValidationError

Thrown before the request is sent:

import { SendGridClient, ValidationError } from "mailweaver";

try {
  await client.send({
    to: "invalid-email",
    from: "[email protected]",
    subject: "Test",
    text: "Hello",
  });
} catch (err) {
  if (err instanceof ValidationError) {
    console.error("Validation failed:", err.message, err.field);
  }
}

SendGridError and retries

Thrown when the SendGrid API returns an error. Use isRetryable() and getRetryAfterMs() for retry logic:

import { SendGridClient, SendGridError } from "mailweaver";

try {
  await client.send(options);
} catch (err) {
  if (err instanceof SendGridError) {
    console.error("API error:", err.statusCode, err.errors);
    if (err.isRetryable()) {
      const delayMs = err.getRetryAfterMs();  // For 429: uses rate limit reset
      if (delayMs) setTimeout(() => retry(), delayMs);
    }
  }
}

isRetryable() returns true for 429, 5xx, and 408. getRetryAfterMs() returns a suggested delay for 429 when rate limit headers are present.

Rate limits

On 429 Too Many Requests, the error includes rateLimit with limit, remaining, and reset (Unix timestamp):

if (err instanceof SendGridError && err.rateLimit) {
  console.log(`Limit: ${err.rateLimit.limit}, remaining: ${err.rateLimit.remaining}`);
  console.log(`Resets at: ${new Date(err.rateLimit.reset * 1000)}`);
}

Error serialization

All errors implement toJSON() for logging and monitoring:

catch (err) {
  if (EmailerError.isEmailerError(err)) {
    console.error(JSON.stringify(err.toJSON()));
  }
}

Logging

Pass a logger to enable structured, PII-safe logging. Logs are JSON-formatted and never include API keys, email content, or full addresses.

Built-in console logger

import { SendGridClient, createConsoleLogger } from "mailweaver";

const client = new SendGridClient({
  apiKey: process.env.SENDGRID_API_KEY!,
  logger: createConsoleLogger({
    minLevel: "info",   // "debug" | "info" | "warn" | "error"
    prefix: "[emailer]",
  }),
});

Custom logger

Implement the Logger interface to integrate with pino, winston, or your logging infrastructure:

import type { Logger, LogContext } from "mailweaver";

const myLogger: Logger = {
  debug: (msg, ctx) => log.debug(ctx, msg),
  info: (msg, ctx) => log.info(ctx, msg),
  warn: (msg, ctx) => log.warn(ctx, msg),
  error: (msg, ctx) => log.error(ctx, msg),
  child: (ctx) => myLogger.child ? myLogger.child(ctx) : myLogger,
};

const client = new SendGridClient({ apiKey: "...", logger: myLogger });

What gets logged

  • debug: Validation start, request start (recipient count, template usage)
  • info: Send succeeded (status code, rate limit)
  • warn: Validation failures
  • error: Send failed, API errors, network/timeout errors

EU Region

For EU regional subusers, use the EU base URL:

const client = new SendGridClient({
  apiKey: process.env.SENDGRID_API_KEY!,
  baseUrl: "https://api.eu.sendgrid.com",
});

Limitations

This library enforces SendGrid's documented limits:

| Constraint | Limit | |------------|-------| | Recipients (to + cc + bcc) | Max 1,000 per request | | Personalizations | Max 1,000 per request | | Total email size | Max 30MB | | Custom args | Max 10,000 bytes | | Reply-to list | Max 1,000 addresses | | Categories | Max 10, each max 255 chars | | Scheduled send | Max 72 hours in advance | | From field | ASCII only (no Unicode) |

See docs/LIMITATIONS.md for details.

API Reference

SendGridClient

const client = new SendGridClient(config: {
  apiKey: string;
  baseUrl?: string;
  timeoutMs?: number;
  logger?: Logger;
});

| Config | Type | Description | |--------|------|-------------| | apiKey | string | SendGrid API key (required) | | baseUrl | string | Override API base URL (e.g. EU region) | | timeoutMs | number | Request timeout in milliseconds | | logger | Logger | Optional structured logger |

send(options: SendEmailOptions): Promise

Sends an email. Returns { statusCode, headers, rateLimit? } on success.

Error classes and codes

  • ErrorCode – Constants: VALIDATION_ERROR, CONFIGURATION_ERROR, SENDGRID_API_ERROR, NETWORK_ERROR, TIMEOUT_ERROR, SERIALIZATION_ERROR, UNKNOWN_ERROR
  • EmailerError – Base class; use isEmailerError() and toJSON()
  • ValidationError – Pre-send validation failures
  • ConfigurationError – Invalid client config
  • SendGridError – API errors; isRetryable(), getRetryAfterMs()
  • TransportError – Network failures
  • TimeoutError – Request timeout
  • SerializationError – JSON serialization failure

Logger utilities

  • createConsoleLogger(options?) – JSON logger for console
  • noopLogger – No-op logger (default when none provided)
  • redactEmail(email) – Redact email for safe logging
  • createRequestId() – Generate request correlation ID

Types

  • SendEmailOptions – All options for a single send
  • EmailAddress{ email: string; name?: string }
  • Attachment{ content: string; filename: string; type?: string; disposition?: "inline" | "attachment"; content_id?: string }
  • SendResponse{ statusCode: number; headers: Record<string, string>; rateLimit?: RateLimitInfo }
  • Logger{ debug, info, warn, error, child? }

Show some love 💙

If this library saves you time or helps you ship more reliable email flows:

  • Star the repo on GitHub: DevboiDesigns/mailweaver
  • Share feedback or ideas via issues – real-world use cases help shape the API
  • Tell a friend or teammate who is tired of hand-rolling SendGrid calls

Every star and suggestion genuinely helps keep this project healthy and evolving.

License

ISC