TypeScript SDK

Official TypeScript SDK for the Lumail API - type-safe client with full autocompletion for subscribers, campaigns, emails, tags, events, and tools.

The Lumail TypeScript SDK provides a type-safe client for interacting with the Lumail API. It covers all V1 REST endpoints and V2 tools, with built-in error handling, retries, and full TypeScript autocompletion.

For a task-focused guide with separate pages for setup, resources, tools, and error handling, start with the SDK Introduction.

Installation

npm install lumail

Quick Start

import { Lumail } from "lumail";

const lumail = new Lumail({ apiKey: "lum_your_api_token_here" });

// Create a subscriber
const { subscriber } = await lumail.subscribers.create({
  email: "[email protected]",
  name: "John Doe",
  tags: ["newsletter"],
});

// Send a campaign
await lumail.campaigns.send("campaign_id");

Configuration

const lumail = new Lumail({
  apiKey: "lum_...", // Required - your API token
  baseUrl: "https://lumail.io/api", // Optional - defaults to production
});

Get your API token from Settings > API Tokens in your Lumail dashboard, or follow the API Tokens guide.

Subscribers

Create or update a subscriber

const { subscriber } = await lumail.subscribers.create({
  email: "[email protected]",
  name: "John Doe",
  phone: "+1234567890",
  tags: ["vip", "newsletter"],
  fields: { company: "Acme", role: "CEO" },
  resubscribe: true, // Re-subscribe if previously unsubscribed
  triggerWorkflows: true, // Trigger matching workflows
  skipDoubleOptIn: true, // Trusted backends only; skip org double opt-in
  country: "US", // 2-letter country code
});

If the email already exists, it updates the existing subscriber.

Get a subscriber

const { subscriber } = await lumail.subscribers.get("[email protected]");
// or by ID
const { subscriber } = await lumail.subscribers.get("sub_abc123");

Update a subscriber

const { subscriber } = await lumail.subscribers.update("[email protected]", {
  name: "Jane Doe",
  tags: ["premium"],
  replaceTags: true, // Replace all tags instead of appending
});

Unsubscribe

const { subscriber } = await lumail.subscribers.unsubscribe("[email protected]");

Manage tags

// Add tags (creates tags if they don't exist)
const { added, tags } = await lumail.subscribers.addTags("[email protected]", [
  "premium",
  "beta-tester",
]);

// Remove tags
const { removed } = await lumail.subscribers.removeTags("[email protected]", [
  "old-tag",
]);

List events

const { events, nextCursor } = await lumail.subscribers.listEvents(
  "[email protected]",
  {
    take: 50,
    order: "desc",
    eventTypes: ["EMAIL_OPENED", "EMAIL_CLICKED"],
    startDate: "2025-01-01T00:00:00Z",
  },
);

// Cursor-based pagination
if (nextCursor) {
  const next = await lumail.subscribers.listEvents("[email protected]", {
    cursor: nextCursor,
    take: 50,
  });
}

Campaigns

List campaigns

const { campaigns, total, pageCount } = await lumail.campaigns.list({
  status: "DRAFT", // "all" | "DRAFT" | "ARCHIVED" | "SCHEDULED" | "SENT"
  page: 1,
  limit: 20,
  query: "welcome", // Search by name or subject
  sortBy: "name", // "name" | "name_desc"
});

Create a campaign

const { campaign, campaignId } = await lumail.campaigns.create({
  subject: "Welcome to our newsletter!",
  name: "Welcome Campaign",
  preview: "You're in. Here's what to expect.",
  contentType: "MARKDOWN", // "MAILY" | "PLATE" | "MARKDOWN"
});

Get campaign details

const { campaign } = await lumail.campaigns.get("campaign_id");
// Includes sender info, recipient filters, and full content

Update a campaign

Only DRAFT campaigns can be updated.

await lumail.campaigns.update("campaign_id", {
  subject: "Updated Subject Line",
  preview: "New preview text",
});

Delete a campaign

Only DRAFT campaigns can be deleted.

await lumail.campaigns.delete("campaign_id");

