lumail 0.1.0 → 0.1.1

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,253 @@
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 { isValid } = await lumail.emails.verify({
154
+ email: "check@example.com",
155
+ });
156
+ ```
157
+
158
+ ### Tags
159
+
160
+ ```typescript
161
+ // List all tags
162
+ const { tags } = await lumail.tags.list();
163
+
164
+ // Create a tag
165
+ const { tag } = await lumail.tags.create({ name: "vip" });
166
+
167
+ // Get a tag (includes subscriber count)
168
+ const { tag } = await lumail.tags.get("vip");
169
+
170
+ // Update a tag
171
+ await lumail.tags.update("vip", { name: "VIP Customers" });
172
+ ```
173
+
174
+ ### Events
175
+
176
+ ```typescript
177
+ // Create a custom event
178
+ await lumail.events.create({
179
+ eventType: "SUBSCRIBER_PAYMENT",
180
+ subscriber: "john@example.com",
181
+ data: { amount: 99, currency: "USD", plan: "pro" },
182
+ });
183
+ ```
184
+
185
+ ### Tools (v2)
186
+
187
+ AI-powered tools for email marketing.
188
+
189
+ ```typescript
190
+ // List available tools
191
+ const { tools } = await lumail.tools.list();
192
+
193
+ // Get tool details
194
+ const { tool } = await lumail.tools.get("generate-subject-line");
195
+
196
+ // Run a tool
197
+ const result = await lumail.tools.run("generate-subject-line", {
198
+ topic: "Spring sale announcement",
199
+ tone: "excited",
200
+ });
201
+ ```
202
+
203
+ ## Error Handling
204
+
205
+ The SDK throws typed errors you can catch individually:
206
+
207
+ ```typescript
208
+ import {
209
+ Lumail,
210
+ LumailAuthenticationError,
211
+ LumailValidationError,
212
+ LumailNotFoundError,
213
+ LumailRateLimitError,
214
+ LumailPaymentRequiredError,
215
+ } from "lumail";
216
+
217
+ try {
218
+ await lumail.subscribers.get("unknown@example.com");
219
+ } catch (error) {
220
+ if (error instanceof LumailNotFoundError) {
221
+ console.log("Subscriber not found");
222
+ } else if (error instanceof LumailAuthenticationError) {
223
+ console.log("Invalid API key");
224
+ } else if (error instanceof LumailRateLimitError) {
225
+ console.log(`Rate limited, retry after ${error.retryAfter}ms`);
226
+ } else if (error instanceof LumailValidationError) {
227
+ console.log(`Bad request: ${error.message}`);
228
+ } else if (error instanceof LumailPaymentRequiredError) {
229
+ console.log("Plan limit reached — upgrade at lumail.io/billing");
230
+ }
231
+ }
232
+ ```
233
+
234
+ All errors extend `LumailError` which includes `status` and `code` properties.
235
+
236
+ The client automatically retries idempotent requests (GET, PUT, DELETE) on rate-limit (429) and network errors with exponential backoff.
237
+
238
+ ## CLI
239
+
240
+ The package also ships a CLI:
241
+
242
+ ```bash
243
+ npx lumail --help
244
+ ```
245
+
246
+ ## Requirements
247
+
248
+ - Node.js 18+ (uses native `fetch`)
249
+ - An API key from [lumail.io](https://lumail.io)
250
+
251
+ ## License
252
+
253
+ 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;
@@ -2336,8 +2362,7 @@ class LumailClient {
2336
2362
  let body = {};
2337
2363
  try {
2338
2364
  body = await response.json();
2339
- } catch {
2340
- }
2365
+ } catch {}
2341
2366
  const message = body.message ?? body.error ?? response.statusText;
2342
2367
  const code = body.code;
2343
2368
  switch (response.status) {
@@ -2345,6 +2370,12 @@ class LumailClient {
2345
2370
  return new LumailAuthenticationError(message);
2346
2371
  case 402:
2347
2372
  return new LumailPaymentRequiredError(message);
2373
+ case 403: {
2374
+ if (body.error === "CONFIRMATION_REQUIRED") {
2375
+ return new LumailConfirmationRequiredError(message, body.confirmationCode, body.action, body.instruction);
2376
+ }
2377
+ return new LumailValidationError(message);
2378
+ }
2348
2379
  case 404:
2349
2380
  return new LumailNotFoundError(message);
2350
2381
  case 429: {
@@ -2444,6 +2475,11 @@ class Subscribers {
2444
2475
  constructor(client) {
2445
2476
  this.client = client;
2446
2477
  }
2478
+ async list(params) {
2479
+ return this.client.request("GET", "/v1/subscribers", {
2480
+ params
2481
+ });
2482
+ }
2447
2483
  async create(params) {
2448
2484
  return this.client.request("POST", "/v1/subscribers", {
2449
2485
  body: params
@@ -2511,8 +2547,21 @@ class Tools {
2511
2547
  async get(toolName) {
2512
2548
  return this.client.request("GET", `/v2/tools/${encodeURIComponent(toolName)}`);
2513
2549
  }
2514
- async run(toolName, params) {
2515
- return this.client.request("POST", `/v2/tools/${encodeURIComponent(toolName)}`, { body: params ?? {} });
2550
+ async run(toolName, params, options) {
2551
+ const { autoConfirm = true } = options ?? {};
2552
+ const endpoint = `/v2/tools/${encodeURIComponent(toolName)}`;
2553
+ try {
2554
+ return await this.client.request("POST", endpoint, {
2555
+ body: params ?? {}
2556
+ });
2557
+ } catch (error) {
2558
+ if (autoConfirm && error instanceof LumailConfirmationRequiredError) {
2559
+ return this.client.request("POST", endpoint, {
2560
+ body: { ...params, confirmationCode: error.confirmationCode }
2561
+ });
2562
+ }
2563
+ throw error;
2564
+ }
2516
2565
  }
2517
2566
  }
2518
2567
 
@@ -2664,7 +2713,7 @@ function fmtCell(v) {
2664
2713
  return s;
2665
2714
  }
2666
2715
  function stripAnsi(s) {
2667
- return s.replace(/\x1b\[[0-9;]*m/g, "");
2716
+ return s.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), "");
2668
2717
  }
2669
2718
  function printTable(rows) {
2670
2719
  const cols = Object.keys(rows[0] ?? {});
@@ -2753,6 +2802,26 @@ API key is valid.`));
2753
2802
 
