lumail 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,257 @@
1
+ # Lumail
2
+
3
+ TypeScript SDK and CLI for the [Lumail](https://lumail.io) email marketing platform.
4
+
5
+ Manage subscribers, send campaigns, trigger transactional emails, and more — all from code.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install lumail
11
+ # or
12
+ pnpm add lumail
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```typescript
18
+ import { Lumail } from "lumail";
19
+
20
+ const lumail = new Lumail({
21
+ apiKey: "lum_your_api_key", // Get yours at lumail.io/settings
22
+ });
23
+
24
+ // Add a subscriber
25
+ const { subscriber } = await lumail.subscribers.create({
26
+ email: "john@example.com",
27
+ name: "John Doe",
28
+ tags: ["newsletter", "welcome"],
29
+ });
30
+
31
+ // Send a transactional email
32
+ await lumail.emails.send({
33
+ to: "john@example.com",
34
+ from: "hello@yourdomain.com",
35
+ subject: "Welcome!",
36
+ content: "Thanks for signing up.",
37
+ contentType: "MARKDOWN",
38
+ });
39
+ ```
40
+
41
+ ## Authentication
42
+
43
+ Get your API key from your [Lumail settings page](https://lumail.io/settings), then pass it to the constructor:
44
+
45
+ ```typescript
46
+ const lumail = new Lumail({ apiKey: "lum_..." });
47
+ ```
48
+
49
+ ## Resources
50
+
51
+ ### Subscribers
52
+
53
+ ```typescript
54
+ // List subscribers (with cursor pagination)
55
+ const { subscribers, nextCursor } = await lumail.subscribers.list({
56
+ tag: "newsletter",
57
+ status: "SUBSCRIBED",
58
+ limit: 50,
59
+ });
60
+
61
+ // Get a subscriber by ID or email
62
+ const { subscriber } = await lumail.subscribers.get("john@example.com");
63
+
64
+ // Create a subscriber
65
+ const { subscriber } = await lumail.subscribers.create({
66
+ email: "john@example.com",
67
+ name: "John Doe",
68
+ phone: "+1234567890",
69
+ tags: ["newsletter"],
70
+ fields: { company: "Acme" },
71
+ triggerWorkflows: true,
72
+ });
73
+
74
+ // Update a subscriber
75
+ await lumail.subscribers.update("john@example.com", {
76
+ name: "John D.",
77
+ fields: { company: "New Corp" },
78
+ });
79
+
80
+ // Delete a subscriber
81
+ await lumail.subscribers.delete("john@example.com");
82
+
83
+ // Unsubscribe
84
+ await lumail.subscribers.unsubscribe("john@example.com");
85
+
86
+ // Manage tags
87
+ await lumail.subscribers.addTags("john@example.com", ["vip", "early-adopter"]);
88
+ await lumail.subscribers.removeTags("john@example.com", ["early-adopter"]);
89
+
90
+ // List subscriber events
91
+ const { events } = await lumail.subscribers.listEvents("john@example.com", {
92
+ eventTypes: ["EMAIL_OPENED", "EMAIL_CLICKED"],
93
+ order: "desc",
94
+ take: 20,
95
+ });
96
+ ```
97
+
98
+ ### Campaigns
99
+
100
+ ```typescript
101
+ // List campaigns
102
+ const { campaigns } = await lumail.campaigns.list({
103
+ status: "DRAFT",
104
+ limit: 10,
105
+ });
106
+
107
+ // Create a campaign
108
+ const { campaign } = await lumail.campaigns.create({
109
+ subject: "March Newsletter",
110
+ name: "march-2026",
111
+ preview: "What's new this month",
112
+ contentType: "MARKDOWN",
113
+ content: "# Hello\n\nHere's what's new...",
114
+ });
115
+
116
+ // Get a campaign
117
+ const { campaign } = await lumail.campaigns.get("campaign_id");
118
+
119
+ // Update a campaign
120
+ await lumail.campaigns.update("campaign_id", {
121
+ subject: "Updated Subject",
122
+ });
123
+
124
+ // Send immediately
125
+ await lumail.campaigns.send("campaign_id");
126
+
127
+ // Schedule for later
128
+ await lumail.campaigns.send("campaign_id", {
129
+ scheduledAt: "2026-04-10T09:00:00Z",
130
+ timezone: "Europe/Paris",
131
+ });
132
+
133
+ // Delete a campaign
134
+ await lumail.campaigns.delete("campaign_id");
135
+ ```
136
+
137
+ ### Emails
138
+
139
+ ```typescript
140
+ // Send a transactional email
141
+ await lumail.emails.send({
142
+ to: "user@example.com",
143
+ from: "noreply@yourdomain.com",
144
+ subject: "Your invoice",
145
+ content: "<h1>Invoice #123</h1><p>Amount: $99</p>",
146
+ contentType: "HTML",
147
+ replyTo: "support@yourdomain.com",
148
+ transactional: true,
149
+ tracking: { open: true, links: true },
150
+ });
151
+
152
+ // Verify an email address
153
+ const result = await lumail.emails.verify({
154
+ email: "check@example.com",
155
+ });
156
+
157
+ if (!result.success) {
158
+ console.log(result.code, result.error, result.suggestion);
159
+ }
160
+ ```
161
+
162
+ ### Tags
163
+
164
+ ```typescript
165
+ // List all tags
166
+ const { tags } = await lumail.tags.list();
167
+
168
+ // Create a tag
169
+ const { tag } = await lumail.tags.create({ name: "vip" });
170
+
171
+ // Get a tag (includes subscriber count)
172
+ const { tag } = await lumail.tags.get("vip");
173
+
174
+ // Update a tag
175
+ await lumail.tags.update("vip", { name: "VIP Customers" });
176
+ ```
177
+
178
+ ### Events
179
+
180
+ ```typescript
181
+ // Create a custom event
182
+ await lumail.events.create({
183
+ eventType: "SUBSCRIBER_PAYMENT",
184
+ subscriber: "john@example.com",
185
+ data: { amount: 99, currency: "USD", plan: "pro" },
186
+ });
187
+ ```
188
+
189
+ ### Tools (v2)
190
+
191
+ AI-powered tools for email marketing.
192
+
193
+ ```typescript
194
+ // List available tools
195
+ const { tools } = await lumail.tools.list();
196
+
197
+ // Get tool details
198
+ const { tool } = await lumail.tools.get("generate-subject-line");
199
+
200
+ // Run a tool
201
+ const result = await lumail.tools.run("generate-subject-line", {
202
+ topic: "Spring sale announcement",
203
+ tone: "excited",
204
+ });
205
+ ```
206
+
207
+ ## Error Handling
208
+
209
+ The SDK throws typed errors you can catch individually:
210
+
211
+ ```typescript
212
+ import {
213
+ Lumail,
214
+ LumailAuthenticationError,
215
+ LumailValidationError,
216
+ LumailNotFoundError,
217
+ LumailRateLimitError,
218
+ LumailPaymentRequiredError,
219
+ } from "lumail";
220
+
221
+ try {
222
+ await lumail.subscribers.get("unknown@example.com");
223
+ } catch (error) {
224
+ if (error instanceof LumailNotFoundError) {
225
+ console.log("Subscriber not found");
226
+ } else if (error instanceof LumailAuthenticationError) {
227
+ console.log("Invalid API key");
228
+ } else if (error instanceof LumailRateLimitError) {
229
+ console.log(`Rate limited, retry after ${error.retryAfter}ms`);
230
+ } else if (error instanceof LumailValidationError) {
231
+ console.log(`Bad request: ${error.message}`);
232
+ } else if (error instanceof LumailPaymentRequiredError) {
233
+ console.log("Plan limit reached — upgrade at lumail.io/billing");
234
+ }
235
+ }
236
+ ```
237
+
238
+ All errors extend `LumailError` which includes `status` and `code` properties.
239
+
240
+ The client automatically retries idempotent requests (GET, PUT, DELETE) on rate-limit (429) and network errors with exponential backoff.
241
+
242
+ ## CLI
243
+
244
+ The package also ships a CLI:
245
+
246
+ ```bash
247
+ npx lumail --help
248
+ ```
249
+
250
+ ## Requirements
251
+
252
+ - Node.js 18+ (uses native `fetch`)
253
+ - An API key from [lumail.io](https://lumail.io)
254
+
255
+ ## License
256
+
257
+ MIT
package/dist/cli.js CHANGED
@@ -5,15 +5,29 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
@@ -991,8 +1005,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
991
1005
  this._exitCallback = (err) => {
992
1006
  if (err.code !== "commander.executeSubCommandAsync") {
993
1007
  throw err;
994
- } else {
995
- }
1008
+ } else {}
996
1009
  };
997
1010
  }
998
1011
  return this;
@@ -2260,6 +2273,19 @@ class LumailPaymentRequiredError extends LumailError {
2260
2273
  }
2261
2274
  }
