---
name: lemn
description: Use the lemn-api npm package to manage email marketing with Lemn. Covers contact lists, broadcasts, transactional emails, suppression lists, exclusion lists, webhooks, and exports.
---

# Lemn Skill

Use this skill when the user wants to send emails, manage contact lists, run broadcast campaigns, or configure webhooks using the Lemn email marketing platform via the `lemn-api` npm package.

---

## Setup

```bash
npm install lemn-api
```

```javascript
const LemnAPI = require('lemn-api');
const lemn = new LemnAPI('your_api_key');
```

### How Authentication Works (Important)

The package handles auth internally. It sends your API key as the header `X-Auth-APIKey` on every request. You never set headers manually — just pass the key to the constructor and use the methods.

The base URL used internally is `https://app.xn--lemn-sqa.com/api`. You do not need to use this directly, but it is good to know for debugging.

---

## Module Overview

| Namespace | What it does |
|---|---|
| `lemn.lists` | Contact lists — create, manage contacts, tags, domain stats |
| `lemn.broadcasts` | Email campaigns — create, send, test, stats |
| `lemn.transactional` | Send one-off transactional emails |
| `lemn.supplists` | Suppression lists — block specific contacts |
| `lemn.exclusion` | Exclusion lists — global send exclusions |
| `lemn.webhooks` | Create and manage event webhooks |
| `lemn.exports` | Retrieve exported files |

---

## Lists (`lemn.lists`)

### Create a list
```javascript
const list = await lemn.lists.create('Newsletter Subscribers');
// list.id is used in all subsequent list operations
```

### Get all lists
```javascript
const lists = await lemn.lists.getAll();
```

### Get a specific list
```javascript
const list = await lemn.lists.get(listId);
```

### Update a list
```javascript
await lemn.lists.update(listId, { name: 'New Name' });
```

### Delete a list
```javascript
await lemn.lists.delete(listId);
```

### Export a list
```javascript
await lemn.lists.export(listId);
```

### Add a single contact to a list
```javascript
const contact = {
  email: 'john@example.com',
  first_name: 'John',
  last_name: 'Doe'
};
await lemn.lists.addSingleContact(listId, contact);
```

### Add contacts via CSV
```javascript
// csvData must be a CSV string with headers
const csvData = 'email,first_name,last_namenjohn@example.com,John,Doe';
await lemn.lists.addData(listId, csvData);
// Internally sends Content-Type: text/csv
```

### Delete contacts from a list
```javascript
// Pass the emails array directly (not wrapped in an object)
await lemn.lists.deleteContacts(listId, ['john@example.com', 'jane@example.com']);
```

### Add emails to unsubscribe list
```javascript
// Pass emails array directly — sent as text/plain internally
await lemn.lists.addUnsubscribes(listId, ['john@example.com', 'jane@example.com']);
```

### Get domain statistics for a list
```javascript
const stats = await lemn.lists.getDomainStats(listId);
```

### Delete specific domains from a list
```javascript
// Pass domains array directly (not wrapped in an object)
await lemn.lists.deleteDomains(listId, ['gmail.com', 'yahoo.com']);
```

### Get contact data by email
```javascript
const contact = await lemn.lists.getContactData('john@example.com');
```

### Delete contact data by email
```javascript
await lemn.lists.deleteContactData('john@example.com');
```

### Get all tags
```javascript
const tags = await lemn.lists.getAllTags();
```

---

## Broadcasts (`lemn.broadcasts`)

### Get available postal routes (needed before creating broadcasts)
```javascript
const routes = await lemn.broadcasts.getUserRoutes();
// Use a route id from this response when creating broadcasts
```

### Create a broadcast
```javascript
const broadcast = await lemn.broadcasts.create({
  name: 'My Campaign',
  subject: 'Hello from Lemn',
  fromname: 'Your Company',
  fromemail: 'hello@yourcompany.com',
  body: '<h1>Hello!</h1><p>Your email content here.</p>',
  listid: listId,       // contact list to send to
  route: routeId        // from getUserRoutes()
});
// broadcast.id is used in all subsequent broadcast operations
```

### Get all broadcasts
```javascript
// No filters
const broadcasts = await lemn.broadcasts.getAll();

// With optional filters
const filtered = await lemn.broadcasts.getAll({
  older: 'cursor-value',   // for pagination
  search: 'keyword'
});
```

### Get a specific broadcast
```javascript
const broadcast = await lemn.broadcasts.get(broadcastId);
```

### Update a draft broadcast
```javascript
await lemn.broadcasts.updateDraft(broadcastId, {
  subject: 'Updated Subject',
  body: '<p>Updated content</p>'
});
// Only works on drafts — cannot update already-sent broadcasts this way
```

### Send a test email
```javascript
await lemn.broadcasts.sendTest(broadcastId, {
  to: 'test@example.com'
});
```

### Start sending a broadcast
```javascript
await lemn.broadcasts.start(broadcastId);
// Immediately begins sending to the list
```

### Cancel a broadcast
```javascript
await lemn.broadcasts.cancel(broadcastId);
```

### Duplicate a broadcast
```javascript
const copy = await lemn.broadcasts.duplicate(broadcastId);
```

### Update a sent broadcast
```javascript
await lemn.broadcasts.updateSent(broadcastId, { name: 'New Name' });
```

### Export broadcast data
```javascript
await lemn.broadcasts.export(broadcastId);
```

### Get domain statistics
```javascript
const stats = await lemn.broadcasts.getDomainStats(broadcastId);
```

### Get client statistics (devices, browsers, locations)
```javascript
const clientStats = await lemn.broadcasts.getClientStats(broadcastId);
```

### Get bounce messages
```javascript
const bounces = await lemn.broadcasts.getBounceMessages(broadcastId, 'gmail.com', 'hard');
// type can be 'hard' or 'soft'
```

