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

sadasend

v0.1.1

Published

Official TypeScript/JavaScript SDK for SadaSend. Transactional email with unbypassable server-enforced guardrails for human engineers and autonomous AI agents (Claude, Cursor, MCP). Zero dependencies, native fetch & WebCrypto, automatic idempotency, and c

Readme

sadasend

npm version License: MIT Zero Dependencies TypeScript Strict Runtime Support

The official TypeScript and JavaScript SDK for SadaSend. High-performance transactional email with unbypassable server-enforced guardrails engineered for human engineering teams and autonomous AI agents.

Zero dependencies. Runs on native web standards (globalThis.fetch and crypto.subtle) across Node.js 18+, Bun, Deno, Cloudflare Workers, Next.js Edge, and modern browsers. No transitive supply-chain risks, no bloated bundles (< 8 KB unpacked).


Table of Contents


Why SadaSend? The AI Agent Moat

Traditional transactional email APIs (SendGrid, Mailgun, Postmark, Resend) were created exclusively for deterministic, hardcoded human backend jobs. When an autonomous AI agent (Claude Code, Cursor, LangChain, CrewAI, AutoGen) is given a traditional email API key, a single prompt injection or hallucination can spam thousands of external customers or burn your sending domain reputation.

SadaSend introduces Server-Enforced Guardrails that no agent prompt can talk its way past:

  ┌────────────────────────────────────────────────────────────────────────┐
  │  Human Engineer Mints Key in SadaSend Dashboard                        │
  │  {                                                                     │
  │    scopes: ['send', 'read'],            // Permitted operations        │
  │    allowlist: ['@internal-corp.com'],   // Who it may reach            │
  │    rateLimit: '100 / 1h',               // Token bucket ceiling        │
  │    mode: 'approval'                     // Require human sign-off      │
  │  }                                                                     │
  └───────────────────────────────────┬────────────────────────────────────┘
                                      │ Mints sada_agent_sk_...
                                      ▼
  ┌────────────────────────────────────────────────────────────────────────┐
  │  Autonomous Agent Code (TypeScript / Python / MCP)                     │
  │  const sadasend = new SadaSend(process.env.AGENT_KEY);                 │
  │                                                                        │
  │  ❌ Agent CANNOT widen allowlist or elevate scopes                     │
  │  ❌ Agent CANNOT mint new API keys (session-restricted endpoint)       │
  │  ❌ Agent CANNOT sign off on its own held messages                     │
  │  ✅ Server intercepts rogue sends and returns typed remediation        │
  └────────────────────────────────────────────────────────────────────────┘

Comparison Matrix