Send or schedule

// Send immediately
await lumail.campaigns.send("campaign_id");

// Schedule for later
await lumail.campaigns.send("campaign_id", {
  scheduledAt: "2025-12-25T10:00:00Z",
  timezone: "Europe/Paris",
});

Emails (Transactional)

Send an email

const { id } = await lumail.emails.send({
  to: "[email protected]",
  from: "[email protected]",
  subject: "Order Confirmation",
  markdown: "Your order **#1234** has been confirmed.",
  reply_to: "[email protected]",
});

The from address must belong to a verified domain in your organization.

Verify an email

const result = await lumail.emails.verify({ email: "[email protected]" });

if (result.success) {
  console.log(result.warnings);
} else {
  console.log(result.code, result.error, result.suggestion);
}

Returns a discriminated union:

type VerifyEmailResponse =
  | { success: true; warnings?: string[] }
  | {
      success: false;
      error: string;
      code:
        | "invalid_format"
        | "disposable_email"
        | "spam_domain"
        | "invalid_domain"
        | "test_email"
        | "internal_error";
      suggestion?: string;
      warnings?: string[];
    };

When success is false, code identifies the reason and suggestion may contain a corrected address for common typos. Disposable providers such as passmail.net and yopmail.com return success: false with code: "disposable_email". Results are cached for 30 days.

Tags

// List all tags
const { tags } = await lumail.tags.list();

// Create a tag
const { tag } = await lumail.tags.create({ name: "premium" });

// Get tag details (by name or ID)
const { tag } = await lumail.tags.get("premium");
// tag.subscribersCount shows how many subscribers have this tag

// Rename a tag
await lumail.tags.update("premium", { name: "gold" });

Events

Track custom subscriber events:

await lumail.events.create({
  eventType: "SUBSCRIBER_PAYMENT",
  subscriber: "[email protected]", // Email or subscriber ID
  data: {
    amount: 99,
    plan: "pro",
    currency: "USD",
  },
});

Available event types: SUBSCRIBED, UNSUBSCRIBED, TAG_ADDED, TAG_REMOVED, EMAIL_OPENED, EMAIL_CLICKED, EMAIL_SENT, EMAIL_RECEIVED, WORKFLOW_STARTED, WORKFLOW_COMPLETED, WORKFLOW_CANCELED, FIELD_UPDATED, EMAIL_BOUNCED, EMAIL_COMPLAINED, WEBHOOK_EXECUTED, SUBSCRIBER_PAYMENT, SUBSCRIBER_REFUND

Tools (V2 API)

Access all 59+ Lumail tools programmatically:

// List available tools
const { tools, grouped } = await lumail.tools.list();

// Get a tool's schema
const { tool } = await lumail.tools.get("list_subscribers");

// Run a tool with typed response
const result = await lumail.tools.run<{ subscribers: unknown[] }>(
  "list_subscribers",
  { limit: 10, status: "SUBSCRIBED" },
);

See Tools API (v2) for the full list of available tools.

Error Handling

The SDK throws typed errors for different HTTP status codes:

import {
  Lumail,
  LumailAuthenticationError, // 401 - invalid API key
  LumailPaymentRequiredError, // 402 - plan limit reached
  LumailValidationError, // 400 - invalid request
  LumailNotFoundError, // 404 - resource not found
  LumailRateLimitError, // 429 - rate limited
  LumailError, // Other errors
} from "lumail";

try {
  await lumail.subscribers.get("[email protected]");
} catch (error) {
  if (error instanceof LumailNotFoundError) {
    console.log("Subscriber not found");
  } else if (error instanceof LumailRateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter}ms`);
  } else if (error instanceof LumailAuthenticationError) {
    console.log("Check your API key");
  }
}

Retry Behavior

MethodRetriesWhen
GET, PUT, DELETEUp to 3Network errors, 429 rate limits
POST, PATCHNo retriesPrevents duplicate operations

Retry delays follow exponential backoff: 1s, 2s, 4s. The SDK respects Retry-After headers from rate limit responses.