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

@sendmailos/sdk

v1.5.1

Published

Official JavaScript/TypeScript SDK for SendMailOS email API

Readme

@sendmailos/sdk

Official JavaScript/TypeScript SDK for SendMailOS email API.

Installation

npm install @sendmailos/sdk
# or
yarn add @sendmailos/sdk
# or
pnpm add @sendmailos/sdk

Quick Start

import { SendMailOS } from '@sendmailos/sdk';

const client = new SendMailOS('sk_live_your_api_key');

// Send a transactional email
await client.emails.send({
  to: '[email protected]',
  fromName: 'Your Company',
  fromEmail: '[email protected]',
  subject: 'Welcome!',
  html: '<h1>Hello!</h1><p>Welcome to our platform.</p>'
});

Features

  • Type-safe: Full TypeScript support with exported types
  • Lightweight: Zero dependencies for core SDK
  • Attachments: Send files via Base64 or URL (auto-fetched)
  • Email Automation: Build workflows with triggers, delays, conditions, and actions
  • Industry Templates: 50+ pre-built templates for different industries
  • Agency Workspaces: Manage multiple clients from one account
  • Pixel Tracking: Track website visitors and link to email campaigns
  • Webhooks: Real-time event notifications with signature verification
  • React Components: Pre-built components and hooks
  • Error Handling: Custom error classes for different scenarios
  • External IDs & Metadata: Correlate records with your system and store custom data

External IDs & Metadata

All resources support externalId and metadata fields for integrating with your existing systems.

// Store your system's ID and custom data on any resource
await client.subscribers.create({
  email: '[email protected]',
  externalId: 'your_system_user_123',  // Your internal ID
  metadata: {
    source: 'mobile_app',
    signup_campaign: 'summer_2024',
    customer_tier: 'gold'
  }
});

// Query by external ID
const { subscribers } = await client.subscribers.list({
  externalId: 'your_system_user_123'
});

// Works on all resources: subscribers, domains, campaigns, emails, workflows, pixels
await client.domains.create({
  domain: 'client.com',
  externalId: 'client_domain_456',
  metadata: { client_name: 'Acme Corp' }
});

await client.emails.send({
  to: '[email protected]',
  subject: 'Order Confirmation',
  html: '<h1>Thanks!</h1>',
  fromEmail: '[email protected]',
  externalId: 'order_email_789',
  metadata: { order_id: 'ORD-123', amount: 99.99 }
});

API Reference

Emails

// Send transactional email (Single Brand)
await client.emails.send({
  to: '[email protected]',
  subject: 'Hello!',
  html: '<h1>Welcome!</h1>',
  fromName: 'Acme',
  fromEmail: '[email protected]',
});

// Send email (Multi-Brand / SaaS Platform)
// Workspace auto-detected from fromEmail domain - no extra params needed!
await platform.emails.send({
  to: '[email protected]',
  subject: 'Your order is ready!',
  html: '<h1>Order Ready!</h1>',
  fromName: 'Pizza Palace',
  fromEmail: '[email protected]',  // ← Workspace auto-detected
});

// Send with template
await client.emails.sendTemplate({
  to: '[email protected]',
  templateId: 'tmpl_welcome',
  variables: { firstName: 'John' }
});

// Send with external ID and metadata (for tracking in your system)
await client.emails.send({
  to: '[email protected]',
  subject: 'Order Confirmation #12345',
  html: '<h1>Order Confirmed!</h1>',
  fromName: 'Acme Store',
  fromEmail: '[email protected]',
  externalId: 'order_email_12345',
  metadata: {
    order_id: 'ORD-12345',
    order_total: 149.99,
    source: 'checkout_flow'
  }
});

Attachments

Send emails with file attachments (max 5 files, 7MB total).

// With Base64 content
await client.emails.send({
  to: '[email protected]',
  subject: 'Invoice Attached',
  html: '<p>Please find your invoice attached.</p>',
  fromEmail: '[email protected]',
  fromName: 'Billing',
  attachments: [
    {
      filename: 'invoice.pdf',
      content: 'JVBERi0xLjQK...', // Base64 encoded
      contentType: 'application/pdf'
    }
  ]
});