### Upload a file (for use in broadcast content)
```javascript
const fs = require('fs');
const fileStream = fs.createReadStream('./image.jpg');
const result = await lemn.broadcasts.uploadFile(fileStream);
// Pass a ReadStream, not a file path string
```

---

## Transactional Emails (`lemn.transactional`)

Use this for one-off emails triggered by user actions (welcome emails, receipts, password resets, etc.).

### Send a transactional email
```javascript
await lemn.transactional.send({
  fromname: 'Your Company',          // required
  fromemail: 'noreply@company.com',  // required
  to: 'user@example.com',            // required
  toname: 'John Doe',                // optional
  subject: 'Welcome!',               // required
  body: '<h1>Welcome!</h1>',         // required, HTML format
  replyto: 'support@company.com',    // optional
  returnpath: 'bounce@company.com',  // optional
  tag: 'welcome-email',              // optional, for reporting
  route: routeId,                    // optional, specific postal route
  template: templateId,              // optional, use a saved template instead of body
  variables: {                       // optional, Jinja2 template variables
    username: 'johndoe',
    plan: 'pro'
  }
});
```

Note: if `template` is provided, it overrides `body`. Variables are applied using Jinja2 syntax in the template.

---

## Suppression Lists (`lemn.supplists`)

Suppression lists prevent emails from being sent to specific contacts.

### Create a suppression list
```javascript
const suplist = await lemn.supplists.create('Unsubscribed Users');
```

### Get all suppression lists
```javascript
const lists = await lemn.supplists.getAll();
```

### Get a specific suppression list
```javascript
const list = await lemn.supplists.get(listId);
```

### Update a suppression list
```javascript
await lemn.supplists.update(listId, 'New List Name');
// Second arg is just the new name string, not an object
```

### Delete a suppression list
```javascript
await lemn.supplists.delete(listId);
```

### Add emails to a suppression list
```javascript
// Pass newline-separated email addresses as a string
// Sent internally as Content-Type: text/plain
await lemn.supplists.addData(listId, 'john@example.comnjane@example.com');
```

---

## Exclusion Lists (`lemn.exclusion`)

Global exclusions that apply across all sends.

### Get all exclusion lists
```javascript
const lists = await lemn.exclusion.getAll();
```

### Add data to an exclusion list
```javascript
// data is wrapped internally as { data: yourData }
await lemn.exclusion.addToList(listId, 'john@example.comnjane@example.com');
```

---

## Webhooks (`lemn.webhooks`)

### Create a webhook
```javascript
await lemn.webhooks.createWebhook({
  url: 'https://yoursite.com/webhook',
  events: ['delivered', 'bounced', 'opened', 'clicked', 'unsubscribed']
});
```

### Get all webhooks
```javascript
const webhooks = await lemn.webhooks.getAllWebhooks();
```

### Update a webhook
```javascript
await lemn.webhooks.updateWebhook(webhookId, {
  url: 'https://yoursite.com/new-webhook-url'
});
```

### Delete a webhook
```javascript
await lemn.webhooks.deleteWebhook(webhookId);
```

---

## Exports (`lemn.exports`)

### Get all exported files
```javascript
const exports = await lemn.exports.getAll();
```

---

## Error Handling

All methods return Promises and throw on non-2xx responses. The thrown error is the parsed JSON error body from the API.

```javascript
try {
  await lemn.lists.create('My List');
} catch (error) {
  console.error(error); // parsed API error object
}
```

---

## Common Workflows

### Create a list and add contacts, then send a broadcast

```javascript
const lemn = new LemnAPI('your_api_key');

// 1. Get a postal route first
const routes = await lemn.broadcasts.getUserRoutes();
const routeId = routes[0].id; // pick the first available route

// 2. Create a contact list
const list = await lemn.lists.create('My Campaign List');

// 3. Add contacts
await lemn.lists.addSingleContact(list.id, {
  email: 'john@example.com',
  first_name: 'John',
  last_name: 'Doe'
});

// 4. Create a broadcast draft
const broadcast = await lemn.broadcasts.create({
  name: 'My First Campaign',
  subject: 'Hello from our team',
  fromname: 'My Company',
  fromemail: 'hello@mycompany.com',
  body: '<h1>Hello!</h1><p>Thanks for signing up.</p>',
  listid: list.id,
  route: routeId
});

// 5. Send a test first
await lemn.broadcasts.sendTest(broadcast.id, { to: 'me@mycompany.com' });

// 6. Start the broadcast when ready
await lemn.broadcasts.start(broadcast.id);
```

### Send a transactional welcome email

```javascript
const lemn = new LemnAPI('your_api_key');

await lemn.transactional.send({
  fromname: 'My App',
  fromemail: 'noreply@myapp.com',
  to: newUser.email,
  toname: newUser.name,
  subject: 'Welcome to My App!',
  body: `<h1>Hi ${newUser.name}!</h1><p>Your account is ready.</p>`,
  tag: 'welcome'
});
```

---

## Gotchas

- **`broadcasts.uploadFile` takes a ReadStream**, not a file path. Use `fs.createReadStream('./file.jpg')`.
- **`supplists.addData`** takes a newline-separated string of emails, not an array.
- **`supplists.update`** second argument is just a name string, not an object.
- **`lists.deleteContacts` and `lists.deleteDomains`** take the array directly as the second argument, not wrapped in an object.
- **`lists.addUnsubscribes`** takes an array but sends it as `text/plain` internally — do not pre-format it.
- **`exclusion.addToList`** wraps your data internally as `{ data: yourData }` — do not wrap it yourself.
- **Always call `getUserRoutes()` before creating broadcasts** — you need a valid route ID.
- Node.js 16 or higher is required.