lemn
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.