// With URL (fetched automatically)
await client.emails.send({
  to: '[email protected]',
  subject: 'Your Report',
  html: '<p>Here is your report.</p>',
  fromEmail: '[email protected]',
  attachments: [
    {
      filename: 'report.pdf',
      url: 'https://cdn.example.com/reports/123.pdf',
      contentType: 'application/pdf'
    }
  ]
});

// Inline images (using cid)
await client.emails.send({
  to: '[email protected]',
  subject: 'Check out our logo',
  html: '<p>Our logo: <img src="cid:logo-image"></p>',
  fromEmail: '[email protected]',
  attachments: [
    {
      filename: 'logo.png',
      url: 'https://cdn.example.com/logo.png',
      contentType: 'image/png',
      cid: 'logo-image'  // Reference in HTML with cid:logo-image
    }
  ]
});

Attachment Limits: | Limit | Value | |-------|-------| | Max attachments | 5 | | Single file max | 5 MB | | Total size | 7 MB |

Subscribers

// Create subscriber (Single Brand)
const { subscriber } = await client.subscribers.create({
  email: '[email protected]',
  firstName: 'John',
  tags: ['newsletter', 'premium'],
  externalId: 'crm_contact_123',  // Optional: your system's ID
  metadata: { source: 'website' } // Optional: custom data
});

// Create subscriber (Multi-Brand / SaaS Platform)
const { subscriber } = await platform.subscribers.create({
  email: '[email protected]',
  firstName: 'Jane',
  domain: 'pizzapalace.com',  // Required for multi-brand
  tags: ['loyalty-member'],
  externalId: 'rb_customer_456',
  metadata: { restaurant_id: 'rest_789' }
});

// List subscribers
const { subscribers, total } = await client.subscribers.list({
  limit: 50,
  offset: 0
});

// List by external ID
const { subscribers } = await client.subscribers.list({
  externalId: 'crm_contact_123'
});

// List subscribers for a specific client (Multi-Brand)
const { subscribers } = await platform.subscribers.list({
  domain: 'pizzapalace.com',
  limit: 50
});

Campaigns

// Send campaign (Single Brand)
await client.campaigns.send({
  name: 'Weekly Newsletter',
  subject: 'What\'s new this week',
  fromName: 'Company Newsletter',
  fromEmail: '[email protected]',
  html: '<h1>Hello {{first_name}}!</h1>',
  tags: ['newsletter'], // Filter by subscriber tags
  externalId: 'campaign_jan_2024',  // Optional: track in your system
  metadata: { campaign_type: 'weekly' }
});

// Send campaign (Multi-Brand / SaaS Platform)
// Workspace auto-detected from fromEmail domain
await platform.campaigns.send({
  name: 'Pizza Promo',
  subject: 'New menu items!',
  fromName: 'Pizza Palace',
  fromEmail: '[email protected]',  // ← Workspace auto-detected
  html: '<h1>Check out our new items!</h1>',
  sourceDomains: ['pizzapalace.com'],  // Only send to subscribers from this domain
  externalId: 'rb_promo_123',
  metadata: { restaurant_id: 'rest_456' }
});

Domains

// Add sending domain (Single Brand)
const { domain } = await client.domains.create({
  domain: 'yourcompany.com',
  externalId: 'domain_001',  // Optional: your system's ID
  metadata: { environment: 'production' }
});

console.log('DNS Records to add:', domain.dnsRecords);

// Add client domain (Multi-Brand / SaaS Platform)
// Show these DNS records to your client in your UI
const { dns_records, domain_id } = await platform.domains.create({
  domain: 'pizzapalace.com',
  externalId: 'rb_domain_456',
  metadata: { client_name: 'Pizza Palace' }
});

// After client verifies DNS:
// ✓ Domain status becomes "verified"
// ✓ Workspace is AUTO-CREATED for this domain
// ✓ You can now send emails from @pizzapalace.com

Organization & Industries

Set your industry during onboarding to get pre-built templates and workflows.

