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
npm install lemn-api
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
const list = await lemn.lists.create('Newsletter Subscribers');
// list.id is used in all subsequent list operations
Get all lists
const lists = await lemn.lists.getAll();
Get a specific list
const list = await lemn.lists.get(listId);
Update a list
await lemn.lists.update(listId, { name: 'New Name' });
Delete a list
await lemn.lists.delete(listId);
Export a list
await lemn.lists.export(listId);
Add a single contact to a list
const contact = {
email: 'john@example.com',
first_name: 'John',
last_name: 'Doe'
};
await lemn.lists.addSingleContact(listId, contact);
Add contacts via CSV
// csvData must be a CSV string with headers
const csvData = 'email,first_name,last_name\njohn@example.com,John,Doe';
await lemn.lists.addData(listId, csvData);
// Internally sends Content-Type: text/csv
Delete contacts from a list
// 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
// 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
const stats = await lemn.lists.getDomainStats(listId);
Delete specific domains from a list
// Pass domains array directly (not wrapped in an object)
await lemn.lists.deleteDomains(listId, ['gmail.com', 'yahoo.com']);
Get contact data by email
const contact = await lemn.lists.getContactData('john@example.com');
Delete contact data by email
await lemn.lists.deleteContactData('john@example.com');
Get all tags
const tags = await lemn.lists.getAllTags();
Broadcasts (lemn.broadcasts)
Get available postal routes (needed before creating broadcasts)
const routes = await lemn.broadcasts.getUserRoutes();
// Use a route id from this response when creating broadcasts
Create a broadcast
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
// 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
const broadcast = await lemn.broadcasts.get(broadcastId);
Update a draft broadcast
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
await lemn.broadcasts.sendTest(broadcastId, {
to: 'test@example.com'
});
Start sending a broadcast
await lemn.broadcasts.start(broadcastId);
// Immediately begins sending to the list
Cancel a broadcast
await lemn.broadcasts.cancel(broadcastId);
Duplicate a broadcast
const copy = await lemn.broadcasts.duplicate(broadcastId);
Update a sent broadcast
await lemn.broadcasts.updateSent(broadcastId, { name: 'New Name' });
Export broadcast data
await lemn.broadcasts.export(broadcastId);
Get domain statistics
const stats = await lemn.broadcasts.getDomainStats(broadcastId);
Get client statistics (devices, browsers, locations)
const clientStats = await lemn.broadcasts.getClientStats(broadcastId);
Get bounce messages
const bounces = await lemn.broadcasts.getBounceMessages(broadcastId, 'gmail.com', 'hard');
// type can be 'hard' or 'soft'
Upload a file (for use in broadcast content)
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
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
const suplist = await lemn.supplists.create('Unsubscribed Users');
Get all suppression lists
const lists = await lemn.supplists.getAll();
Get a specific suppression list
const list = await lemn.supplists.get(listId);
Update a suppression list
await lemn.supplists.update(listId, 'New List Name');
// Second arg is just the new name string, not an object
Delete a suppression list
await lemn.supplists.delete(listId);
Add emails to a suppression list
// Pass newline-separated email addresses as a string
// Sent internally as Content-Type: text/plain
await lemn.supplists.addData(listId, 'john@example.com\njane@example.com');
Exclusion Lists (lemn.exclusion)
Global exclusions that apply across all sends.
Get all exclusion lists
const lists = await lemn.exclusion.getAll();
Add data to an exclusion list
// data is wrapped internally as { data: yourData }
await lemn.exclusion.addToList(listId, 'john@example.com\njane@example.com');
Webhooks (lemn.webhooks)
Create a webhook
await lemn.webhooks.createWebhook({
url: 'https://yoursite.com/webhook',
events: ['delivered', 'bounced', 'opened', 'clicked', 'unsubscribed']
});
Get all webhooks
const webhooks = await lemn.webhooks.getAllWebhooks();
Update a webhook
await lemn.webhooks.updateWebhook(webhookId, {
url: 'https://yoursite.com/new-webhook-url'
});
Delete a webhook
await lemn.webhooks.deleteWebhook(webhookId);
Exports (lemn.exports)
Get all exported files
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.
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
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
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.uploadFiletakes a ReadStream, not a file path. Usefs.createReadStream('./file.jpg').supplists.addDatatakes a newline-separated string of emails, not an array.supplists.updatesecond argument is just a name string, not an object.lists.deleteContactsandlists.deleteDomainstake the array directly as the second argument, not wrapped in an object.lists.addUnsubscribestakes an array but sends it astext/plaininternally — do not pre-format it.exclusion.addToListwraps 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.
Scan to join WeChat group