2754
2803
  // ../../src/cli/commands/subscribers.ts
2755
2804
  var subscribersCommand = new Command("subscribers").description("Manage subscribers");
2805
+ 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) => {
2806
+ try {
2807
+ const limit = Number(opts.limit);
2808
+ if (!Number.isFinite(limit) || limit < 1) {
2809
+ console.error("Error: --limit must be a positive integer");
2810
+ process.exit(1);
2811
+ }
2812
+ const lumail = getLumail();
2813
+ const result = await lumail.subscribers.list({
2814
+ tag: opts.tag,
2815
+ status: opts.status,
2816
+ limit,
2817
+ cursor: opts.cursor,
2818
+ query: opts.query
2819
+ });
2820
+ output(result);
2821
+ } catch (error) {
2822
+ handleError(error);
2823
+ }
2824
+ });
2756
2825
  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
2826
  try {
2758
2827
  const lumail = getLumail();
@@ -3041,7 +3110,7 @@ import { homedir as homedir2 } from "os";
3041
3110
  import { join as join2 } from "path";
3042
3111
  var SKILL_URL = "https://raw.githubusercontent.com/Melvynx/lumail-opensource/main/skills/lumail/SKILL.md";
3043
3112
  var SKILLS_DIR = join2(homedir2(), ".claude", "skills", "lumail");
3044
- function prompt(question) {
3113
+ async function prompt(question) {
3045
3114
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3046
3115
  return new Promise((resolve) => {
3047
3116
  rl.question(question, (answer) => {
@@ -3097,7 +3166,7 @@ Installing Lumail skill...`));
3097
3166
  console.log(import_picocolors3.default.green("Skill installed to ~/.claude/skills/lumail/"));
3098
3167
  } catch {
3099
3168
  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));
3169
+ console.log(import_picocolors3.default.dim(`Manual install: curl -o ~/.claude/skills/lumail/SKILL.md --create-dirs ${SKILL_URL}`));
3101
3170
  }
3102
3171
  setToken(token);
3103
3172
  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;
@@ -164,8 +188,7 @@ class LumailClient {
164
188
  let body = {};
165
189
  try {
166
190
  body = await response.json();
167
- } catch {
168
- }
191
+ } catch {}
169
192
  const message = body.message ?? body.error ?? response.statusText;
170
193
  const code = body.code;
171
194
  switch (response.status) {
@@ -173,6 +196,12 @@ class LumailClient {
173
196
  return new LumailAuthenticationError(message);
174
197
  case 402:
175
198
  return new LumailPaymentRequiredError(message);
199
+ case 403: {
200
+ if (body.error === "CONFIRMATION_REQUIRED") {
201
+ return new LumailConfirmationRequiredError(message, body.confirmationCode, body.action, body.instruction);
202
+ }
203
+ return new LumailValidationError(message);
204
+ }
176
205
  case 404:
177
206
  return new LumailNotFoundError(message);
178
207
  case 429: {
@@ -272,6 +301,11 @@ class Subscribers {
272
301
  constructor(client) {
273
302
  this.client = client;
274
303
  }
304
+ async list(params) {
305
+ return this.client.request("GET", "/v1/subscribers", {
306
+ params
307
+ });
308
+ }
275
309
  async create(params) {
276
310
  return this.client.request("POST", "/v1/subscribers", {
277
311
  body: params
@@ -339,8 +373,21 @@ class Tools {
339
373
  async get(toolName) {
340
374
  return this.client.request("GET", `/v2/tools/${encodeURIComponent(toolName)}`);
341
375
  }
342
- async run(toolName, params) {
343
- return this.client.request("POST", `/v2/tools/${encodeURIComponent(toolName)}`, { body: params ?? {} });
376
+ async run(toolName, params, options) {
377
+ const { autoConfirm = true } = options ?? {};
378
+ const endpoint = `/v2/tools/${encodeURIComponent(toolName)}`;
379
+ try {
380
+ return await this.client.request("POST", endpoint, {
381
+ body: params ?? {}
382
+ });
383
+ } catch (error) {
384
+ if (autoConfirm && error instanceof LumailConfirmationRequiredError) {
385
+ return this.client.request("POST", endpoint, {
386
+ body: { ...params, confirmationCode: error.confirmationCode }
387
+ });
388
+ }
389
+ throw error;
390
+ }
344
391
  }
345
392
  }
346
393
 
@@ -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;
@@ -123,8 +136,7 @@ class LumailClient {
123
136
  let body = {};
124
137
  try {
125
138
  body = await response.json();
126
- } catch {
127
- }
139
+ } catch {}
128
140
  const message = body.message ?? body.error ?? response.statusText;
129
141
  const code = body.code;
130
142
  switch (response.status) {
@@ -132,6 +144,12 @@ class LumailClient {
132
144
  return new LumailAuthenticationError(message);
133
145
  case 402:
134
146
  return new LumailPaymentRequiredError(message);
147
+ case 403: {
148
+ if (body.error === "CONFIRMATION_REQUIRED") {
149
+ return new LumailConfirmationRequiredError(message, body.confirmationCode, body.action, body.instruction);
150
+ }
151
+ return new LumailValidationError(message);
152
+ }
135
153
  case 404:
136
154
  return new LumailNotFoundError(message);
137
155
  case 429: {
@@ -231,6 +249,11 @@ class Subscribers {
231
249
  constructor(client) {
232
250
  this.client = client;
233
251
  }
252
+ async list(params) {
253
+ return this.client.request("GET", "/v1/subscribers", {
254
+ params
255
+ });
256
+ }
234
257
  async create(params) {
235
258
  return this.client.request("POST", "/v1/subscribers", {
236
259
  body: params
@@ -298,8 +321,21 @@ class Tools {
298
321
  async get(toolName) {
299
322
  return this.client.request("GET", `/v2/tools/${encodeURIComponent(toolName)}`);
300
323
  }
301
- async run(toolName, params) {
302
- return this.client.request("POST", `/v2/tools/${encodeURIComponent(toolName)}`, { body: params ?? {} });
324
+ async run(toolName, params, options) {
325
+ const { autoConfirm = true } = options ?? {};
326
+ const endpoint = `/v2/tools/${encodeURIComponent(toolName)}`;
327
+ try {
328
+ return await this.client.request("POST", endpoint, {
329
+ body: params ?? {}
330
+ });
331
+ } catch (error) {
332
+ if (autoConfirm && error instanceof LumailConfirmationRequiredError) {
333
+ return this.client.request("POST", endpoint, {
334
+ body: { ...params, confirmationCode: error.confirmationCode }
335
+ });
336
+ }
337
+ throw error;
338
+ }
303
339
  }
304
340
  }
305
341
 
@@ -327,6 +363,7 @@ export {
327
363
  LumailPaymentRequiredError,
328
364
  LumailNotFoundError,
329
365
  LumailError,
366
+ LumailConfirmationRequiredError,
330
367
  LumailAuthenticationError,
331
368
  Lumail
332
369
  };
@@ -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,323 @@
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
+ isValid: boolean;
249
+ };
250
+ export type Tag = {
251
+ id: string;
252
+ name: string;
253
+ };
254
+ export type TagWithCount = Tag & {
255
+ subscribersCount: number;
256
+ };
257
+ export type ListTagsResponse = {
258
+ success: true;
259
+ tags: Tag[];
260
+ };
261
+ export type CreateTagParams = {
262
+ name: string;
263
+ };
264
+ export type CreateTagResponse = {
265
+ success: true;
266
+ tag: Tag;
267
+ };
268
+ export type GetTagResponse = {
269
+ tag: TagWithCount;
270
+ };
271
+ export type UpdateTagParams = {
272
+ name: string;
273
+ };
274
+ export type UpdateTagResponse = {
275
+ success: true;
276
+ tag: Tag;
277
+ };
278
+ export type CreateEventParams = {
279
+ eventType: SubscriberEventType;
280
+ subscriber: string;
281
+ data: Record<string, unknown>;
282
+ };
283
+ export type CreateEventResponse = {
284
+ success: true;
285
+ event: {
286
+ id: string;
287
+ eventType: SubscriberEventType;
288
+ data: Record<string, unknown>;
289
+ subscriberId: string;
290
+ organizationId: string;
291
+ createdAt: string;
292
+ };
293
+ };
294
+ export type ToolDefinition = {
295
+ name: string;
296
+ description: string;
297
+ category?: string;
298
+ inputSchema?: Record<string, unknown>;
299
+ outputSchema?: Record<string, unknown>;
300
+ };
301
+ export type ListToolsResponse = {
302
+ success: true;
303
+ total: number;
304
+ tools: ToolDefinition[];
305
+ grouped: Record<string, ToolDefinition[]>;
306
+ docs: string;
307
+ };
308
+ export type GetToolResponse = {
309
+ success: true;
310
+ tool: ToolDefinition;
311
+ };
312
+ export type RunToolResponse<T = unknown> = {
313
+ success: true;
314
+ data: T;
315
+ };
316
+ export type ConfirmationRequiredResponse = {
317
+ success: false;
318
+ error: "CONFIRMATION_REQUIRED";
319
+ message: string;
320
+ action: string;
321
+ confirmationCode: number;
322
+ instruction: string;
323
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumail",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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
  },