import { SendMailOS, INDUSTRIES } from '@sendmailos/sdk';

const client = new SendMailOS('sk_live_...');

// Get current organization
const org = await client.organization.get();

// Set industries (multi-select)
await client.organization.setIndustries(['ecommerce', 'saas']);

// Convert to agency account (enables workspaces)
await client.organization.convertToAgency();

// Available industries (50+)
console.log(INDUSTRIES);
// { hotels: 'Hotels & Hospitality', restaurants: 'Restaurants & Food Service', ... }

Supported Industries

Hotels, Restaurants, Gyms, Travel Agencies, Real Estate, Schools, E-commerce, Dentists, Car Dealerships, Events, Beauty Salons, Law Firms, Non-Profits, SaaS, Recruitment, Insurance, Online Courses, Municipalities, Personal Trainers, Influencers, Doctors/Clinics, Nightclubs, Coworking, Wedding Planners, Art Galleries, Car Rentals, Podcasters, Consultants, Bakeries, Veterinarians, Airbnb Hosts, Accountants, Developers, Musicians, Spas, Bookstores, Cleaning Services, Churches, Graphic Designers, Coffee Shops, Florists, Political Campaigns, Libraries, Taxis/Limos, Home Inspectors, Photographers, Universities, Fashion, Nutritionists, Startups

Account Types

Single Brand (Business)

For companies sending emails from their own domain. Simple setup, no workspaces needed.

const client = new SendMailOS('sk_live_your_api_key');

await client.emails.send({
  to: '[email protected]',
  fromEmail: '[email protected]',
  subject: 'Welcome!',
  html: '<h1>Hello!</h1>'
});

Multi-Brand / SaaS Platform

For agencies or SaaS platforms managing multiple clients. Each client gets their own workspace with isolated data.

// Your platform's master API key
const platform = new SendMailOS('sk_live_platform_key');

// 1. Client signs up → Register their domain
const { dns_records } = await platform.domains.create({
  domain: 'pizzapalace.com'
});
// Show dns_records to client in your UI

// 2. Domain verified → Workspace auto-created!
//    (No manual workspace creation needed)

// 3. Send emails → Workspace auto-detected from fromEmail
await platform.emails.send({
  to: '[email protected]',
  fromEmail: '[email protected]',  // ← Workspace detected automatically!
  fromName: 'Pizza Palace',
  subject: 'Your order is ready!',
  html: '<h1>Order Ready!</h1>'
});

// 4. Add subscribers → Use domain param
await platform.subscribers.create({
  email: '[email protected]',
  domain: 'pizzapalace.com'  // Required for multi-brand
});

// 5. List subscribers for a specific client
const { subscribers } = await platform.subscribers.list({
  domain: 'pizzapalace.com'
});

// 6. Send campaign → Workspace auto-detected
await platform.campaigns.send({
  subject: 'New Menu Items!',
  fromName: 'Pizza Palace',
  fromEmail: '[email protected]',  // ← Auto-detected
  html: '<h1>Check out our new menu!</h1>'
});

How Workspace Detection Works

| Action | How workspace is identified | |--------|----------------------------| | Register domain | Creates domain record (pending) | | Domain verified | Workspace auto-created | | Send email | Auto-detect from fromEmail domain | | Send campaign | Auto-detect from fromEmail domain | | Add subscriber | From domain parameter | | List subscribers | From domain parameter |

One verified domain = One workspace. Simple.

Master API Keys

Master API keys can access all workspaces in your organization with a single key. Ideal for SaaS platforms that need to manage multiple client workspaces programmatically.

Recommended Flow for SaaS Platforms

// Your platform backend - when a new client signs up
const platform = new SendMailOS('sk_live_master_key');

// 1. Create workspace first (with your client's data)
const { workspace } = await platform.workspaces.create({
  name: 'Pizza Palace',           // From your user's profile
  industry: 'restaurants',        // Optional - can be null
  website: 'https://pizzapalace.com'
});

// 2. Add domain TO that workspace
const { dns_records } = await platform.domains.create({
  domain: 'pizzapalace.com',
  workspaceId: workspace.id       // Link to the workspace
});