2262
2275
 
2276
+ class LumailConfirmationRequiredError extends LumailError {
2277
+ confirmationCode;
2278
+ action;
2279
+ instruction;
2280
+ constructor(message, confirmationCode, action, instruction) {
2281
+ super(message, 403, "CONFIRMATION_REQUIRED");
2282
+ this.name = "LumailConfirmationRequiredError";
2283
+ this.confirmationCode = confirmationCode;
2284
+ this.action = action;
2285
+ this.instruction = instruction;
2286
+ }
2287
+ }
2288
+
2263
2289
  // ../../src/lib/lumail-sdk/client.ts
2264
2290
  var DEFAULT_BASE_URL = "https://lumail.io/api";
2265
2291
  var DEFAULT_TIMEOUT = 30000;
@@ -2295,11 +2321,7 @@ class LumailClient {
2295
2321
  signal
2296
2322
  });
2297
2323
  if (response.ok) {
2298
- const data = await response.json();
2299
- if (data.success === false) {
2300
- throw new LumailError(data.message ?? "Request failed", response.status, data.code);
2301
- }
2302
- return data;
2324
+ return await response.json();
2303
2325
  }
2304
2326
  if (response.status === 429 && isIdempotent && attempt < maxRetries) {
2305
2327
  await sleep(parseRetryAfter(response, RETRY_DELAYS[attempt]));
@@ -2336,8 +2358,7 @@ class LumailClient {
2336
2358
  let body = {};
2337
2359
  try {
2338
2360
  body = await response.json();
2339
- } catch {
2340
- }
2361
+ } catch {}
2341
2362
  const message = body.message ?? body.error ?? response.statusText;
2342
2363
  const code = body.code;
2343
2364
  switch (response.status) {
@@ -2345,6 +2366,12 @@ class LumailClient {
2345
2366
  return new LumailAuthenticationError(message);
2346
2367
  case 402:
2347
2368
  return new LumailPaymentRequiredError(message);
2369
+ case 403: {
2370
+ if (body.error === "CONFIRMATION_REQUIRED") {
2371
+ return new LumailConfirmationRequiredError(message, body.confirmationCode, body.action, body.instruction);
2372
+ }
2373
+ return new LumailValidationError(message);
2374
+ }
2348
2375
  case 404:
2349
2376
  return new LumailNotFoundError(message);
2350
2377
  case 429: {
@@ -2444,6 +2471,11 @@ class Subscribers {
2444
2471
  constructor(client) {
2445
2472
  this.client = client;
2446
2473
  }
2474
+ async list(params) {
2475
+ return this.client.request("GET", "/v1/subscribers", {
2476
+ params
2477
+ });
2478
+ }
2447
2479
  async create(params) {
2448
2480
  return this.client.request("POST", "/v1/subscribers", {
2449
2481
  body: params
@@ -2511,8 +2543,21 @@ class Tools {
2511
2543
  async get(toolName) {
2512
2544
  return this.client.request("GET", `/v2/tools/${encodeURIComponent(toolName)}`);
2513
2545
  }
2514
- async run(toolName, params) {
2515
- return this.client.request("POST", `/v2/tools/${encodeURIComponent(toolName)}`, { body: params ?? {} });
2546
+ async run(toolName, params, options) {
2547
+ const { autoConfirm = true } = options ?? {};
2548
+ const endpoint = `/v2/tools/${encodeURIComponent(toolName)}`;
2549
+ try {
2550
+ return await this.client.request("POST", endpoint, {
2551
+ body: params ?? {}
2552
+ });
2553
+ } catch (error) {
2554
+ if (autoConfirm && error instanceof LumailConfirmationRequiredError) {
2555
+ return this.client.request("POST", endpoint, {
2556
+ body: { ...params, confirmationCode: error.confirmationCode }
2557
+ });
2558
+ }
2559
+ throw error;
2560
+ }
2516
2561
  }
2517
2562
  }
2518
2563
 
@@ -2664,7 +2709,7 @@ function fmtCell(v) {
2664
2709
  return s;
2665
2710
  }
2666
2711
  function stripAnsi(s) {
2667
- return s.replace(/\x1b\[[0-9;]*m/g, "");
2712
+ return s.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), "");
2668
2713
  }
2669
2714
  function printTable(rows) {
2670
2715
  const cols = Object.keys(rows[0] ?? {});
@@ -2753,6 +2798,26 @@ API key is valid.`));
2753
2798
 
2754
2799
  // ../../src/cli/commands/subscribers.ts
2755
2800
  var subscribersCommand = new Command("subscribers").description("Manage subscribers");
2801
+ subscribersCommand.command("list").description("List subscribers with optional filters").option("--tag <tag>", "Filter by tag name").option("--status <status>", "Filter by status (all, SUBSCRIBED, UNSUBSCRIBED, etc.)", "all").option("--limit <n>", "Max number of subscribers to return", "50").option("--cursor <id>", "Cursor for pagination (subscriber ID)").option("--query <q>", "Search by name or email").action(async (opts) => {
2802
+ try {
2803
+ const limit = Number(opts.limit);
2804
+ if (!Number.isFinite(limit) || limit < 1) {
2805
+ console.error("Error: --limit must be a positive integer");
2806
+ process.exit(1);
2807
+ }
2808
+ const lumail = getLumail();
2809
+ const result = await lumail.subscribers.list({
2810
+ tag: opts.tag,
2811
+ status: opts.status,
2812
+ limit,
2813
+ cursor: opts.cursor,
2814
+ query: opts.query
2815
+ });
2816
+ output(result);
2817
+ } catch (error) {
2818
+ handleError(error);
2819
+ }
2820
+ });
2756
2821
  subscribersCommand.command("create").description("Create or update a subscriber").requiredOption("--email <email>", "Email address").option("--name <name>", "Subscriber name").option("--tags <tags...>", "Tags to add").action(async (opts) => {
2757
2822
  try {
2758
2823
  const lumail = getLumail();
@@ -3041,7 +3106,7 @@ import { homedir as homedir2 } from "os";
3041
3106
  import { join as join2 } from "path";
3042
3107
  var SKILL_URL = "https://raw.githubusercontent.com/Melvynx/lumail-opensource/main/skills/lumail/SKILL.md";
3043
3108
  var SKILLS_DIR = join2(homedir2(), ".claude", "skills", "lumail");
3044
- function prompt(question) {
3109
+ async function prompt(question) {
3045
3110
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3046
3111
  return new Promise((resolve) => {
3047
3112
  rl.question(question, (answer) => {
@@ -3097,7 +3162,7 @@ Installing Lumail skill...`));
3097
3162
  console.log(import_picocolors3.default.green("Skill installed to ~/.claude/skills/lumail/"));
3098
3163
  } catch {
3099
3164
  console.log(import_picocolors3.default.yellow("Could not download skill from GitHub."));
3100
- console.log(import_picocolors3.default.dim("Manual install: curl -o ~/.claude/skills/lumail/SKILL.md --create-dirs " + SKILL_URL));
3165
+ console.log(import_picocolors3.default.dim(`Manual install: curl -o ~/.claude/skills/lumail/SKILL.md --create-dirs ${SKILL_URL}`));
3101
3166
  }
3102
3167
  setToken(token);
3103
3168
  console.log(import_picocolors3.default.green("API key saved."));
@@ -0,0 +1,9 @@
1
+ import type { RequestOptions } from "./types";
2
+ export declare class LumailClient {
3
+ private readonly apiKey;
4
+ private readonly baseUrl;
5
+ constructor(apiKey: string, baseUrl?: string);
6
+ request<T>(method: string, path: string, options?: RequestOptions): Promise<T>;
7
+ private buildUrl;
8
+ private handleErrorResponse;
9
+ }
@@ -0,0 +1,27 @@
1
+ export declare class LumailError extends Error {
2
+ readonly status: number;
3
+ readonly code: string | undefined;
4
+ constructor(message: string, status: number, code?: string);
5
+ }
6
+ export declare class LumailAuthenticationError extends LumailError {
7
+ constructor(message?: string);
8
+ }
9
+ export declare class LumailRateLimitError extends LumailError {
10
+ readonly retryAfter: number | null;
11
+ constructor(message?: string, retryAfter?: number | null);
12
+ }
13
+ export declare class LumailValidationError extends LumailError {
14
+ constructor(message: string);
15
+ }
16
+ export declare class LumailNotFoundError extends LumailError {
17
+ constructor(message?: string);
18
+ }
19
+ export declare class LumailPaymentRequiredError extends LumailError {
20
+ constructor(message?: string);
21
+ }
22
+ export declare class LumailConfirmationRequiredError extends LumailError {
23
+ readonly confirmationCode: number;
24
+ readonly action: string;
25
+ readonly instruction: string;
26
+ constructor(message: string, confirmationCode: number, action: string, instruction: string);
27
+ }
package/dist/index.cjs CHANGED
@@ -2,27 +2,37 @@ var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- var __moduleCache = /* @__PURE__ */ new WeakMap;
5
+ function __accessProp(key) {
6
+ return this[key];
7
+ }
6
8
  var __toCommonJS = (from) => {
7
- var entry = __moduleCache.get(from), desc;
9
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
8
10
  if (entry)
9
11
  return entry;
10
12
  entry = __defProp({}, "__esModule", { value: true });
11
- if (from && typeof from === "object" || typeof from === "function")
12
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
- get: () => from[key],
14
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
- }));
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (var key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(entry, key))
16
+ __defProp(entry, key, {
17
+ get: __accessProp.bind(from, key),
18
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
+ });
20
+ }
16
21
  __moduleCache.set(from, entry);
17
22
  return entry;
18
23
  };
24
+ var __moduleCache;
25
+ var __returnValue = (v) => v;
26
+ function __exportSetter(name, newValue) {
27
+ this[name] = __returnValue.bind(null, newValue);
28
+ }
19
29
  var __export = (target, all) => {
20
30
  for (var name in all)
21
31
  __defProp(target, name, {
22
32
  get: all[name],
23
33
  enumerable: true,
24
34
  configurable: true,
25
- set: (newValue) => all[name] = () => newValue
35
+ set: __exportSetter.bind(all, name)
26
36
  });
27
37
  };
28
38
 
@@ -34,6 +44,7 @@ __export(exports_lumail_sdk, {
34
44
  LumailPaymentRequiredError: () => LumailPaymentRequiredError,
35
45
  LumailNotFoundError: () => LumailNotFoundError,
36
46
  LumailError: () => LumailError,
47
+ LumailConfirmationRequiredError: () => LumailConfirmationRequiredError,
37
48
  LumailAuthenticationError: () => LumailAuthenticationError,
38
49
  Lumail: () => Lumail
39
50
  });
@@ -88,6 +99,19 @@ class LumailPaymentRequiredError extends LumailError {
88
99
  }
89
100
  }
90
101
 
102
+ class LumailConfirmationRequiredError extends LumailError {
103
+ confirmationCode;
104
+ action;
105
+ instruction;
106
+ constructor(message, confirmationCode, action, instruction) {
107
+ super(message, 403, "CONFIRMATION_REQUIRED");
108
+ this.name = "LumailConfirmationRequiredError";
109
+ this.confirmationCode = confirmationCode;
110
+ this.action = action;
111
+ this.instruction = instruction;
112
+ }
113
+ }
114
+
91
115
  // ../../src/lib/lumail-sdk/client.ts
92
116
  var DEFAULT_BASE_URL = "https://lumail.io/api";
93
117
  var DEFAULT_TIMEOUT = 30000;
@@ -123,11 +147,7 @@ class LumailClient {
123
147
  signal
124
148
  });
