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](/docs/sdk/introduction).

## Installation

```bash
npm install lumail
```

## Quick Start

```typescript
import { Lumail } from "lumail";

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

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

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

## Configuration

```typescript
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](/docs/api-reference/api-tokens).

## Subscribers

### Create or update a subscriber

```typescript
const { subscriber } = await lumail.subscribers.create({
  email: "user@example.com",
  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

```typescript
const { subscriber } = await lumail.subscribers.get("user@example.com");
// or by ID
const { subscriber } = await lumail.subscribers.get("sub_abc123");
```

### Update a subscriber

```typescript
const { subscriber } = await lumail.subscribers.update("user@example.com", {
  name: "Jane Doe",
  tags: ["premium"],
  replaceTags: true, // Replace all tags instead of appending
});
```

### Unsubscribe

```typescript
const { subscriber } = await lumail.subscribers.unsubscribe("user@example.com");
```

### Manage tags

```typescript
// Add tags (creates tags if they don't exist)
const { added, tags } = await lumail.subscribers.addTags("user@example.com", [
  "premium",
  "beta-tester",
]);

// Remove tags
const { removed } = await lumail.subscribers.removeTags("user@example.com", [
  "old-tag",
]);
```

### List events

```typescript
const { events, nextCursor } = await lumail.subscribers.listEvents(
  "user@example.com",
  {
    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("user@example.com", {
    cursor: nextCursor,
    take: 50,
  });
}
```

## Campaigns

### List campaigns

```typescript
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

```typescript
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

```typescript
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.

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

### Delete a campaign

Only DRAFT campaigns can be deleted.

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

### Send or schedule

```typescript
// 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

```typescript
const { id } = await lumail.emails.send({
  to: "user@example.com",
  from: "noreply@yourdomain.com",
  subject: "Order Confirmation",
  markdown: "Your order **#1234** has been confirmed.",
  reply_to: "support@yourdomain.com",
});
```

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

### Verify an email

```typescript
const result = await lumail.emails.verify({ email: "test@example.com" });

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

Returns a discriminated union:

```typescript
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

```typescript
// 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:

```typescript
await lumail.events.create({
  eventType: "SUBSCRIBER_PAYMENT",
  subscriber: "user@example.com", // 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:

```typescript
// 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)](/docs/api-reference/v2-tools) for the full list of available tools.

## Error Handling

The SDK throws typed errors for different HTTP status codes:

```typescript
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("unknown@example.com");
} 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

| Method           | Retries    | When                            |
| ---------------- | ---------- | ------------------------------- |
| GET, PUT, DELETE | Up to 3    | Network errors, 429 rate limits |
| POST, PATCH      | No retries | Prevents duplicate operations   |

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

## Related Documentation

- [API Tokens](/docs/api-reference/api-tokens) - Generate your API key
- [SDK Introduction](/docs/sdk/introduction) - Task-focused SDK guide
- [CLI](/docs/api-reference/cli) - Command-line interface
- [MCP Server](/docs/api-reference/mcp) - AI agent integration
- [API Limits](/docs/api-reference/api-limits) - Rate limits and quotas