// 3. Show DNS records to your user
// They verify DNS, domain becomes active

// 4. Now send emails using workspace_id or domain
await platform.emails.send({
  to: '[email protected]',
  fromEmail: '[email protected]',
  subject: 'Welcome!',
  html: '<h1>Welcome!</h1>',
  workspaceId: workspace.id
});

Your end users only see: "Add domain → Verify DNS → Done"

Behind the scenes: Workspace created with proper settings → Domain linked → Ready to send.

Master Key vs Workspace Key: | Feature | Master Key | Workspace Key | |---------|-----------|---------------| | Access | All workspaces | Single workspace | | workspaceId param | Required | Not needed | | Use case | Platform/SaaS backend | Per-client integrations |

Agency Workspaces (Advanced)

For more control, you can manually manage workspaces:

// Convert to agency account first
await client.organization.convertToAgency();

// Create client workspaces manually
const pizzaPlace = await client.workspaces.create({
  name: 'Pizza Palace',
  industry: 'restaurants',
  import_templates: true  // Auto-import restaurant templates
});

// List all clients
const { workspaces } = await client.workspaces.list();
for (const ws of workspaces) {
  console.log(`${ws.name}: ${ws.stats?.subscribers} subscribers`);
}

// Get detailed stats
const details = await client.workspaces.get(pizzaPlace.id);
console.log(details.stats);
// { total_subscribers: 1200, active_subscribers: 1150, total_campaigns: 24, ... }

// Invite client to view reports
await client.workspaces.inviteClient(pizzaPlace.id, '[email protected]');

Workflows

Automate email sequences with workflows. Create them via API, build the steps in your UI, then trigger them programmatically.

Workflow Lifecycle

1. Create workflow (draft)
2. Add nodes & edges (build the flow)
3. Activate workflow (start accepting triggers)
4. Trigger for subscribers (via API or events)

Create & Manage Workflows

// Create a workflow for a client (Multi-Brand)
const { data } = await platform.workflows.create({
  name: 'Welcome Sequence',
  domain: 'pizzapalace.com',  // Auto-detect workspace
  trigger: { type: 'api' }    // Triggered via API
});

const workflowId = data.workflow.id;

// List workflows
const { data: list } = await platform.workflows.list({ domain: 'pizzapalace.com' });

// Get workflow with nodes/edges
const { data: workflow } = await platform.workflows.get(workflowId);

// Delete workflow
await platform.workflows.delete(workflowId);

Build Workflow Steps

Update a workflow with nodes (steps) and edges (connections):

await platform.workflows.update(workflowId, {
  nodes: [
    {
      id: 'trigger-1',
      type: 'trigger',
      data: { triggerType: 'api' }
    },
    {
      id: 'email-1',
      type: 'email',
      data: {
        subject: 'Welcome to {{restaurantName}}!',
        templateId: 'tmpl_welcome'
      }
    },
    {
      id: 'delay-1',
      type: 'delay',
      data: { duration: 3, unit: 'days' }
    },
    {
      id: 'email-2',
      type: 'email',
      data: {
        subject: 'Your first order discount',
        templateId: 'tmpl_promo'
      }
    }
  ],
  edges: [
    { id: 'e1', source: 'trigger-1', target: 'email-1' },
    { id: 'e2', source: 'email-1', target: 'delay-1' },
    { id: 'e3', source: 'delay-1', target: 'email-2' }
  ]
});

Node Types

| Type | Description | Data Fields | |------|-------------|-------------| | trigger | Entry point | triggerType | | email | Send email | subject, templateId, fromName, fromEmail | | delay | Wait period | duration, unit (minutes/hours/days) | | condition | Branch logic | field, operator, value | | tag | Add/remove tags | action (add/remove), tags | | http | Call webhook | url, method, headers, body | | wait_for_event | Wait for event | eventName, timeout | | unsubscribe | Unsubscribe user | - | | exit | End workflow | - |

Activate & Pause

// Activate workflow (start accepting triggers)
await platform.workflows.activate(workflowId);

// Pause workflow (stop new triggers, existing runs continue)
await platform.workflows.pause(workflowId);

