JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 34
  • Score
    100M100P100Q53158F
  • License MIT

Official JavaScript/TypeScript SDK for SendMailOS email API

Package Exports

  • @sendmailos/sdk
  • @sendmailos/sdk/react

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: 'user@example.com',
  fromName: 'Your Company',
  fromEmail: 'hello@yourcompany.com',
  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: 'user@example.com',
  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: 'user@example.com',
  subject: 'Order Confirmation',
  html: '<h1>Thanks!</h1>',
  fromEmail: 'orders@company.com',
  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: 'user@example.com',
  subject: 'Hello!',
  html: '<h1>Welcome!</h1>',
  fromName: 'Acme',
  fromEmail: 'hello@acme.com',
});

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

// Send with template
await client.emails.sendTemplate({
  to: 'user@example.com',
  templateId: 'tmpl_welcome',
  variables: { firstName: 'John' }
});

// Send with external ID and metadata (for tracking in your system)
await client.emails.send({
  to: 'customer@example.com',
  subject: 'Order Confirmation #12345',
  html: '<h1>Order Confirmed!</h1>',
  fromName: 'Acme Store',
  fromEmail: 'orders@acme.com',
  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: 'user@example.com',
  subject: 'Invoice Attached',
  html: '<p>Please find your invoice attached.</p>',
  fromEmail: 'billing@company.com',
  fromName: 'Billing',
  attachments: [
    {
      filename: 'invoice.pdf',
      content: 'JVBERi0xLjQK...', // Base64 encoded
      contentType: 'application/pdf'
    }
  ]
});

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

// Inline images (using cid)
await client.emails.send({
  to: 'user@example.com',
  subject: 'Check out our logo',
  html: '<p>Our logo: <img src="cid:logo-image"></p>',
  fromEmail: 'hello@company.com',
  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: 'user@example.com',
  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: 'customer@example.com',
  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: 'news@company.com',
  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: 'promo@pizzapalace.com',  // ← 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: 'customer@example.com',
  fromEmail: 'hello@yourcompany.com',
  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: 'customer@example.com',
  fromEmail: 'orders@pizzapalace.com',  // ← 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: 'customer@example.com',
  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: 'promo@pizzapalace.com',  // ← 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.

// 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: 'customer@example.com',
  fromEmail: 'hello@pizzapalace.com',
  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, 'owner@pizzapalace.com');

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: 'customer@example.com',
  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: 'customer@example.com',
  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: 'newcustomer@gmail.com',
  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('user@example.com', {
  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', 'user@example.com');
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