mailchannels-sdk 1.5.2 → 1.7.0
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 +7 -4
- package/dist/_chunks/mailchannels.d.mts +126 -122
- package/dist/_chunks/mailchannels.mjs +88 -16
- package/dist/cli/index.mjs +483 -28
- package/dist/plugins/nodemailer/index.d.mts +10 -8
- package/dist/plugins/nodemailer/index.mjs +9 -4
- package/dist/simulator/index.d.mts +2 -3
- package/package.json +16 -14
- package/CHANGELOG.md +0 -1572
package/dist/cli/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Attachment, Emails, MailChannelsClient, name, version } from "../_chunks/mailchannels.mjs";
|
|
2
|
+
import { Attachment, Emails, MailChannelsClient, Suppressions, Webhooks, name, version } from "../_chunks/mailchannels.mjs";
|
|
3
3
|
import { createSimulator } from "../_chunks/simulator.mjs";
|
|
4
4
|
import { defineCommand, runMain } from "citty";
|
|
5
5
|
var simulate_default = defineCommand({
|
|
@@ -39,16 +39,42 @@ var simulate_default = defineCommand({
|
|
|
39
39
|
process.on("SIGTERM", shutdown);
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
|
+
const getApiKey = (apiKey) => {
|
|
43
|
+
const { MAILCHANNELS_API_KEY } = process.env;
|
|
44
|
+
const key = apiKey || MAILCHANNELS_API_KEY;
|
|
45
|
+
if (!key) {
|
|
46
|
+
console.error("Missing API key. Provide it using the '--api-key' flag or set the 'MAILCHANNELS_API_KEY' environment variable.");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
return key;
|
|
50
|
+
};
|
|
51
|
+
const sharedArgs = { "api-key": {
|
|
52
|
+
type: "string",
|
|
53
|
+
alias: "k",
|
|
54
|
+
valueHint: "key"
|
|
55
|
+
} };
|
|
42
56
|
const emailArgs = {
|
|
43
|
-
|
|
57
|
+
...sharedArgs,
|
|
58
|
+
"campaign-id": {
|
|
59
|
+
type: "string",
|
|
60
|
+
valueHint: "id"
|
|
61
|
+
},
|
|
62
|
+
"dkim-domain": {
|
|
63
|
+
type: "string",
|
|
64
|
+
valueHint: "domain"
|
|
65
|
+
},
|
|
66
|
+
"dkim-selector": {
|
|
67
|
+
type: "string",
|
|
68
|
+
valueHint: "selector"
|
|
69
|
+
},
|
|
70
|
+
"dkim-private-key": {
|
|
71
|
+
type: "string",
|
|
72
|
+
valueHint: "key"
|
|
73
|
+
},
|
|
74
|
+
"envelope-from": {
|
|
44
75
|
type: "string",
|
|
45
|
-
|
|
76
|
+
valueHint: "address"
|
|
46
77
|
},
|
|
47
|
-
"campaign-id": { type: "string" },
|
|
48
|
-
"dkim-domain": { type: "string" },
|
|
49
|
-
"dkim-selector": { type: "string" },
|
|
50
|
-
"dkim-private-key": { type: "string" },
|
|
51
|
-
"envelope-from": { type: "string" },
|
|
52
78
|
"tracking-click": {
|
|
53
79
|
type: "boolean",
|
|
54
80
|
default: false
|
|
@@ -67,16 +93,17 @@ const emailArgs = {
|
|
|
67
93
|
},
|
|
68
94
|
"from": {
|
|
69
95
|
type: "string",
|
|
70
|
-
required: true
|
|
96
|
+
required: true,
|
|
97
|
+
valueHint: "address"
|
|
71
98
|
},
|
|
72
99
|
"headers": {
|
|
73
100
|
type: "string",
|
|
74
|
-
|
|
75
|
-
valueHint: "JSON"
|
|
101
|
+
valueHint: "json"
|
|
76
102
|
},
|
|
77
103
|
"to": {
|
|
78
104
|
type: "string",
|
|
79
|
-
required: true
|
|
105
|
+
required: true,
|
|
106
|
+
valueHint: "addresses"
|
|
80
107
|
},
|
|
81
108
|
"subject": {
|
|
82
109
|
type: "string",
|
|
@@ -90,9 +117,18 @@ const emailArgs = {
|
|
|
90
117
|
type: "string",
|
|
91
118
|
default: ""
|
|
92
119
|
},
|
|
93
|
-
"cc": {
|
|
94
|
-
|
|
95
|
-
|
|
120
|
+
"cc": {
|
|
121
|
+
type: "string",
|
|
122
|
+
valueHint: "addresses"
|
|
123
|
+
},
|
|
124
|
+
"bcc": {
|
|
125
|
+
type: "string",
|
|
126
|
+
valueHint: "addresses"
|
|
127
|
+
},
|
|
128
|
+
"reply-to": {
|
|
129
|
+
type: "string",
|
|
130
|
+
valueHint: "address"
|
|
131
|
+
},
|
|
96
132
|
"attachments": {
|
|
97
133
|
type: "boolean",
|
|
98
134
|
alias: "a",
|
|
@@ -107,15 +143,6 @@ const emailArgs = {
|
|
|
107
143
|
valueHint: "name"
|
|
108
144
|
}
|
|
109
145
|
};
|
|
110
|
-
const getApiKey = (apiKey) => {
|
|
111
|
-
const { MAILCHANNELS_API_KEY } = process.env;
|
|
112
|
-
const key = MAILCHANNELS_API_KEY || apiKey;
|
|
113
|
-
if (!key) {
|
|
114
|
-
console.error("[Emails] Missing API key. Provide it using the '--api-key' flag or set the 'MAILCHANNELS_API_KEY' environment variable.");
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
return key;
|
|
118
|
-
};
|
|
119
146
|
const parseAttachments = async (rawAttachments) => {
|
|
120
147
|
if (!rawAttachments) return;
|
|
121
148
|
if (process.stdin.isTTY) {
|
|
@@ -196,7 +223,7 @@ const parseOptions = async (args) => {
|
|
|
196
223
|
var send_default = defineCommand({
|
|
197
224
|
meta: {
|
|
198
225
|
name: "send",
|
|
199
|
-
description: "Send an email
|
|
226
|
+
description: "Send an email"
|
|
200
227
|
},
|
|
201
228
|
args: {
|
|
202
229
|
...emailArgs,
|
|
@@ -221,7 +248,7 @@ var send_default = defineCommand({
|
|
|
221
248
|
var queue_default = defineCommand({
|
|
222
249
|
meta: {
|
|
223
250
|
name: "queue",
|
|
224
|
-
description: "Queue an email to be sent
|
|
251
|
+
description: "Queue an email to be sent"
|
|
225
252
|
},
|
|
226
253
|
args: emailArgs,
|
|
227
254
|
async run({ args }) {
|
|
@@ -240,7 +267,7 @@ var queue_default = defineCommand({
|
|
|
240
267
|
var emails_default = defineCommand({
|
|
241
268
|
meta: {
|
|
242
269
|
name: "emails",
|
|
243
|
-
description: "Commands for sending emails
|
|
270
|
+
description: "Commands for sending emails"
|
|
244
271
|
},
|
|
245
272
|
args: {},
|
|
246
273
|
subCommands: {
|
|
@@ -248,6 +275,431 @@ var emails_default = defineCommand({
|
|
|
248
275
|
queue: queue_default
|
|
249
276
|
}
|
|
250
277
|
});
|
|
278
|
+
const isValidEntry = (value) => {
|
|
279
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
280
|
+
const entry = value;
|
|
281
|
+
return typeof entry.recipient === "string" && (entry.types === void 0 || Array.isArray(entry.types));
|
|
282
|
+
};
|
|
283
|
+
const parseEntries = async (rawEntries, recipient, types, notes) => {
|
|
284
|
+
if (rawEntries) {
|
|
285
|
+
if (recipient) console.warn("[Suppressions] Warning: both '--entries' and '--recipient' are provided. Using '--entries'.");
|
|
286
|
+
if (types || notes) console.warn("[Suppressions] Warning: '--entries' ignores '--types' and '--notes'.");
|
|
287
|
+
if (process.stdin.isTTY) {
|
|
288
|
+
console.error("[Suppressions] '--entries' requires JSON input piped to stdin.");
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
let input = "";
|
|
292
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
293
|
+
let parsed;
|
|
294
|
+
try {
|
|
295
|
+
parsed = JSON.parse(input);
|
|
296
|
+
} catch {
|
|
297
|
+
console.error("[Suppressions] JSON input for '--entries' is not valid.");
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
if (!Array.isArray(parsed) || !parsed.every(isValidEntry)) {
|
|
301
|
+
console.error("[Suppressions] '--entries' must be a JSON array of suppression entries.");
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
return parsed;
|
|
305
|
+
}
|
|
306
|
+
if (!recipient) {
|
|
307
|
+
console.error("[Suppressions] Either '--entries' or '--recipient' is required.");
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
const entry = { recipient };
|
|
311
|
+
const parsedTypes = types?.split(",").map((type) => type.trim());
|
|
312
|
+
if (parsedTypes?.length) entry.types = parsedTypes;
|
|
313
|
+
if (notes) entry.notes = notes;
|
|
314
|
+
return [entry];
|
|
315
|
+
};
|
|
316
|
+
var create_default$1 = defineCommand({
|
|
317
|
+
meta: {
|
|
318
|
+
name: "create",
|
|
319
|
+
description: "Create suppression entries"
|
|
320
|
+
},
|
|
321
|
+
args: {
|
|
322
|
+
...sharedArgs,
|
|
323
|
+
"recipient": {
|
|
324
|
+
type: "string",
|
|
325
|
+
valueHint: "address"
|
|
326
|
+
},
|
|
327
|
+
"types": {
|
|
328
|
+
type: "string",
|
|
329
|
+
valueHint: "types"
|
|
330
|
+
},
|
|
331
|
+
"notes": { type: "string" },
|
|
332
|
+
"entries": {
|
|
333
|
+
type: "boolean",
|
|
334
|
+
default: false
|
|
335
|
+
},
|
|
336
|
+
"add-to-sub-accounts": {
|
|
337
|
+
type: "boolean",
|
|
338
|
+
default: false
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
async run({ args }) {
|
|
342
|
+
const apiKey = getApiKey(args["api-key"]);
|
|
343
|
+
const client = new MailChannelsClient(apiKey);
|
|
344
|
+
const suppressions = new Suppressions(client);
|
|
345
|
+
const entries = await parseEntries(args["entries"], args["recipient"], args["types"], args["notes"]);
|
|
346
|
+
const { error } = await suppressions.create(entries, { addToSubAccounts: args["add-to-sub-accounts"] });
|
|
347
|
+
if (error) {
|
|
348
|
+
console.error(`[Suppressions] ${error.message}`);
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
console.info("[Suppressions] Suppression entries created successfully.");
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
var delete_default = defineCommand({
|
|
355
|
+
meta: {
|
|
356
|
+
name: "delete",
|
|
357
|
+
description: "Delete a suppression entry"
|
|
358
|
+
},
|
|
359
|
+
args: {
|
|
360
|
+
...sharedArgs,
|
|
361
|
+
recipient: {
|
|
362
|
+
type: "string",
|
|
363
|
+
required: true,
|
|
364
|
+
valueHint: "address"
|
|
365
|
+
},
|
|
366
|
+
source: {
|
|
367
|
+
type: "string",
|
|
368
|
+
valueHint: "source"
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
async run({ args }) {
|
|
372
|
+
const apiKey = getApiKey(args["api-key"]);
|
|
373
|
+
const client = new MailChannelsClient(apiKey);
|
|
374
|
+
const { error } = await new Suppressions(client).delete(args["recipient"], args["source"]);
|
|
375
|
+
if (error) {
|
|
376
|
+
console.error(`[Suppressions] ${error.message}`);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
console.info("[Suppressions] Suppression entry deleted successfully.");
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
const parsePagination = (limit, offset) => {
|
|
383
|
+
return {
|
|
384
|
+
limit: limit ? Number(limit) : void 0,
|
|
385
|
+
offset: offset ? Number(offset) : void 0
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
const escape = "\x1B";
|
|
389
|
+
const createStyle = (code, reset = 39) => (text) => {
|
|
390
|
+
return `${escape}[${code}m${text}${escape}[${reset}m`;
|
|
391
|
+
};
|
|
392
|
+
const styles = {
|
|
393
|
+
green: createStyle(32),
|
|
394
|
+
yellow: createStyle(33),
|
|
395
|
+
blue: createStyle(34),
|
|
396
|
+
underline: createStyle(4, 24)
|
|
397
|
+
};
|
|
398
|
+
const styleText = (format, text) => {
|
|
399
|
+
if (Array.isArray(format)) return format.reduceRight((styledText, currentFormat) => styles[currentFormat](styledText), text);
|
|
400
|
+
return styles[format](text);
|
|
401
|
+
};
|
|
402
|
+
const styleValue = (value) => {
|
|
403
|
+
if (Array.isArray(value)) return `[ ${value.map(styleValue).join(", ")} ]`;
|
|
404
|
+
if (value !== null && typeof value === "object") return `{ ${Object.entries(value).map(([key, nestedValue]) => `${key}: ${styleValue(nestedValue)}`).join(", ")} }`;
|
|
405
|
+
const text = String(value);
|
|
406
|
+
switch (typeof value) {
|
|
407
|
+
case "number":
|
|
408
|
+
case "boolean": return styleText("yellow", text);
|
|
409
|
+
case "string": return styleText("green", `'${text}'`);
|
|
410
|
+
default: return text;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
const camelCaseToWords = (text) => {
|
|
414
|
+
return text.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
|
|
415
|
+
};
|
|
416
|
+
const toWordsKeys = (record) => {
|
|
417
|
+
if (Array.isArray(record)) return record.map((item) => typeof item === "object" && item !== null ? toWordsKeys(item) : item);
|
|
418
|
+
return Object.fromEntries(Object.entries(record).map(([key, value]) => [camelCaseToWords(key), typeof value === "object" && value !== null ? toWordsKeys(value) : value]));
|
|
419
|
+
};
|
|
420
|
+
const separatorChar = "─";
|
|
421
|
+
const separatorLength = 64;
|
|
422
|
+
const markerStart = 2;
|
|
423
|
+
const separator = separatorChar.repeat(separatorLength);
|
|
424
|
+
const separatorWithIndex = (index) => {
|
|
425
|
+
const marker = ` [${index}] `;
|
|
426
|
+
return separator.slice(0, markerStart) + marker + separator.slice(markerStart + marker.length);
|
|
427
|
+
};
|
|
428
|
+
const tabulate = (record) => {
|
|
429
|
+
const entries = Object.entries(toWordsKeys(record));
|
|
430
|
+
const labelWidth = Math.max(...entries.map(([label]) => label.length));
|
|
431
|
+
return entries.map(([label, value]) => ` ${label.padEnd(labelWidth + 2)}${styleValue(value)}`).join("\n");
|
|
432
|
+
};
|
|
433
|
+
const tabulatedSections = (records) => {
|
|
434
|
+
const isArray = Array.isArray(records);
|
|
435
|
+
const arr = isArray ? records : [records];
|
|
436
|
+
return arr.map((record, i) => {
|
|
437
|
+
const content = tabulate(record);
|
|
438
|
+
return `\n${isArray ? separatorWithIndex(i) : separator}\n${content}${i < arr.length - 1 ? "" : "\n" + separator}`;
|
|
439
|
+
}).join("");
|
|
440
|
+
};
|
|
441
|
+
var list_default$1 = defineCommand({
|
|
442
|
+
meta: {
|
|
443
|
+
name: "list",
|
|
444
|
+
description: "List suppression entries"
|
|
445
|
+
},
|
|
446
|
+
args: {
|
|
447
|
+
...sharedArgs,
|
|
448
|
+
"recipient": {
|
|
449
|
+
type: "string",
|
|
450
|
+
valueHint: "address"
|
|
451
|
+
},
|
|
452
|
+
"source": {
|
|
453
|
+
type: "string",
|
|
454
|
+
valueHint: "source"
|
|
455
|
+
},
|
|
456
|
+
"created-after": {
|
|
457
|
+
type: "string",
|
|
458
|
+
valueHint: "datetime"
|
|
459
|
+
},
|
|
460
|
+
"created-before": {
|
|
461
|
+
type: "string",
|
|
462
|
+
valueHint: "datetime"
|
|
463
|
+
},
|
|
464
|
+
"limit": {
|
|
465
|
+
type: "string",
|
|
466
|
+
alias: "l",
|
|
467
|
+
valueHint: "number"
|
|
468
|
+
},
|
|
469
|
+
"offset": {
|
|
470
|
+
type: "string",
|
|
471
|
+
alias: "o",
|
|
472
|
+
valueHint: "number"
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
async run({ args }) {
|
|
476
|
+
const apiKey = getApiKey(args["api-key"]);
|
|
477
|
+
const client = new MailChannelsClient(apiKey);
|
|
478
|
+
const { data, error } = await new Suppressions(client).list({
|
|
479
|
+
recipient: args["recipient"],
|
|
480
|
+
source: args["source"],
|
|
481
|
+
createdAfter: args["created-after"],
|
|
482
|
+
createdBefore: args["created-before"],
|
|
483
|
+
...parsePagination(args["limit"], args["offset"])
|
|
484
|
+
});
|
|
485
|
+
if (error) {
|
|
486
|
+
console.error(`[Suppressions] ${error.message}`);
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
if (!data.length) {
|
|
490
|
+
console.info("[Suppressions] No suppression entries found.");
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
console.info("[Suppressions] Retrieved suppression entries:" + tabulatedSections(data));
|
|
494
|
+
console.info("[Suppressions] Total suppression entries retrieved:", data.length, `(with limit: ${styleText("yellow", args["limit"] ?? "1000")}, offset: ${styleText("yellow", args["offset"] ?? "0")})`);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
var suppressions_default = defineCommand({
|
|
498
|
+
meta: {
|
|
499
|
+
name: "suppressions",
|
|
500
|
+
description: "Commands for managing suppression entries"
|
|
501
|
+
},
|
|
502
|
+
args: {},
|
|
503
|
+
subCommands: {
|
|
504
|
+
create: create_default$1,
|
|
505
|
+
delete: delete_default,
|
|
506
|
+
list: list_default$1
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
var create_default = defineCommand({
|
|
510
|
+
meta: {
|
|
511
|
+
name: "create",
|
|
512
|
+
description: "Enroll a new webhook endpoint"
|
|
513
|
+
},
|
|
514
|
+
args: {
|
|
515
|
+
...sharedArgs,
|
|
516
|
+
endpoint: {
|
|
517
|
+
type: "string",
|
|
518
|
+
alias: "e",
|
|
519
|
+
required: true,
|
|
520
|
+
valueHint: "url"
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
async run({ args }) {
|
|
524
|
+
const apiKey = getApiKey(args["api-key"]);
|
|
525
|
+
const client = new MailChannelsClient(apiKey);
|
|
526
|
+
const { error } = await new Webhooks(client).create(args["endpoint"]);
|
|
527
|
+
if (error) {
|
|
528
|
+
console.error(`[Webhooks] ${error.message}`);
|
|
529
|
+
process.exit(1);
|
|
530
|
+
}
|
|
531
|
+
console.info("[Webhooks] Webhook created successfully.");
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
var delete_all_default = defineCommand({
|
|
535
|
+
meta: {
|
|
536
|
+
name: "delete-all",
|
|
537
|
+
description: "Delete all webhook endpoints"
|
|
538
|
+
},
|
|
539
|
+
args: { ...sharedArgs },
|
|
540
|
+
async run({ args }) {
|
|
541
|
+
const apiKey = getApiKey(args["api-key"]);
|
|
542
|
+
const client = new MailChannelsClient(apiKey);
|
|
543
|
+
const { error } = await new Webhooks(client).deleteAll();
|
|
544
|
+
if (error) {
|
|
545
|
+
console.error(`[Webhooks] ${error.message}`);
|
|
546
|
+
process.exit(1);
|
|
547
|
+
}
|
|
548
|
+
console.info("[Webhooks] All webhooks deleted successfully.");
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
var list_default = defineCommand({
|
|
552
|
+
meta: {
|
|
553
|
+
name: "list",
|
|
554
|
+
description: "List all webhook endpoints"
|
|
555
|
+
},
|
|
556
|
+
args: { ...sharedArgs },
|
|
557
|
+
async run({ args }) {
|
|
558
|
+
const apiKey = getApiKey(args["api-key"]);
|
|
559
|
+
const client = new MailChannelsClient(apiKey);
|
|
560
|
+
const { data, error } = await new Webhooks(client).list();
|
|
561
|
+
if (error) {
|
|
562
|
+
console.error(`[Webhooks] ${error.message}`);
|
|
563
|
+
process.exit(1);
|
|
564
|
+
}
|
|
565
|
+
if (!data.length) {
|
|
566
|
+
console.info("[Webhooks] No registered webhooks found.");
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
console.info("[Webhooks] Registered webhook endpoints:");
|
|
570
|
+
console.table(toWordsKeys(data));
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
var batches_default = defineCommand({
|
|
574
|
+
meta: {
|
|
575
|
+
name: "batches",
|
|
576
|
+
description: "Retrieve paged webhook batches"
|
|
577
|
+
},
|
|
578
|
+
args: {
|
|
579
|
+
...sharedArgs,
|
|
580
|
+
"created-after": {
|
|
581
|
+
type: "string",
|
|
582
|
+
valueHint: "datetime"
|
|
583
|
+
},
|
|
584
|
+
"created-before": {
|
|
585
|
+
type: "string",
|
|
586
|
+
valueHint: "datetime"
|
|
587
|
+
},
|
|
588
|
+
"statuses": {
|
|
589
|
+
type: "string",
|
|
590
|
+
alias: "s"
|
|
591
|
+
},
|
|
592
|
+
"webhook": {
|
|
593
|
+
type: "string",
|
|
594
|
+
alias: ["e", "endpoint"],
|
|
595
|
+
valueHint: "url"
|
|
596
|
+
},
|
|
597
|
+
"limit": {
|
|
598
|
+
type: "string",
|
|
599
|
+
alias: "l",
|
|
600
|
+
valueHint: "number"
|
|
601
|
+
},
|
|
602
|
+
"offset": {
|
|
603
|
+
type: "string",
|
|
604
|
+
alias: "o",
|
|
605
|
+
valueHint: "number"
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
async run({ args }) {
|
|
609
|
+
const apiKey = getApiKey(args["api-key"]);
|
|
610
|
+
const client = new MailChannelsClient(apiKey);
|
|
611
|
+
const { data, error } = await new Webhooks(client).batches({
|
|
612
|
+
createdAfter: args["created-after"],
|
|
613
|
+
createdBefore: args["created-before"],
|
|
614
|
+
statuses: args["statuses"]?.split(",").map((s) => s.trim()),
|
|
615
|
+
webhook: args["webhook"],
|
|
616
|
+
...parsePagination(args["limit"], args["offset"])
|
|
617
|
+
});
|
|
618
|
+
if (error) {
|
|
619
|
+
console.error(`[Webhooks] ${error.message}`);
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
622
|
+
if (!data.length) {
|
|
623
|
+
console.info("[Webhooks] No webhook batches found.");
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
console.info("[Webhooks] Retrieved webhook batches:" + tabulatedSections(data.map((batch) => ({
|
|
627
|
+
...batch,
|
|
628
|
+
duration: batch.duration ? `${batch.duration.value} ${batch.duration.unit}` : ""
|
|
629
|
+
}))));
|
|
630
|
+
console.info("[Webhooks] Total batches retrieved:", data.length, `(with limit: ${styleText("yellow", args["limit"] ?? "500")}, offset: ${styleText("yellow", args["offset"] ?? "0")})`);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
var resend_batch_default = defineCommand({
|
|
634
|
+
meta: {
|
|
635
|
+
name: "resend-batch",
|
|
636
|
+
description: "Resend a webhook batch"
|
|
637
|
+
},
|
|
638
|
+
args: {
|
|
639
|
+
...sharedArgs,
|
|
640
|
+
"batch-id": {
|
|
641
|
+
type: "string",
|
|
642
|
+
alias: "b",
|
|
643
|
+
required: true,
|
|
644
|
+
valueHint: "id"
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
async run({ args }) {
|
|
648
|
+
const apiKey = getApiKey(args["api-key"]);
|
|
649
|
+
const client = new MailChannelsClient(apiKey);
|
|
650
|
+
const { data, error } = await new Webhooks(client).resendBatch(Number(args["batch-id"]));
|
|
651
|
+
if (error) {
|
|
652
|
+
console.error(`[Webhooks] ${error.message}`);
|
|
653
|
+
process.exit(1);
|
|
654
|
+
}
|
|
655
|
+
console.info("[Webhooks] Webhook batch resent:" + tabulatedSections(data));
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
var validate_default = defineCommand({
|
|
659
|
+
meta: {
|
|
660
|
+
name: "validate",
|
|
661
|
+
description: "Validate enrolled webhooks"
|
|
662
|
+
},
|
|
663
|
+
args: {
|
|
664
|
+
...sharedArgs,
|
|
665
|
+
"request-id": {
|
|
666
|
+
type: "string",
|
|
667
|
+
alias: "r",
|
|
668
|
+
valueHint: "id"
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
async run({ args }) {
|
|
672
|
+
const apiKey = getApiKey(args["api-key"]);
|
|
673
|
+
const client = new MailChannelsClient(apiKey);
|
|
674
|
+
const { data, error } = await new Webhooks(client).validate(args["request-id"]);
|
|
675
|
+
if (error) {
|
|
676
|
+
console.error(`[Webhooks] ${error.message}`);
|
|
677
|
+
process.exit(1);
|
|
678
|
+
}
|
|
679
|
+
console.info("[Webhooks] Webhook validation result:");
|
|
680
|
+
console.table(toWordsKeys(data.results.map(({ result, webhook, response }) => ({
|
|
681
|
+
result,
|
|
682
|
+
webhook,
|
|
683
|
+
status: response?.status
|
|
684
|
+
}))));
|
|
685
|
+
console.info("[Webhooks] Total webhooks validated:", data.results.length, "all passed?:", data.allPassed);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
var webhooks_default = defineCommand({
|
|
689
|
+
meta: {
|
|
690
|
+
name: "webhooks",
|
|
691
|
+
description: "Commands for managing webhooks"
|
|
692
|
+
},
|
|
693
|
+
args: {},
|
|
694
|
+
subCommands: {
|
|
695
|
+
create: create_default,
|
|
696
|
+
"delete-all": delete_all_default,
|
|
697
|
+
list: list_default,
|
|
698
|
+
batches: batches_default,
|
|
699
|
+
"resend-batch": resend_batch_default,
|
|
700
|
+
validate: validate_default
|
|
701
|
+
}
|
|
702
|
+
});
|
|
251
703
|
const main = defineCommand({
|
|
252
704
|
meta: {
|
|
253
705
|
name,
|
|
@@ -256,11 +708,14 @@ const main = defineCommand({
|
|
|
256
708
|
},
|
|
257
709
|
subCommands: {
|
|
258
710
|
simulate: simulate_default,
|
|
259
|
-
emails: emails_default
|
|
711
|
+
emails: emails_default,
|
|
712
|
+
suppressions: suppressions_default,
|
|
713
|
+
webhooks: webhooks_default
|
|
260
714
|
},
|
|
261
715
|
setup() {
|
|
262
716
|
const LOGGER_NAME = "[MailChannels-CLI]";
|
|
263
717
|
console.info = console.info.bind(console, LOGGER_NAME);
|
|
718
|
+
console.warn = console.warn.bind(console, LOGGER_NAME);
|
|
264
719
|
console.error = console.error.bind(console, LOGGER_NAME);
|
|
265
720
|
}
|
|
266
721
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { EmailsQueueResponse, EmailsSendOptions, EmailsSendResponse, MailChannelsClientOptions } from "../../_chunks/mailchannels.mjs";
|
|
2
|
-
import { Transport
|
|
2
|
+
import { Transport, TransportOptions, Transporter } from "nodemailer";
|
|
3
3
|
import MimeNode from "nodemailer/lib/mime-node";
|
|
4
|
-
type MailChannelsTransportSendOptions = Pick<EmailsSendOptions, "campaignId" | "tracking" | "transactional" | "unsubscribe">;
|
|
5
|
-
type MailChannelsTransportSendMode = "sync" | "async";
|
|
6
|
-
interface MailChannelsTransportOptions<T extends MailChannelsTransportSendMode = "async"> extends MailChannelsClientOptions {
|
|
4
|
+
export type MailChannelsTransportSendOptions = Pick<EmailsSendOptions, "campaignId" | "tracking" | "transactional" | "unsubscribe">;
|
|
5
|
+
export type MailChannelsTransportSendMode = "sync" | "async";
|
|
6
|
+
export interface MailChannelsTransportOptions<T extends MailChannelsTransportSendMode = "async"> extends MailChannelsClientOptions {
|
|
7
7
|
/**
|
|
8
8
|
* The MailChannels Email API key.
|
|
9
9
|
*/
|
|
@@ -14,7 +14,7 @@ interface MailChannelsTransportOptions<T extends MailChannelsTransportSendMode =
|
|
|
14
14
|
*/
|
|
15
15
|
sendMode?: T;
|
|
16
16
|
}
|
|
17
|
-
interface MailChannelsTransportInfo<T extends MailChannelsTransportSendMode> {
|
|
17
|
+
export interface MailChannelsTransportInfo<T extends MailChannelsTransportSendMode> {
|
|
18
18
|
messageId: string | null;
|
|
19
19
|
accepted: string[];
|
|
20
20
|
rejected: string[];
|
|
@@ -22,12 +22,14 @@ interface MailChannelsTransportInfo<T extends MailChannelsTransportSendMode> {
|
|
|
22
22
|
response: (T extends "sync" ? EmailsSendResponse : EmailsQueueResponse) | null;
|
|
23
23
|
}
|
|
24
24
|
declare module "nodemailer" {
|
|
25
|
-
function createTransport<T>(transport: Transport<T> | TransportOptions, defaults?: TransportOptions): Transporter<T
|
|
25
|
+
function createTransport<T>(transport: Transport<T> | TransportOptions, defaults?: TransportOptions): Transporter<T>;
|
|
26
|
+
interface SendMailOptions {
|
|
27
|
+
mailchannels?: MailChannelsTransportSendOptions;
|
|
28
|
+
}
|
|
26
29
|
}
|
|
27
30
|
declare module "nodemailer/lib/mailer" {
|
|
28
31
|
interface Options {
|
|
29
32
|
mailchannels?: MailChannelsTransportSendOptions;
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
|
-
declare const mailchannelsTransport: <T extends MailChannelsTransportSendMode = "async">(options: MailChannelsTransportOptions<T>) => Transport
|
|
33
|
-
export { MailChannelsTransportInfo, MailChannelsTransportOptions, MailChannelsTransportSendMode, MailChannelsTransportSendOptions, mailchannelsTransport };
|
|
35
|
+
export declare const mailchannelsTransport: <T extends MailChannelsTransportSendMode = "async">(options: MailChannelsTransportOptions<T>) => Transport<MailChannelsTransportInfo<T>>;
|
|
@@ -5,9 +5,10 @@ const parseAddress = (address) => {
|
|
|
5
5
|
if (!address) return "";
|
|
6
6
|
if (Array.isArray(address)) return parseAddress(address[0]);
|
|
7
7
|
if (typeof address === "string") return address;
|
|
8
|
+
if (!address.address) return "";
|
|
8
9
|
return {
|
|
9
10
|
email: address.address,
|
|
10
|
-
name: address.name
|
|
11
|
+
name: address.name
|
|
11
12
|
};
|
|
12
13
|
};
|
|
13
14
|
const parseAddresses = (addresses) => {
|
|
@@ -41,7 +42,7 @@ const parseDkim = (dkim) => {
|
|
|
41
42
|
if ("keys" in dkim) throw new Error("Multiple DKIM signatures are not supported");
|
|
42
43
|
let privateKey;
|
|
43
44
|
if (typeof dkim.privateKey === "string") privateKey = dkim.privateKey;
|
|
44
|
-
else if (dkim.privateKey
|
|
45
|
+
else if (dkim.privateKey && "key" in dkim.privateKey && "passphrase" in dkim.privateKey) try {
|
|
45
46
|
privateKey = createPrivateKey({
|
|
46
47
|
key: dkim.privateKey.key,
|
|
47
48
|
passphrase: dkim.privateKey.passphrase
|
|
@@ -62,7 +63,8 @@ const parseDkim = (dkim) => {
|
|
|
62
63
|
const parseHeaders = (headers) => {
|
|
63
64
|
if (!headers) return;
|
|
64
65
|
if (Array.isArray(headers)) return headers.reduce((acc, { key, value }) => {
|
|
65
|
-
|
|
66
|
+
if (value == void 0) return acc;
|
|
67
|
+
acc[key] = value.toString();
|
|
66
68
|
return acc;
|
|
67
69
|
}, {});
|
|
68
70
|
return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, Array.isArray(value) ? value.join(",") : value && typeof value === "object" && "value" in value ? String(value.value) : String(value)]));
|
|
@@ -94,7 +96,10 @@ const mailchannelsTransport = (options) => {
|
|
|
94
96
|
messageId: null,
|
|
95
97
|
accepted: [],
|
|
96
98
|
rejected: [],
|
|
97
|
-
envelope: mail.message
|
|
99
|
+
envelope: mail.message?.getEnvelope() ?? {
|
|
100
|
+
from: false,
|
|
101
|
+
to: []
|
|
102
|
+
},
|
|
98
103
|
response: null
|
|
99
104
|
};
|
|
100
105
|
mail.normalize(async (err, data) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const createSimulator: (options?: {
|
|
1
|
+
export declare const createSimulator: (options?: {
|
|
2
2
|
host?: string;
|
|
3
3
|
port?: number;
|
|
4
4
|
silent?: boolean;
|
|
@@ -16,5 +16,4 @@ declare const createSimulator: (options?: {
|
|
|
16
16
|
host?: string;
|
|
17
17
|
port?: number;
|
|
18
18
|
}): Promise<string>;
|
|
19
|
-
};
|
|
20
|
-
export { createSimulator };
|
|
19
|
+
};
|