Trigger Workflows

// When a customer signs up at the restaurant
await platform.workflows.trigger(workflowId, {
  email: '[email protected]',
  data: {
    firstName: 'John',
    restaurantName: 'Pizza Palace',
    signupSource: 'website'
  }
});

Wait for Events

Workflows can pause and wait for specific events:

// Workflow has a "wait_for_event: purchase_completed" node
// When customer makes a purchase, send the event:
await platform.workflows.sendEvent({
  event: 'purchase_completed',
  email: '[email protected]',
  data: { orderTotal: 45.99 }
});
// The workflow resumes from where it was waiting

Example: Restaurant Welcome Flow

// 1. Create workflow
const { data } = await platform.workflows.create({
  name: 'New Customer Welcome',
  domain: 'pizzapalace.com'
});

// 2. Build the flow
await platform.workflows.update(data.workflow.id, {
  nodes: [
    { id: '1', type: 'trigger', data: { triggerType: 'api' } },
    { id: '2', type: 'email', data: { subject: 'Welcome!', templateId: 'tmpl_welcome' } },
    { id: '3', type: 'delay', data: { duration: 2, unit: 'days' } },
    { id: '4', type: 'condition', data: { field: 'has_ordered', operator: 'eq', value: true } },
    { id: '5', type: 'email', data: { subject: 'Thanks for ordering!', templateId: 'tmpl_thanks' } },
    { id: '6', type: 'email', data: { subject: '10% off your first order', templateId: 'tmpl_promo' } }
  ],
  edges: [
    { id: 'e1', source: '1', target: '2' },
    { id: 'e2', source: '2', target: '3' },
    { id: 'e3', source: '3', target: '4' },
    { id: 'e4', source: '4', target: '5', sourceHandle: 'yes' },
    { id: 'e5', source: '4', target: '6', sourceHandle: 'no' }
  ]
});

// 3. Activate
await platform.workflows.activate(data.workflow.id);

// 4. Trigger when new customer signs up
await platform.workflows.trigger(data.workflow.id, {
  email: '[email protected]',
  data: { firstName: 'Jane', restaurantName: 'Pizza Palace' }
});

Webhooks

Receive real-time notifications when events occur (emails sent, opened, clicked, bounced, etc.).

Create & Manage Webhooks

// Create a webhook endpoint
const { webhook } = await client.webhooks.create({
  url: 'https://yourserver.com/webhooks',
  events: ['email.sent', 'email.delivered', 'email.opened', 'email.clicked', 'email.bounced'],
});

console.log('Webhook secret:', webhook.secret); // Use this to verify signatures

// List all webhooks
const { webhooks } = await client.webhooks.list();

// Get webhook with delivery history
const { webhook, recentDeliveries, stats } = await client.webhooks.get('webhook-id');

// Update webhook
await client.webhooks.update('webhook-id', {
  events: ['email.sent', 'email.bounced', 'email.complained']
});

// Test webhook endpoint
const result = await client.webhooks.test('webhook-id');
if (result.test.sent) {
  console.log(`Test delivered in ${result.test.responseTimeMs}ms`);
}

// Disable/Enable webhook
await client.webhooks.disable('webhook-id');
await client.webhooks.enable('webhook-id');

// Delete webhook
await client.webhooks.delete('webhook-id');

Event Types

| Event | Description | |-------|-------------| | email.sent | Email was sent to the recipient | | email.delivered | Email was delivered to recipient's server | | email.opened | Recipient opened the email | | email.clicked | Recipient clicked a link in the email | | email.bounced | Email bounced (hard or soft) | | email.complained | Recipient marked email as spam | | subscriber.created | New subscriber was added | | subscriber.updated | Subscriber was updated | | subscriber.unsubscribed | Subscriber unsubscribed | | campaign.sent | Campaign finished sending | | workflow.started | Workflow execution started | | workflow.completed | Workflow execution completed |

Pixel Tracking

Track website visitors and connect their behavior to email campaigns.

Basic Setup

import { SendmailPixel } from '@sendmailos/sdk';