| Feature | Legacy Email APIs | SadaSend | | :--- | :---: | :---: | | Agent Recipient Allowlists | ❌ None (Sends anywhere) | 🟢 Server-Enforced (@domain.com or exact addresses) | | Human-in-the-Loop Mode | ❌ None (Direct to wire) | 🟢 Built-in Approval Queue (mode: 'approval') | | Zero-Cost Preflight Dry Runs | ❌ Burns quota | 🟢 Native dryRun: true (Validates deliverability free) | | Idempotency Guarantees | ⚠️ Manual / Inconsistent | 🟢 Auto UUIDv4 rides retries to prevent duplicate sends | | Result Type Model | ❌ Throws on every 4xx/5xx | 🟢 Discriminated Union (Result<T>: ok: true / ok: false) | | Dependencies | ❌ Often requires heavy SDKs | 🟢 Zero Dependencies (Pure native fetch & WebCrypto) | | Model Context Protocol (MCP) | ❌ Requires custom wrappers | 🟢 Native SSE MCP Server (https://mcp.sadasend.com/mcp/sse) |


Installation

Install the official package from npm via your preferred package manager:

# npm
npm install sadasend

# Bun
bun add sadasend

# pnpm
pnpm add sadasend

# yarn
yarn add sadasend

Ten-Line Quickstart

// bun add sadasend
import { SadaSend } from 'sadasend';

const sadasend = new SadaSend(process.env.SADASEND_API_KEY);

const result = await sadasend.emails.send({
  from: '[email protected]',
  to: '[email protected]',
  subject: 'It works.',
  html: '<strong>Sent from the SadaSend SDK.</strong>',
});

if (!result.ok) throw new Error(result.error.message);
console.log(result.status, result.id);

Note: The code above is an active integration test verified against a live server in CI via examples/ten-lines.ts.

For an executable walkthrough covering all 19 advanced features, inspect examples/full-features.ts.


Server-Enforced Agent Guardrails

1. Recipient Allowlists (RecipientNotAllowlistedError)

When an agent attempts to message an address outside its permitted domain or email list, SadaSend blocks delivery at the API gateway and returns the exact allowlist patterns so the agent can self-correct:

import { SadaSend, RecipientNotAllowlistedError } from 'sadasend';

const sadasend = new SadaSend(process.env.SADASEND_AGENT_KEY);

const result = await sadasend.emails.send({
  from: 'Agent <[email protected]>',
  to: '[email protected]',
  subject: 'Autonomous follow-up',
  text: 'Hello!',
});

if (!result.ok && result.error instanceof RecipientNotAllowlistedError) {
  console.error('Delivery prevented by server guardrail:');
  console.log('Violating recipient:', result.error.recipient);
  console.log('Permitted patterns:', result.error.allowlist);
  // Example: ["@yourcompany.com", "[email protected]"]
}

2. Human Approval Mode (mode: 'approval')

Keys minted in approval mode automatically place outgoing messages into pending_approval status. The message is queued safely in the SadaSend Web Dashboard until a team member approves or cancels it:

const send = await sadasend.emails.send({
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Proposal Agreement Draft',
  html: '<p>Please review the proposal draft.</p>',
});

if (send.ok && send.status === 'pending_approval') {
  console.log(`Held in human approval queue with ID: ${send.id}`);
}

3. Preflight Dry-Runs (dryRun: true)

Validate template compilation, suppression status, and domain SPF/DKIM verification without sending an email or consuming monthly plan quota:

const preview = await sadasend.emails.send({
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Weekly Digest',
  html: '<h1>Digest</h1>',
  dryRun: true,
});

if (preview.ok) {
  console.log('Dry run successful. Ready for dispatch.');
}

Comprehensive API Reference

1. Transactional Sends (emails.send)

Send rich transactional emails with attachments, tags, custom metadata, CC/BCC, and reply-to addresses:

const res = await sadasend.emails.send({
  from: 'Billing Team <[email protected]>',
  to: ['[email protected]'],
  cc: ['[email protected]'],
  replyTo: '[email protected]',
  subject: 'Invoice #INV-2026-10',
  html: '<h1>Your Invoice is Attached</h1><p>Thank you for your business!</p>',
  text: 'Your Invoice is Attached. Thank you for your business!',
  tags: ['billing', 'october-invoices'],
  headers: {
    'X-Entity-ID': 'cust_98124',
  },
  attachments: [
    {
      filename: 'invoice.pdf',
      content: Buffer.from('%PDF-1.4...').toString('base64'),
      contentType: 'application/pdf',
    },
  ],
  verifyBeforeSend: true, // Deliverability Shield: validates MX/syntax before send, burns 0 quota
  marketing: true,        // Automatically injects RFC 8058 One-Click List-Unsubscribe headers
  utm: {                  // Standardized analytics tracking
    source: 'invoicing_system',
    medium: 'email',
    campaign: 'q4_statements',
  },
  idempotencyKey: 'invoice-send-INV-2026-10',
});

if (res.ok) {
  console.log(`Message queued with ID: ${res.id}`);
}

2. High-Throughput Batch Sends (emails.batch)

Send up to 100 individual emails in a single atomic HTTP request. Returns an RFC 4918 207 Multi-Status object detailing per-item delivery outcomes:

const batch = await sadasend.emails.batch([
  { from: '[email protected]', to: '[email protected]', subject: 'Server Alert', text: 'CPU at 92%' },
  { from: '[email protected]', to: '[email protected]', subject: 'Server Alert', text: 'CPU at 92%' },
]);

if (batch.ok) {
  console.log(`Accepted: ${batch.data.accepted}, Refused: ${batch.data.refused}`);
  for (const item of batch.data.results) {
    if (item.ok) {
      console.log(`Item ${item.index}: Accepted ID ${item.id}`);
    } else {
      console.error(`Item ${item.index}: Refused due to ${item.error}`);
    }
  }
}

3. Scheduled Delivery & Atomic Cancellation (emails.cancel)

Schedule a future dispatch and cancel it before it leaves the server:

// Schedule dispatch for 2 hours in the future
const send = await sadasend.emails.send({
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Scheduled Notification',
  text: 'This is scheduled.',
  scheduledAt: new Date(Date.now() + 2 * 60 * 60 * 1000),
});

// Cancel if user cancels their action
if (send.ok) {
  const cancel = await sadasend.emails.cancel(send.id);
  if (cancel.ok) {
    console.log('Scheduled email cancelled successfully.');
  }
}

4. Sending Domains & Live DNS Verification (domains)

Programmatically register domains and retrieve required SPF, DKIM (2048-bit RSA), and DMARC DNS records:

// List domains
const domains = await sadasend.domains.list();

// Retrieve DNS records for setup
const records = await sadasend.domains.records('dom_981a2b');
if (records.ok) {
  for (const record of records.data.records) {
    console.log(`Type: ${record.type} | Host: ${record.host} | Value: ${record.value}`);
  }
}

// Perform live internet DNS verification
const verify = await sadasend.domains.verify('dom_981a2b');
if (verify.ok) {
  console.log(`Verification status: ${verify.data.status}`); // 'verified' | 'unverified'
}

5. Server-Side Liquid Dynamic Templates (templates)

Render server-side Liquid templates with strict input sanitization:

// List available templates
const templates = await sadasend.templates.list();

// Test render against test variables
const preview = await sadasend.templates.render('welcome-email', {
  first_name: 'Sarah',
  login_url: 'https://app.sadasend.com/login',
});

if (preview.ok) {
  console.log('Rendered HTML:', preview.data.html);
}

6. Automated & Manual Suppressions (suppressions)

Manage suppression lists to prevent contacting hard-bounced or unsubscribed recipients:

// Add manual suppression
await sadasend.suppressions.add('[email protected]', 'unsubscribe');

// Query suppression list
const list = await sadasend.suppressions.list({ q: '[email protected]' });
if (list.ok && list.data.suppressions.length > 0) {
  console.log('Recipient is suppressed:', list.data.suppressions[0]!.reason);
}

7. Account Deliverability & Telemetry (account)

Monitor your sending velocity, tier quotas, and deliverability health:

// Quota & tier metrics
const acc = await sadasend.account.get();
if (acc.ok) {
  console.log(`Plan: ${acc.data.plan} | Quota: ${acc.data.emailsUsed} / ${acc.data.emailQuota}`);
}

// 14-day deliverability ratios
const stats = await sadasend.account.stats({ window: '14d' });
if (stats.ok) {
  console.log(`Sent: ${stats.data.sent} | Delivered: ${stats.data.delivered} | Bounced: ${stats.data.bounced}`);
}

// Inspect active agent circuit breakers
const breakers = await sadasend.account.circuitBreakers();

8. API Key Governance & Approvals Queue (keys)

Audit active API keys and inspect held emails pending human sign-off:

// List registered keys
const keys = await sadasend.keys.list();

// List held emails waiting in the approval queue
const held = await sadasend.keys.approvals();
if (held.ok) {
  console.log(`Pending approval count: ${held.data.pending.length}`);
}

9. Real-Time Deliverability Verification & Shield (tools.verify, verifyBeforeSend)

Protect sender reputation and verify recipient mailbox deliverability in real time before sending without debiting account sending quota:

// 1. Standalone single recipient verification
const verified = await sadasend.tools.verify('[email protected]');
if (verified.ok) {
  console.log(`Verdict: ${verified.data.verdict}`);
  if (verified.data.checks) {
    console.log(`Deliverability score: ${verified.data.checks.score}/100, MX: ${verified.data.checks.mx}`);
  }
}

// 2. High-throughput batch verification
const batchVerified = await sadasend.tools.verifyBatch([
  '[email protected]',
  'invalid-syntax@bad',
  '[email protected]',
]);
if (batchVerified.ok) {
  console.log(`Verified ${batchVerified.data.total} addresses (${batchVerified.data.deliverable} deliverable)`);
}

// 3. Send-time deliverability shield: refuses undeliverables before queuing, debits 0 quota
const sendResult = await sadasend.emails.send({
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Welcome to our platform',
  text: 'Verify your account.',
  verifyBeforeSend: true, // Server performs instant MX, disposable, and syntax verification
  marketing: true,        // Automatically injects RFC 8058 One-Click List-Unsubscribe headers
});

10. Standardized UTM Campaign URL Tagging (tools.buildUtm)

Ensure zero broken tracking URLs across email marketing workflows:

const tagged = await sadasend.tools.buildUtm({
  url: 'https://yourdomain.com/launch?ref=partner#pricing',
  source: 'newsletter',
  medium: 'email',
  campaign: 'q4_launch',
  term: 'transactional',
  content: 'hero_cta',
});

if (tagged.ok) {
  console.log('Normalized URL:', tagged.data.url);
  // Preserves existing query params and hash anchor (#pricing)
}

11. Prepaid Credit Packs & Paddle Checkout (billing.creditsCheckout)

Initialize a one-time Paddle checkout transaction for prepaid verification or sending credit packs:

const checkout = await sadasend.billing.creditsCheckout({
  type: 'verify',      // 'verify' | 'send'
  pack: '25k',         // '5k' | '25k' | '100k' | '500k' for verify; '10k' | '50k' | '100k' for send
  returnUrl: 'https://yourdomain.com/dashboard/billing',
});

if (checkout.ok) {
  console.log('Transaction ID:', checkout.data.transactionId);
  console.log('Paddle Checkout URL:', checkout.data.checkoutUrl);
}

Timing-Safe HMAC Webhook Verification

SadaSend signs all webhook deliveries with HMAC-SHA256 (t=timestamp,v1=signature). Verify webhooks with zero third-party dependencies using native WebCrypto (crypto.subtle):

import { parseWebhook, verifyWebhook } from 'sadasend/webhooks';

// Example: Next.js App Router (app/api/webhooks/route.ts)
export async function POST(req: Request) {
  // CRITICAL: Must read as raw string. JSON.parse() changes formatting and breaks HMAC!
  const rawBody = await req.text();
  const signatureHeader = req.headers.get('sadasend-signature');

  if (!signatureHeader) {
    return new Response('Missing signature header', { status: 400 });
  }

  const event = await parseWebhook(
    process.env.SADASEND_WEBHOOK_SECRET!,
    rawBody,
    signatureHeader
  );

  if (!event) {
    return new Response('Invalid webhook signature or expired timestamp', { status: 401 });
  }

  // Handle typed event
  switch (event.type) {
    case 'email.delivered':
      console.log('Delivered message:', event.data.messageId);
      break;
    case 'email.bounced':
      console.warn('Bounced message:', event.data.messageId, event.data.detail);
      break;
    case 'email.complained':
      console.warn('Spam complaint recorded for:', event.data.messageId);
      break;
  }

  return new Response('OK', { status: 200 });
}
  • Replay Protection: Timestamps are cryptographically bound to the signed body and rejected if older than 5 minutes.
  • Timing-Attack Immune: Evaluated in constant time to prevent side-channel timing analysis.

Error Handling: Discriminated Unions

SadaSend returns a discriminated union result (Result<T>: { ok: true, data } | { ok: false, error }) instead of throwing exceptions. This ensures that guardrail refusals (such as allowlist violations, suppressions, or unverified domains) are handled as normal business logic:

const result = await sadasend.emails.send({
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Project Update',
  text: 'Here is the update.',
});

if (!result.ok) {
  if (result.error instanceof DomainNotVerifiedError) {
    // DNS records are attached directly to the error for instant remediation!
    console.error('Domain not verified. Configure these DNS records:');
    for (const rec of result.error.records) {
      console.log(rec.type, rec.host, rec.value);
    }
  } else if (result.error instanceof RecipientNotAllowlistedError) {
    console.error('Allowlist violation:', result.error.recipient);
  } else if (result.error instanceof RecipientSuppressedError) {
    console.error('Recipient suppressed:', result.error.reason);
  } else if (result.error instanceof RateLimitError) {
    console.error(`Rate limit exceeded. Retry in ${result.error.retryAfterSeconds}s`);
  }
  return;
}

// Success branch is automatically narrowed by TypeScript
console.log('Message ID:', result.id);

Error Hierarchy Table

| Error Class | Trigger Scenario | Automatic Retry | Attached Remediation Data | | :--- | :--- | :---: | :--- | | AuthenticationError | Missing, invalid, or revoked API key | No | None | | InsufficientScopeError | API key lacks required scope (send, read, domains) | No | required, held | | InvalidFromError | Sender address is malformed | No | None | | InvalidSubjectError | Subject contains carriage returns/control chars or is empty | No | None | | InvalidDateError | scheduledAt timestamp is in past or beyond 72-hour limit | No | None | | DomainNotFoundError | Sending domain is not registered on this account | No | None | | DomainNotVerifiedError | Sending domain has not completed SPF/DKIM verification | No | records (Table of required DNS records) | | RecipientNotAllowlistedError | Agent attempted to email an address not in allowlist | No | allowlist, recipient | | RecipientSuppressedError | Recipient previously bounced or unsubscribed | No | reason, recipient | | UndeliverableRecipientError | Deliverability Shield caught undeliverable/disposable recipient mailbox (HTTP 422) | No | recipient | | BatchSizeExceededError | Batch size exceeded account or tier limits (HTTP 400) | No | None | | PlanTierInsufficientError | Requested feature requires higher plan tier (HTTP 403) | No | None | | AnonymousLimitExceededError | Anonymous or unauthenticated playground quota exhausted (HTTP 429) | No | None | | InvitationAlreadyPendingError | A pending team invite already exists for this email (HTTP 409) | No | None | | InvalidPlanError | Invalid plan tier or pack parameter provided (HTTP 400) | No | None | | IdempotencyKeyReusedError | Same idempotency key passed with altered payload | No | None | | IdempotencyInProgressError | Original idempotent request is still processing | Yes | None | | RateLimitError | Per-key or account rate limit ceiling reached (HTTP 429) | Yes | retryAfterSeconds | | ServerError | Transient upstream 5xx error | Yes | HTTP status | | ConnectionError | Socket drop or DNS network timeout (Thrown) | Yes | Root cause, attempts |


Native Model Context Protocol (MCP) Integration

SadaSend features a native Model Context Protocol (MCP) Server-Sent Events (SSE) server for Cursor, Claude Code, Windsurf, and custom LLM runtimes:

Connect your AI agent with zero local software to install:

{
  "mcpServers": {
    "sadasend": {
      "url": "https://mcp.sadasend.com/mcp/sse",
      "headers": {
        "Authorization": "Bearer sada_agent_sk_your_key_here"
      }
    }
  }
}

Read the full Model Context Protocol Guide for tool declarations, schemas, and best practices.


Canonical Documentation & Technical References

For exhaustive architectural specifications, REST API contracts, and guides:


Frequently Asked Questions (FAQ)

How is SadaSend different from Resend, SendGrid, or AWS SES?

Traditional email APIs only provide basic transactional sending with no concept of AI agent safety. SadaSend introduces server-enforced recipient allowlists, human-in-the-loop approval queues, per-key rate limits, and preflight dry-runs. When an autonomous agent hallucinating an email address tries to send, SadaSend refuses the dispatch before real mail is queued.

Can an AI agent bypass its recipient allowlist or edit its scopes?

No. All guardrails are enforced on SadaSend servers. Key management and allowlist modification endpoints require secure browser sessions with CSRF protection and multi-factor authentication (2FA). API keys can never edit themselves or mint other keys.

Does the TypeScript SDK work in Cloudflare Workers and Next.js Edge?

Yes. The SDK uses zero Node.js-only built-in modules. It relies strictly on globalThis.fetch, crypto.subtle, and TextEncoder, running identically in Node.js 18+, Bun, Deno, Cloudflare Workers, Vercel Edge Functions, and modern browser environments.

What happens if an email request fails due to a network drop?

SadaSend automatically mints a UUIDv4 Idempotency-Key on every send. When the SDK retries after a connection timeout or network drop, it reuses the exact same idempotency key. The server returns the cached response if the initial request succeeded, preventing duplicate emails.

What is Human Approval Mode?

When an API key is minted with mode: 'approval', outgoing messages are held in pending_approval state rather than dispatched to the network. An operator can review the message body, recipient, and attachments in the SadaSend Web Console before clicking "Approve".


AI Agent Skill Integration

This repository includes a standardized workspace skill for AI coding assistants (Cursor, Claude Code, Antigravity, CrewAI, LangChain) at .agents/skills/sadasend-email.

It guides agents to:

  • Treat pending_approval as an intentional human-in-the-loop sign-off state rather than a failure
  • Conduct zero-cost preflight dry runs (dryRun: true) before making production changes
  • Self-correct on guardrail errors using attached remediation data (error.allowlist, error.records)
  • Enforce idempotency across multi-step execution loops

License & Support