125
149
  if (response.ok) {
126
- const data = await response.json();
127
- if (data.success === false) {
128
- throw new LumailError(data.message ?? "Request failed", response.status, data.code);
129
- }
130
- return data;
150
+ return await response.json();
131
151
  }
132
152
  if (response.status === 429 && isIdempotent && attempt < maxRetries) {
133
153
  await sleep(parseRetryAfter(response, RETRY_DELAYS[attempt]));
@@ -164,8 +184,7 @@ class LumailClient {
164
184
  let body = {};
165
185
  try {
166
186
  body = await response.json();
167
- } catch {
168
- }
187
+ } catch {}
169
188
  const message = body.message ?? body.error ?? response.statusText;
170
189
  const code = body.code;
171
190
  switch (response.status) {
@@ -173,6 +192,12 @@ class LumailClient {
173
192
  return new LumailAuthenticationError(message);
174
193
  case 402:
175
194
  return new LumailPaymentRequiredError(message);
195
+ case 403: {
196
+ if (body.error === "CONFIRMATION_REQUIRED") {
197
+ return new LumailConfirmationRequiredError(message, body.confirmationCode, body.action, body.instruction);
198
+ }
199
+ return new LumailValidationError(message);
200
+ }
176
201
  case 404:
177
202
  return new LumailNotFoundError(message);
178
203
  case 429: {
@@ -272,6 +297,11 @@ class Subscribers {
272
297
  constructor(client) {
273
298
  this.client = client;
274
299
  }
300
+ async list(params) {
301
+ return this.client.request("GET", "/v1/subscribers", {
302
+ params
303
+ });
304
+ }
275
305
  async create(params) {
276
306
  return this.client.request("POST", "/v1/subscribers", {
277
307
  body: params
@@ -339,8 +369,21 @@ class Tools {
339
369
  async get(toolName) {
340
370
  return this.client.request("GET", `/v2/tools/${encodeURIComponent(toolName)}`);
341
371
  }
342
- async run(toolName, params) {
343
- return this.client.request("POST", `/v2/tools/${encodeURIComponent(toolName)}`, { body: params ?? {} });
372
+ async run(toolName, params, options) {
373
+ const { autoConfirm = true } = options ?? {};
374
+ const endpoint = `/v2/tools/${encodeURIComponent(toolName)}`;
375
+ try {
376
+ return await this.client.request("POST", endpoint, {
377
+ body: params ?? {}
378
+ });
379
+ } catch (error) {
380
+ if (autoConfirm && error instanceof LumailConfirmationRequiredError) {
381
+ return this.client.request("POST", endpoint, {
382
+ body: { ...params, confirmationCode: error.confirmationCode }
383
+ });
384
+ }
385
+ throw error;
386
+ }
344
387
  }
345
388
  }
346
389
 
@@ -0,0 +1,19 @@
1
+ import { Campaigns } from "./resources/campaigns";
2
+ import { Emails } from "./resources/emails";
3
+ import { Events } from "./resources/events";
4
+ import { Subscribers } from "./resources/subscribers";
5
+ import { Tags } from "./resources/tags";
6
+ import { Tools } from "./resources/tools";
7
+ import type { LumailOptions } from "./types";
8
+ export declare class Lumail {
9
+ readonly subscribers: Subscribers;
10
+ readonly campaigns: Campaigns;
11
+ readonly emails: Emails;
12
+ readonly tags: Tags;
13
+ readonly events: Events;
14
+ readonly tools: Tools;
15
+ constructor(options: LumailOptions);
16
+ }
17
+ export { LumailAuthenticationError, LumailConfirmationRequiredError, LumailError, LumailNotFoundError, LumailPaymentRequiredError, LumailRateLimitError, LumailValidationError, } from "./errors";
18
+ export type { AddTagsResponse, Campaign, CampaignSender, CampaignStatus, CampaignSummary, ContentType, CreateCampaignParams, CreateCampaignResponse, CreateEventParams, CreateEventResponse, CreateSubscriberParams, CreateSubscriberResponse, CreateTagParams, CreateTagResponse, DeleteCampaignResponse, DeleteSubscriberResponse, EmailContentType, GetCampaignResponse, GetSubscriberResponse, GetTagResponse, ConfirmationRequiredResponse, GetToolResponse, ListCampaignsParams, ListCampaignsResponse, ListEventsParams, ListEventsResponse, ListSubscriberEntry, ListSubscribersParams, ListSubscribersResponse, ListTagsResponse, ListToolsResponse, LumailOptions, RemoveTagsResponse, RunToolResponse, SendCampaignParams, SendCampaignResponse, SendEmailParams, SendEmailResponse, Subscriber, SubscriberEvent, SubscriberEventType, SubscriberStatus, SubscriberTag, Tag, TagWithCount, ToolDefinition, UnsubscribeResponse, UpdateCampaignParams, UpdateCampaignResponse, UpdateSubscriberParams, UpdateSubscriberResponse, UpdateTagParams, UpdateTagResponse, VerifyEmailParams, VerifyEmailResponse, } from "./types";
19
+ export type { RunToolOptions } from "./resources/tools";
package/dist/index.js CHANGED
@@ -47,6 +47,19 @@ class LumailPaymentRequiredError extends LumailError {
47
47
  }
48
48
  }
49
49
 
50
+ class LumailConfirmationRequiredError extends LumailError {
51
+ confirmationCode;
52
+ action;
53
+ instruction;
54
+ constructor(message, confirmationCode, action, instruction) {
55
+ super(message, 403, "CONFIRMATION_REQUIRED");
56
+ this.name = "LumailConfirmationRequiredError";
57
+ this.confirmationCode = confirmationCode;
58
+ this.action = action;
59
+ this.instruction = instruction;
60
+ }
61
+ }
62
+
50
63
  // ../../src/lib/lumail-sdk/client.ts
51
64
  var DEFAULT_BASE_URL = "https://lumail.io/api";
52
65
  var DEFAULT_TIMEOUT = 30000;
@@ -82,11 +95,7 @@ class LumailClient {
82
95
  signal
83
96
  });
84
97
  if (response.ok) {
85
- const data = await response.json();
86
- if (data.success === false) {
87
- throw new LumailError(data.message ?? "Request failed", response.status, data.code);
88
- }
89
- return data;
98
+ return await response.json();
90
99
  }
91
100
  if (response.status === 429 && isIdempotent && attempt < maxRetries) {
92
101
  await sleep(parseRetryAfter(response, RETRY_DELAYS[attempt]));
@@ -123,8 +132,7 @@ class LumailClient {
123
132
  let body = {};
124
133
  try {
125
134
  body = await response.json();
126
- } catch {
127
- }
135
+ } catch {}
128
136
  const message = body.message ?? body.error ?? response.statusText;
129
137
  const code = body.code;
130
138
  switch (response.status) {
@@ -132,6 +140,12 @@ class LumailClient {
132
140
  return new LumailAuthenticationError(message);
133
141
  case 402:
134
142
  return new LumailPaymentRequiredError(message);
143
+ case 403: {
144
+ if (body.error === "CONFIRMATION_REQUIRED") {
145
+ return new LumailConfirmationRequiredError(message, body.confirmationCode, body.action, body.instruction);
146
+ }
147
+ return new LumailValidationError(message);
148
+ }
135
149
  case 404:
136
150
  return new LumailNotFoundError(message);
137
151
  case 429: {
@@ -231,6 +245,11 @@ class Subscribers {
231
245
  constructor(client) {
232
246
  this.client = client;
233
247
  }
248
+ async list(params) {
249
+ return this.client.request("GET", "/v1/subscribers", {
250
+ params
251
+ });
252
+ }
234
253
  async create(params) {
235
254
  return this.client.request("POST", "/v1/subscribers", {
236
255
  body: params
@@ -298,8 +317,21 @@ class Tools {
298
317
  async get(toolName) {
299
318
  return this.client.request("GET", `/v2/tools/${encodeURIComponent(toolName)}`);
300
319
  }
301
- async run(toolName, params) {
302
- return this.client.request("POST", `/v2/tools/${encodeURIComponent(toolName)}`, { body: params ?? {} });
320
+ async run(toolName, params, options) {
321
+ const { autoConfirm = true } = options ?? {};
322
+ const endpoint = `/v2/tools/${encodeURIComponent(toolName)}`;
323
+ try {
324
+ return await this.client.request("POST", endpoint, {
325
+ body: params ?? {}
326
+ });
327
+ } catch (error) {
328
+ if (autoConfirm && error instanceof LumailConfirmationRequiredError) {
329
+ return this.client.request("POST", endpoint, {
330
+ body: { ...params, confirmationCode: error.confirmationCode }
331
+ });
332
+ }
333
+ throw error;
334
+ }
303
335
  }
304
336
  }
305
337
 
@@ -327,6 +359,7 @@ export {
327
359
  LumailPaymentRequiredError,
328
360
  LumailNotFoundError,
329
361
  LumailError,
362
+ LumailConfirmationRequiredError,
330
363
  LumailAuthenticationError,
331
364
  Lumail
332
365
  };
@@ -0,0 +1,12 @@
1
+ import type { LumailClient } from "../client";
2
+ import type { CreateCampaignParams, CreateCampaignResponse, DeleteCampaignResponse, GetCampaignResponse, ListCampaignsParams, ListCampaignsResponse, SendCampaignParams, SendCampaignResponse, UpdateCampaignParams, UpdateCampaignResponse } from "../types";
3
+ export declare class Campaigns {
4
+ private readonly client;
5
+ constructor(client: LumailClient);
6
+ list(params?: ListCampaignsParams): Promise<ListCampaignsResponse>;
7
+ create(params: CreateCampaignParams): Promise<CreateCampaignResponse>;
8
+ get(campaignId: string): Promise<GetCampaignResponse>;
9
+ update(campaignId: string, params: UpdateCampaignParams): Promise<UpdateCampaignResponse>;
10
+ delete(campaignId: string): Promise<DeleteCampaignResponse>;
11
+ send(campaignId: string, params?: SendCampaignParams): Promise<SendCampaignResponse>;
12
+ }
@@ -0,0 +1,8 @@
1
+ import type { LumailClient } from "../client";
2
+ import type { SendEmailParams, SendEmailResponse, VerifyEmailParams, VerifyEmailResponse } from "../types";
3
+ export declare class Emails {
4
+ private readonly client;
5
+ constructor(client: LumailClient);
6
+ send(params: SendEmailParams): Promise<SendEmailResponse>;
7
+ verify(params: VerifyEmailParams): Promise<VerifyEmailResponse>;
8
+ }
@@ -0,0 +1,7 @@
1
+ import type { LumailClient } from "../client";
2
+ import type { CreateEventParams, CreateEventResponse } from "../types";
3
+ export declare class Events {
4
+ private readonly client;
5
+ constructor(client: LumailClient);
6
+ create(params: CreateEventParams): Promise<CreateEventResponse>;
7
+ }
@@ -0,0 +1,15 @@
1
+ import type { LumailClient } from "../client";
2
+ import type { AddTagsResponse, CreateSubscriberParams, CreateSubscriberResponse, DeleteSubscriberResponse, GetSubscriberResponse, ListEventsParams, ListEventsResponse, ListSubscribersParams, ListSubscribersResponse, RemoveTagsResponse, UnsubscribeResponse, UpdateSubscriberParams, UpdateSubscriberResponse } from "../types";
3
+ export declare class Subscribers {
4
+ private readonly client;
5
+ constructor(client: LumailClient);
6
+ list(params?: ListSubscribersParams): Promise<ListSubscribersResponse>;
7
+ create(params: CreateSubscriberParams): Promise<CreateSubscriberResponse>;
8
+ get(idOrEmail: string): Promise<GetSubscriberResponse>;
9
+ update(idOrEmail: string, params: UpdateSubscriberParams): Promise<UpdateSubscriberResponse>;
10
+ delete(idOrEmail: string): Promise<DeleteSubscriberResponse>;
11
+ unsubscribe(idOrEmail: string): Promise<UnsubscribeResponse>;
12
+ addTags(idOrEmail: string, tags: string[]): Promise<AddTagsResponse>;
13
+ removeTags(idOrEmail: string, tags: string[]): Promise<RemoveTagsResponse>;
14
+ listEvents(idOrEmail: string, params?: ListEventsParams): Promise<ListEventsResponse>;
15
+ }
@@ -0,0 +1,10 @@
1
+ import type { LumailClient } from "../client";
2
+ import type { CreateTagParams, CreateTagResponse, GetTagResponse, ListTagsResponse, UpdateTagParams, UpdateTagResponse } from "../types";
3
+ export declare class Tags {
4
+ private readonly client;
5
+ constructor(client: LumailClient);
6
+ list(): Promise<ListTagsResponse>;
7
+ create(params: CreateTagParams): Promise<CreateTagResponse>;
8
+ get(idOrName: string): Promise<GetTagResponse>;
9
+ update(idOrName: string, params: UpdateTagParams): Promise<UpdateTagResponse>;
10
+ }
@@ -0,0 +1,12 @@
1
+ import type { LumailClient } from "../client";
2
+ import type { GetToolResponse, ListToolsResponse, RunToolResponse } from "../types";
3
+ export type RunToolOptions = {
4
+ autoConfirm?: boolean;
5
+ };
6
+ export declare class Tools {
7
+ private readonly client;
8
+ constructor(client: LumailClient);
9
+ list(): Promise<ListToolsResponse>;
10
+ get(toolName: string): Promise<GetToolResponse>;
11
+ run<T = unknown>(toolName: string, params?: Record<string, unknown>, options?: RunToolOptions): Promise<RunToolResponse<T>>;
12
+ }
@@ -0,0 +1,327 @@
1
+ export type LumailOptions = {
2
+ apiKey: string;
3
+ baseUrl?: string;
4
+ };
5
+ export type RequestOptions = {
6
+ params?: Record<string, string | number | boolean | undefined>;
7
+ body?: unknown;
8
+ signal?: AbortSignal;
9
+ };
10
+ export type SubscriberStatus = "PENDING_CONFIRMATION" | "SUBSCRIBED" | "UNSUBSCRIBED" | "BOUNCED" | "BANNED" | "COMPLAINED";
11
+ export type SubscriberTag = {
12
+ id: string;
13
+ name: string;
14
+ };
15
+ export type Subscriber = {
16
+ id: string;
17
+ email: string;
18
+ name: string | null;
19
+ phone: string | null;
20
+ status: SubscriberStatus;
21
+ tags: SubscriberTag[];
22
+ fields: Record<string, string>;
23
+ createdAt?: string;
24
+ updatedAt?: string;
25
+ };
26
+ export type ListSubscribersParams = {
27
+ tag?: string;
28
+ status?: SubscriberStatus | "all";
29
+ limit?: number;
30
+ cursor?: string;
31
+ query?: string;
32
+ };
33
+ export type ListSubscriberEntry = {
34
+ id: string;
35
+ email: string;
36
+ name: string | null;
37
+ status: SubscriberStatus;
38
+ createdAt: string;
39
+ tags: SubscriberTag[];
40
+ };
41
+ export type ListSubscribersResponse = {
42
+ success: true;
43
+ subscribers: ListSubscriberEntry[];
44
+ total: number;
45
+ limit: number;
46
+ nextCursor: string | null;
47
+ };
48
+ export type CreateSubscriberParams = {
49
+ email: string;
50
+ name?: string | null;
51
+ phone?: string | null;
52
+ tags?: string[];
53
+ fields?: Record<string, string | null>;
54
+ replaceTags?: boolean;
55
+ resubscribe?: boolean;
56
+ triggerWorkflows?: boolean;
57
+ ipAddress?: string;
58
+ country?: string;
59
+ };
60
+ export type UpdateSubscriberParams = Omit<CreateSubscriberParams, "email"> & {
61
+ email?: string;
62
+ };
63
+ export type CreateSubscriberResponse = {
64
+ success: true;
65
+ subscriber: Subscriber;
66
+ warnings?: string[];
67
+ };
68
+ export type GetSubscriberResponse = {
69
+ success: true;
70
+ subscriber: Subscriber;
71
+ };
72
+ export type UpdateSubscriberResponse = {
73
+ success: true;
74
+ subscriber: Subscriber;
75
+ warnings?: string[];
76
+ };
77
+ export type DeleteSubscriberResponse = {
78
+ success: true;
79
+ message: string;
80
+ };
81
+ export type UnsubscribeResponse = {
82
+ success: true;
83
+ message: string;
84
+ subscriber: {
85
+ id: string;
86
+ email: string;
87
+ status: SubscriberStatus;
88
+ };
89
+ };
90
+ export type AddTagsResponse = {
91
+ success: true;
92
+ added: number;
93
+ tags: SubscriberTag[];
94
+ };
95
+ export type RemoveTagsResponse = {
96
+ success: true;
97
+ removed: number;
98
+ tags?: SubscriberTag[];
99
+ message?: string;
100
+ };
101
+ export type SubscriberEventType = "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";
102
+ export type SubscriberEvent = {
103
+ id: string;
104
+ eventType: SubscriberEventType;
105
+ data: Record<string, unknown> | null;
106
+ subscriberId: string;
107
+ createdAt: string;
108
+ subscriber: {
109
+ id: string;
110
+ email: string;
111
+ name: string | null;
112
+ };
113
+ };
114
+ export type ListEventsParams = {
115
+ cursor?: string;
116
+ take?: number;
117
+ eventTypes?: SubscriberEventType[];
118
+ order?: "asc" | "desc";
119
+ startDate?: string;
120
+ endDate?: string;
121
+ search?: string;
122
+ };
123
+ export type ListEventsResponse = {
124
+ events: SubscriberEvent[];
125
+ nextCursor: string | null;
126
+ };
127
+ export type CampaignStatus = "DRAFT" | "ARCHIVED" | "SCHEDULED" | "SENDING" | "SENT" | "FAILED";
128
+ export type ContentType = "MAILY" | "PLATE" | "MARKDOWN";
129
+ export type CampaignSummary = {
130
+ id: string;
131
+ campaignId: string;
132
+ name: string;
133
+ subject: string;
134
+ preview: string | null;
135
+ status: CampaignStatus;
136
+ contentType: ContentType;
137
+ sendAt: string | null;
138
+ scheduledAt: string | null;
139
+ recipientCount: number;
140
+ emailsSentCount: number;
141
+ emailsOpenedCount: number;
142
+ emailsClickedCount: number;
143
+ createdAt: string;
144
+ updatedAt: string;
145
+ };
146
+ export type CampaignSender = {
147
+ id: string;
148
+ name: string;
149
+ displayName: string;
150
+ localPart: string;
151
+ replyTo: string | null;
152
+ domain: {
153
+ domain: string;
154
+ };
155
+ };
156
+ export type Campaign = CampaignSummary & {
157
+ content: unknown;
158
+ replyTo: string | null;
159
+ senderId: string;
160
+ timezone: string | null;
161
+ recipientFilters: unknown[];
162
+ sender: CampaignSender;
163
+ };
164
+ export type ListCampaignsParams = {
165
+ status?: "all" | "DRAFT" | "ARCHIVED" | "SCHEDULED" | "SENT";
166
+ page?: number;
167
+ limit?: number;
168
+ query?: string;
169
+ sortBy?: "name" | "name_desc";
170
+ };
171
+ export type ListCampaignsResponse = {
172
+ success: true;
173
+ campaigns: CampaignSummary[];
174
+ total: number;
175
+ page: number;
176
+ limit: number;
177
+ pageCount: number;
178
+ };
179
+ export type CreateCampaignParams = {
180
+ subject: string;
181
+ name?: string;
182
+ preview?: string | null;
183
+ content?: unknown;
184
+ contentType?: ContentType;
185
+ senderId?: string;
186
+ replyTo?: string | null;
187
+ };
188
+ export type CreateCampaignResponse = {
189
+ success: true;
190
+ campaign: CampaignSummary & {
191
+ content: unknown;
192
+ replyTo: string | null;
193
+ senderId: string;
194
+ };
195
+ campaignId: string;
196
+ };
197
+ export type GetCampaignResponse = {
198
+ success: true;
199
+ campaign: Campaign;
200
+ campaignId: string;
201
+ };
202
+ export type UpdateCampaignParams = Partial<CreateCampaignParams>;
203
+ export type UpdateCampaignResponse = CreateCampaignResponse;
204
+ export type DeleteCampaignResponse = {
205
+ success: true;
206
+ deleted: true;
207
+ campaignId: string;
208
+ };
209
+ export type SendCampaignParams = {
210
+ scheduledAt?: string;
211
+ timezone?: string;
212
+ };
213
+ export type SendCampaignResponse = {
214
+ success: true;
215
+ campaign: {
216
+ id: string;
217
+ campaignId: string;
218
+ status: "SCHEDULED" | "SENDING";
219
+ scheduledAt: string;
220
+ timezone: string;
221
+ };
222
+ campaignId: string;
223
+ };
224
+ export type EmailContentType = "MARKDOWN" | "HTML" | "TIPTAP";
225
+ export type SendEmailParams = {
226
+ to: string;
227
+ from: string;
228
+ subject: string;
229
+ content: string;
230
+ contentType?: EmailContentType;
231
+ preview?: string;
232
+ replyTo?: string;
233
+ transactional?: boolean;
234
+ tracking?: {
235
+ links?: boolean;
236
+ open?: boolean;
237
+ };
238
+ };
239
+ export type SendEmailResponse = {
240
+ success: true;
241
+ message: string;
242
+ qstashMessageId: string;
243
+ };
244
+ export type VerifyEmailParams = {
245
+ email: string;
246
+ };
247
+ export type VerifyEmailResponse = {
248
+ success: boolean;
249
+ error?: string;
250
+ code?: string;
251
+ suggestion?: string;
252
+ warnings?: string[];
253
+ };
254
+ export type Tag = {
255
+ id: string;
256
+ name: string;
257
+ };
258
+ export type TagWithCount = Tag & {
259
+ subscribersCount: number;
260
+ };
261
+ export type ListTagsResponse = {
262
+ success: true;
263
+ tags: Tag[];
264
+ };
265
+ export type CreateTagParams = {
266
+ name: string;
267
+ };
268
+ export type CreateTagResponse = {
269
+ success: true;
270
+ tag: Tag;
271
+ };
272
+ export type GetTagResponse = {
273
+ tag: TagWithCount;
274
+ };
275
+ export type UpdateTagParams = {
276
+ name: string;
277
+ };
278
+ export type UpdateTagResponse = {
279
+ success: true;
280
+ tag: Tag;
281
+ };
282
+ export type CreateEventParams = {
283
+ eventType: SubscriberEventType;
284
+ subscriber: string;
285
+ data: Record<string, unknown>;
286
+ };
287
+ export type CreateEventResponse = {
288
+ success: true;
289
+ event: {
290
+ id: string;
291
+ eventType: SubscriberEventType;
292
+ data: Record<string, unknown>;
293
+ subscriberId: string;
294
+ organizationId: string;
295
+ createdAt: string;
296
+ };
297
+ };
298
+ export type ToolDefinition = {
299
+ name: string;
300
+ description: string;
301
+ category?: string;
302
+ inputSchema?: Record<string, unknown>;
303
+ outputSchema?: Record<string, unknown>;
304
+ };
305
+ export type ListToolsResponse = {
306
+ success: true;
307
+ total: number;
308
+ tools: ToolDefinition[];
309
+ grouped: Record<string, ToolDefinition[]>;
310
+ docs: string;
311
+ };
312
+ export type GetToolResponse = {
313
+ success: true;
314
+ tool: ToolDefinition;
315
+ };
316
+ export type RunToolResponse<T = unknown> = {
317
+ success: true;
318
+ data: T;
319
+ };
320
+ export type ConfirmationRequiredResponse = {
321
+ success: false;
322
+ error: "CONFIRMATION_REQUIRED";
323
+ message: string;
324
+ action: string;
325
+ confirmationCode: number;
326
+ instruction: string;
327
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumail",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Lumail SDK and CLI - email marketing platform",
5
5
  "author": "Melvyn",
6
6
  "license": "MIT",
@@ -9,6 +9,10 @@
9
9
  "url": "https://github.com/Melvynx/lumail.io.git",
10
10
  "directory": "packages/lumail"
11
11
  },
12
+ "publishConfig": {
13
+ "access": "public",
14
+ "provenance": false
15
+ },
12
16
  "type": "module",
13
17
  "bin": {
14
18
  "lumail": "./dist/cli.js"
@@ -23,7 +27,7 @@
23
27
  }
24
28
  },
25
29
  "scripts": {
26
- "build": "bun run build.ts",
30
+ "build": "rm -rf dist && bun run build.ts && tsc -p tsconfig.build.json",
27
31
  "release": "release-it",
28
32
  "prepublishOnly": "bun run build"
29
33
  },