const pixel = new SendmailPixel({
  token: 'pk_live_your_public_key',
  debug: false // Set true for console logging
});

pixel.init();

Track Events

// Track custom events
pixel.track('button_clicked', { button_id: 'cta-signup' });

// Track page views (called automatically on init)
pixel.pageView();

// Identify a visitor by email
pixel.identify('[email protected]', {
  first_name: 'John',
  plan: 'premium'
});

Privacy Controls

// Opt out of tracking (e.g., user declines cookies)
pixel.optOut();

// Opt back in
pixel.optIn();

// Check opt-out status
if (pixel.isOptedOut()) {
  console.log('Tracking disabled');
}

// Reset identity (e.g., on logout)
pixel.reset();

Global smp() Function

For script tag usage, use createGlobalPixel to set up a global smp() function:

import { createGlobalPixel } from '@sendmailos/sdk';

createGlobalPixel({ token: 'pk_live_...' });

// Now use anywhere:
smp('track', 'event_name', { property: 'value' });
smp('identify', '[email protected]');
smp('optOut');
smp('optIn');

React Pixel Hooks

import { usePixel, usePageTracking, useIdentifyOnLogin } from '@sendmailos/sdk/react';

// Basic usage
function MyComponent() {
  const { track, identify, optOut } = usePixel('pk_live_...');

  return (
    <button onClick={() => track('button_clicked', { id: 'cta' })}>
      Click Me
    </button>
  );
}

// Auto page tracking (Next.js App Router)
function Layout({ children }) {
  const pathname = usePathname();
  usePageTracking('pk_live_...', pathname);
  return <>{children}</>;
}

// Auto identify on login
function App() {
  const { user } = useAuth();
  useIdentifyOnLogin('pk_live_...', user?.email, {
    first_name: user?.firstName,
    plan: user?.plan
  });
  return <Main />;
}

Data Attributes

Track clicks without JavaScript using HTML data attributes:

<button data-smp-track="signup_clicked">Sign Up</button>

<button
  data-smp-track="button_clicked"
  data-smp-track-button-id="cta-main"
  data-smp-track-variant="blue"
>
  Get Started
</button>

React Integration

import { SendMailOSProvider, SubscribeForm } from '@sendmailos/sdk/react';

function App() {
  return (
    <SendMailOSProvider publicKey="pk_live_...">
      <SubscribeForm
        tags={['newsletter']}
        onSuccess={() => console.log('Subscribed!')}
        buttonText="Join Newsletter"
      />
    </SendMailOSProvider>
  );
}

Custom Form with Hook

import { useSubscribe } from '@sendmailos/sdk/react';

function CustomForm() {
  const { subscribe, isLoading, error, isSuccess } = useSubscribe();
  const [email, setEmail] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await subscribe({ email, tags: ['newsletter'] });
  };

  if (isSuccess) return <p>Thanks for subscribing!</p>;

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <button disabled={isLoading}>
        {isLoading ? 'Subscribing...' : 'Subscribe'}
      </button>
      {error && <p>{error.message}</p>}
    </form>
  );
}

Webhook Verification

import { verifyWebhookSignature } from '@sendmailos/sdk';

app.post('/webhooks', (req, res) => {
  const isValid = verifyWebhookSignature({
    payload: JSON.stringify(req.body),
    signature: req.headers['x-sendmailos-signature'],
    timestamp: req.headers['x-sendmailos-timestamp'],
    secret: process.env.WEBHOOK_SECRET
  });

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process webhook event
  const event = req.body;
  console.log('Event:', event.type);

  res.status(200).json({ received: true });
});

Error Handling

import { SendMailOS, SendMailOSError, RateLimitError } from '@sendmailos/sdk';

try {
  await client.emails.send({ ... });
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter}s`);
  } else if (error instanceof SendMailOSError) {
    console.log(`Error: ${error.message} (${error.code})`);
  }
}

Security

  • Never expose secret keys (sk_*) in client-side code
  • Use public keys (pk_*) for React components
  • Verify webhook signatures to prevent spoofing
  • Use environment variables for API keys

License

MIT