mailchannels-sdk 0.8.0 → 1.1.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/.agents/skills/mailchannels-js/SKILL.md +91 -0
- package/.agents/skills/mailchannels-js/resources/attachments.md +113 -0
- package/.agents/skills/mailchannels-js/resources/clients-and-transport.md +98 -0
- package/.agents/skills/mailchannels-js/resources/custom-headers.md +64 -0
- package/.agents/skills/mailchannels-js/resources/dkim.md +148 -0
- package/.agents/skills/mailchannels-js/resources/domain-checks.md +100 -0
- package/.agents/skills/mailchannels-js/resources/error-handling.md +142 -0
- package/.agents/skills/mailchannels-js/resources/metrics-and-usage.md +113 -0
- package/.agents/skills/mailchannels-js/resources/overview.md +91 -0
- package/.agents/skills/mailchannels-js/resources/sending.md +195 -0
- package/.agents/skills/mailchannels-js/resources/sub-accounts.md +129 -0
- package/.agents/skills/mailchannels-js/resources/suppressions.md +91 -0
- package/.agents/skills/mailchannels-js/resources/templates.md +97 -0
- package/.agents/skills/mailchannels-js/resources/testing.md +95 -0
- package/.agents/skills/mailchannels-js/resources/unsubscribe.md +73 -0
- package/.agents/skills/mailchannels-js/resources/webhooks.md +174 -0
- package/README.md +86 -9
- package/dist/_chunks/simulator.mjs +1 -1
- package/dist/mailchannels.d.mts +421 -367
- package/dist/mailchannels.mjs +463 -347
- package/package.json +10 -9
package/dist/mailchannels.mjs
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { $fetch } from "ofetch";
|
|
2
2
|
import { subtle } from "node:crypto";
|
|
3
3
|
import { Buffer } from "node:buffer";
|
|
4
|
-
import { basename } from "node:path";
|
|
5
|
-
import { readFile } from "node:fs/promises";
|
|
6
4
|
import mime from "mime";
|
|
7
|
-
var version = "
|
|
5
|
+
var version = "1.1.0";
|
|
8
6
|
var MailChannelsClient = class MailChannelsClient {
|
|
9
7
|
static DEFAULT_BASE_URL = "https://api.mailchannels.net";
|
|
10
8
|
static DEFAULT_TIMEOUT = 12e4;
|
|
@@ -70,10 +68,22 @@ var MailChannelsClient = class MailChannelsClient {
|
|
|
70
68
|
});
|
|
71
69
|
}
|
|
72
70
|
};
|
|
73
|
-
const
|
|
71
|
+
const STATUS_ERROR_TYPE_MAP = {
|
|
72
|
+
[400]: "invalid_request_error",
|
|
73
|
+
[401]: "authentication_error",
|
|
74
|
+
[403]: "permission_error",
|
|
75
|
+
[404]: "not_found",
|
|
76
|
+
[409]: "conflict_error",
|
|
77
|
+
[413]: "payload_too_large_error",
|
|
78
|
+
[422]: "unprocessable_entity_error",
|
|
79
|
+
[429]: "rate_limit_error",
|
|
80
|
+
[500]: "internal_server_error"
|
|
81
|
+
};
|
|
82
|
+
const createError = (message, statusCode = null, type) => {
|
|
74
83
|
return {
|
|
75
84
|
message,
|
|
76
|
-
statusCode
|
|
85
|
+
statusCode,
|
|
86
|
+
type
|
|
77
87
|
};
|
|
78
88
|
};
|
|
79
89
|
const getStatusError = (response, errors = {}) => {
|
|
@@ -83,15 +93,18 @@ const getStatusError = (response, errors = {}) => {
|
|
|
83
93
|
if (typeof payload === "string") details = payload;
|
|
84
94
|
else if (payload?.message) details = payload.message;
|
|
85
95
|
else if (Array.isArray(payload?.errors) && payload.errors?.length) details = payload.errors.join(", ");
|
|
86
|
-
return createError(details ? `${statusText} ${details}` : statusText, response.status ?? null);
|
|
96
|
+
return createError(details ? `${statusText} ${details}` : statusText, response.status ?? null, STATUS_ERROR_TYPE_MAP[response.status] || "api_error");
|
|
87
97
|
};
|
|
88
98
|
const getResultError = (e, fallback) => {
|
|
89
|
-
return createError(e instanceof Error ? e.message : fallback);
|
|
99
|
+
return createError(e instanceof Error ? e.message : fallback, null, "application_error");
|
|
100
|
+
};
|
|
101
|
+
const createValidationError = (message) => {
|
|
102
|
+
return createError(message, null, "validation_error");
|
|
90
103
|
};
|
|
91
104
|
const validatePagination = (pagination = {}) => {
|
|
92
105
|
const { limit, offset, max } = pagination;
|
|
93
|
-
if (typeof limit === "number" && (limit < 1 || max && limit > max)) return
|
|
94
|
-
if (typeof offset === "number" && offset < 0) return
|
|
106
|
+
if (typeof limit === "number" && (limit < 1 || max && limit > max)) return createValidationError("The limit value " + (max ? `must be between 1 and ${max}.` : "is invalid. Only positive values are allowed."));
|
|
107
|
+
if (typeof offset === "number" && offset < 0) return createValidationError("Offset must be greater than or equal to 0.");
|
|
95
108
|
return null;
|
|
96
109
|
};
|
|
97
110
|
const clean = (data) => {
|
|
@@ -147,7 +160,7 @@ const parseArrayRecipients = (recipients) => {
|
|
|
147
160
|
return filtered.length > 0 ? filtered : void 0;
|
|
148
161
|
};
|
|
149
162
|
const stripPemHeaders = (pem) => pem.replace(/-----[^-]+-----|\s|#.*$/gm, "");
|
|
150
|
-
const RESERVED_HEADER_NAMES = new Set([
|
|
163
|
+
const RESERVED_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
151
164
|
"authentication-results",
|
|
152
165
|
"bcc",
|
|
153
166
|
"cc",
|
|
@@ -243,7 +256,8 @@ const buildSendPayload = async (options) => {
|
|
|
243
256
|
const { from, html, text } = options;
|
|
244
257
|
const contentTypes = options.content ? new Set(options.content.map((item) => item.type.toLowerCase())) : void 0;
|
|
245
258
|
const parsedFrom = parseRecipient(from);
|
|
246
|
-
if (!parsedFrom || !parsedFrom.email) return "No sender provided. Use the
|
|
259
|
+
if (!parsedFrom || !parsedFrom.email) return "No sender provided. Use the 'from' option to specify a sender.";
|
|
260
|
+
if (!options.subject || typeof options.subject !== "string" || !options.subject.trim()) return "No subject provided. Use the 'subject' option to specify a subject.";
|
|
247
261
|
if (!text && !html && (!options.content || !options.content.length)) return "No email content provided";
|
|
248
262
|
if (html && contentTypes?.has("text/html")) return "Cannot provide both 'html' and a 'content' entry with type 'text/html'.";
|
|
249
263
|
if (text && contentTypes?.has("text/plain")) return "Cannot provide both 'text' and a 'content' entry with type 'text/plain'.";
|
|
@@ -337,7 +351,7 @@ var Emails = class {
|
|
|
337
351
|
let error = null;
|
|
338
352
|
const payload = await buildSendPayload(options);
|
|
339
353
|
if (typeof payload === "string") {
|
|
340
|
-
error =
|
|
354
|
+
error = createValidationError(payload);
|
|
341
355
|
return {
|
|
342
356
|
data: null,
|
|
343
357
|
error
|
|
@@ -410,80 +424,6 @@ const mapDkimKey = (key) => ({
|
|
|
410
424
|
status: key.status,
|
|
411
425
|
statusModifiedAt: key.status_modified_at
|
|
412
426
|
});
|
|
413
|
-
var Domains = class {
|
|
414
|
-
mailchannels;
|
|
415
|
-
dkim;
|
|
416
|
-
constructor(mailchannels) {
|
|
417
|
-
this.mailchannels = mailchannels;
|
|
418
|
-
this.dkim = new DomainsDkim(mailchannels);
|
|
419
|
-
}
|
|
420
|
-
async check(domain, options) {
|
|
421
|
-
let error = null;
|
|
422
|
-
if (!domain) {
|
|
423
|
-
error = createError("No domain provided.");
|
|
424
|
-
return {
|
|
425
|
-
data: null,
|
|
426
|
-
error
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
const dkimOptions = options?.dkim ? Array.isArray(options.dkim) ? options.dkim : [options.dkim] : void 0;
|
|
430
|
-
if (dkimOptions && dkimOptions.length > 10) {
|
|
431
|
-
error = createError("A maximum of 10 DKIM settings can be provided.");
|
|
432
|
-
return {
|
|
433
|
-
data: null,
|
|
434
|
-
error
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
if (dkimOptions?.find(({ privateKey, selector }) => privateKey && !selector)) {
|
|
438
|
-
error = createError("DKIM settings with a privateKey must also include a selector.");
|
|
439
|
-
return {
|
|
440
|
-
data: null,
|
|
441
|
-
error
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
const payload = {
|
|
445
|
-
dkim_settings: dkimOptions?.map((dkim) => ({
|
|
446
|
-
dkim_domain: dkim.domain,
|
|
447
|
-
dkim_private_key: dkim.privateKey ? stripPemHeaders(dkim.privateKey) : void 0,
|
|
448
|
-
dkim_selector: dkim.selector
|
|
449
|
-
})),
|
|
450
|
-
domain,
|
|
451
|
-
sender_id: options?.senderId
|
|
452
|
-
};
|
|
453
|
-
const response = await this.mailchannels.post("/tx/v1/check-domain", {
|
|
454
|
-
body: payload,
|
|
455
|
-
onResponseError: async ({ response }) => {
|
|
456
|
-
error = getStatusError(response, {
|
|
457
|
-
[400]: "Bad Request.",
|
|
458
|
-
[403]: "User does not have access to this feature."
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
}).catch((e) => {
|
|
462
|
-
error ||= getResultError(e, "Failed to check domain.");
|
|
463
|
-
return null;
|
|
464
|
-
});
|
|
465
|
-
if (!response) return {
|
|
466
|
-
data: null,
|
|
467
|
-
error
|
|
468
|
-
};
|
|
469
|
-
return {
|
|
470
|
-
data: clean({
|
|
471
|
-
dkim: response.check_results.dkim.map((dkimResults) => ({
|
|
472
|
-
domain: dkimResults.dkim_domain,
|
|
473
|
-
keyStatus: dkimResults.dkim_key_status,
|
|
474
|
-
selector: dkimResults.dkim_selector,
|
|
475
|
-
reason: dkimResults.reason,
|
|
476
|
-
verdict: dkimResults.verdict
|
|
477
|
-
})),
|
|
478
|
-
domainLockdown: response.check_results.domain_lockdown,
|
|
479
|
-
senderDomain: response.check_results.sender_domain,
|
|
480
|
-
spf: response.check_results.spf,
|
|
481
|
-
references: response.references
|
|
482
|
-
}),
|
|
483
|
-
error: null
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
427
|
var DomainsDkim = class {
|
|
488
428
|
mailchannels;
|
|
489
429
|
constructor(mailchannels) {
|
|
@@ -492,14 +432,14 @@ var DomainsDkim = class {
|
|
|
492
432
|
async create(domain, options) {
|
|
493
433
|
let error = null;
|
|
494
434
|
if (!domain) {
|
|
495
|
-
error =
|
|
435
|
+
error = createValidationError("No domain provided.");
|
|
496
436
|
return {
|
|
497
437
|
data: null,
|
|
498
438
|
error
|
|
499
439
|
};
|
|
500
440
|
}
|
|
501
441
|
if (!options.selector || options.selector.length > 63) {
|
|
502
|
-
error =
|
|
442
|
+
error = createValidationError("Selector must be between 1 and 63 characters.");
|
|
503
443
|
return {
|
|
504
444
|
data: null,
|
|
505
445
|
error
|
|
@@ -510,7 +450,7 @@ var DomainsDkim = class {
|
|
|
510
450
|
key_length: options.length,
|
|
511
451
|
selector: options.selector
|
|
512
452
|
};
|
|
513
|
-
const response = await this.mailchannels.post(`/tx/v1/domains/${domain}/dkim-keys`, {
|
|
453
|
+
const response = await this.mailchannels.post(`/tx/v1/domains/${encodeURIComponent(domain)}/dkim-keys`, {
|
|
514
454
|
body: payload,
|
|
515
455
|
onResponseError: async ({ response }) => {
|
|
516
456
|
error = getStatusError(response, {
|
|
@@ -534,14 +474,14 @@ var DomainsDkim = class {
|
|
|
534
474
|
async list(domain, options) {
|
|
535
475
|
let error = null;
|
|
536
476
|
if (!domain) {
|
|
537
|
-
error =
|
|
477
|
+
error = createValidationError("No domain provided.");
|
|
538
478
|
return {
|
|
539
479
|
data: null,
|
|
540
480
|
error
|
|
541
481
|
};
|
|
542
482
|
}
|
|
543
483
|
if (options?.selector && options.selector.length > 63) {
|
|
544
|
-
error =
|
|
484
|
+
error = createValidationError("Selector must be between 1 and 63 characters.");
|
|
545
485
|
return {
|
|
546
486
|
data: null,
|
|
547
487
|
error
|
|
@@ -562,7 +502,7 @@ var DomainsDkim = class {
|
|
|
562
502
|
limit: options?.limit,
|
|
563
503
|
include_dns_record: options?.includeDnsRecord
|
|
564
504
|
};
|
|
565
|
-
const response = await this.mailchannels.get(`/tx/v1/domains/${domain}/dkim-keys`, {
|
|
505
|
+
const response = await this.mailchannels.get(`/tx/v1/domains/${encodeURIComponent(domain)}/dkim-keys`, {
|
|
566
506
|
query: payload,
|
|
567
507
|
onResponseError: async ({ response }) => {
|
|
568
508
|
error = getStatusError(response, { [400]: "Bad Request." });
|
|
@@ -583,21 +523,21 @@ var DomainsDkim = class {
|
|
|
583
523
|
async updateStatus(domain, options) {
|
|
584
524
|
let error = null;
|
|
585
525
|
if (!domain) {
|
|
586
|
-
error =
|
|
526
|
+
error = createValidationError("No domain provided.");
|
|
587
527
|
return {
|
|
588
528
|
success: false,
|
|
589
529
|
error
|
|
590
530
|
};
|
|
591
531
|
}
|
|
592
532
|
if (!options.selector || options.selector.length > 63) {
|
|
593
|
-
error =
|
|
533
|
+
error = createValidationError("Selector must be between 1 and 63 characters.");
|
|
594
534
|
return {
|
|
595
535
|
success: false,
|
|
596
536
|
error
|
|
597
537
|
};
|
|
598
538
|
}
|
|
599
539
|
const payload = { status: options.status };
|
|
600
|
-
await this.mailchannels.patch(`/tx/v1/domains/${domain}/dkim-keys/${options.selector}`, {
|
|
540
|
+
await this.mailchannels.patch(`/tx/v1/domains/${encodeURIComponent(domain)}/dkim-keys/${encodeURIComponent(options.selector)}`, {
|
|
601
541
|
body: payload,
|
|
602
542
|
onResponseError: async ({ response }) => {
|
|
603
543
|
error = getStatusError(response, {
|
|
@@ -616,28 +556,28 @@ var DomainsDkim = class {
|
|
|
616
556
|
async rotate(domain, selector, options) {
|
|
617
557
|
let error = null;
|
|
618
558
|
if (!domain) {
|
|
619
|
-
error =
|
|
559
|
+
error = createValidationError("No domain provided.");
|
|
620
560
|
return {
|
|
621
561
|
data: null,
|
|
622
562
|
error
|
|
623
563
|
};
|
|
624
564
|
}
|
|
625
565
|
if (!selector || selector.length > 63) {
|
|
626
|
-
error =
|
|
566
|
+
error = createValidationError("Selector must be between 1 and 63 characters.");
|
|
627
567
|
return {
|
|
628
568
|
data: null,
|
|
629
569
|
error
|
|
630
570
|
};
|
|
631
571
|
}
|
|
632
572
|
if (!options.newKey.selector || options.newKey.selector.length > 63) {
|
|
633
|
-
error =
|
|
573
|
+
error = createValidationError("New key selector must be between 1 and 63 characters.");
|
|
634
574
|
return {
|
|
635
575
|
data: null,
|
|
636
576
|
error
|
|
637
577
|
};
|
|
638
578
|
}
|
|
639
579
|
const payload = { new_key: { selector: options.newKey.selector } };
|
|
640
|
-
const response = await this.mailchannels.post(`/tx/v1/domains/${domain}/dkim-keys/${selector}/rotate`, {
|
|
580
|
+
const response = await this.mailchannels.post(`/tx/v1/domains/${encodeURIComponent(domain)}/dkim-keys/${encodeURIComponent(selector)}/rotate`, {
|
|
641
581
|
body: payload,
|
|
642
582
|
onResponseError: async ({ response }) => {
|
|
643
583
|
error = getStatusError(response, {
|
|
@@ -663,6 +603,80 @@ var DomainsDkim = class {
|
|
|
663
603
|
};
|
|
664
604
|
}
|
|
665
605
|
};
|
|
606
|
+
var Domains = class {
|
|
607
|
+
mailchannels;
|
|
608
|
+
dkim;
|
|
609
|
+
constructor(mailchannels) {
|
|
610
|
+
this.mailchannels = mailchannels;
|
|
611
|
+
this.dkim = new DomainsDkim(mailchannels);
|
|
612
|
+
}
|
|
613
|
+
async check(domain, options) {
|
|
614
|
+
let error = null;
|
|
615
|
+
if (!domain) {
|
|
616
|
+
error = createValidationError("No domain provided.");
|
|
617
|
+
return {
|
|
618
|
+
data: null,
|
|
619
|
+
error
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
const dkimOptions = options?.dkim ? Array.isArray(options.dkim) ? options.dkim : [options.dkim] : void 0;
|
|
623
|
+
if (dkimOptions && dkimOptions.length > 10) {
|
|
624
|
+
error = createValidationError("A maximum of 10 DKIM settings can be provided.");
|
|
625
|
+
return {
|
|
626
|
+
data: null,
|
|
627
|
+
error
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
if (dkimOptions?.find(({ privateKey, selector }) => privateKey && !selector)) {
|
|
631
|
+
error = createValidationError("DKIM settings with a privateKey must also include a selector.");
|
|
632
|
+
return {
|
|
633
|
+
data: null,
|
|
634
|
+
error
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
const payload = {
|
|
638
|
+
dkim_settings: dkimOptions?.map((dkim) => ({
|
|
639
|
+
dkim_domain: dkim.domain,
|
|
640
|
+
dkim_private_key: dkim.privateKey ? stripPemHeaders(dkim.privateKey) : void 0,
|
|
641
|
+
dkim_selector: dkim.selector
|
|
642
|
+
})),
|
|
643
|
+
domain,
|
|
644
|
+
sender_id: options?.senderId
|
|
645
|
+
};
|
|
646
|
+
const response = await this.mailchannels.post("/tx/v1/check-domain", {
|
|
647
|
+
body: payload,
|
|
648
|
+
onResponseError: async ({ response }) => {
|
|
649
|
+
error = getStatusError(response, {
|
|
650
|
+
[400]: "Bad Request.",
|
|
651
|
+
[403]: "User does not have access to this feature."
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}).catch((e) => {
|
|
655
|
+
error ||= getResultError(e, "Failed to check domain.");
|
|
656
|
+
return null;
|
|
657
|
+
});
|
|
658
|
+
if (!response) return {
|
|
659
|
+
data: null,
|
|
660
|
+
error
|
|
661
|
+
};
|
|
662
|
+
return {
|
|
663
|
+
data: clean({
|
|
664
|
+
dkim: response.check_results.dkim.map((dkimResults) => ({
|
|
665
|
+
domain: dkimResults.dkim_domain,
|
|
666
|
+
keyStatus: dkimResults.dkim_key_status,
|
|
667
|
+
selector: dkimResults.dkim_selector,
|
|
668
|
+
reason: dkimResults.reason,
|
|
669
|
+
verdict: dkimResults.verdict
|
|
670
|
+
})),
|
|
671
|
+
domainLockdown: response.check_results.domain_lockdown,
|
|
672
|
+
senderDomain: response.check_results.sender_domain,
|
|
673
|
+
spf: response.check_results.spf,
|
|
674
|
+
references: response.references
|
|
675
|
+
}),
|
|
676
|
+
error: null
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
};
|
|
666
680
|
const HMAC_SHA256 = {
|
|
667
681
|
name: "HMAC",
|
|
668
682
|
hash: "SHA-256"
|
|
@@ -735,6 +749,35 @@ async function isValidWebhook(options) {
|
|
|
735
749
|
const key = await subtle.importKey(format, publicKeyBuffer, ED25519, false, ["verify"]);
|
|
736
750
|
return subtle.verify(ED25519.name, key, webhookSignatureBuffer, encoder.encode(signingString));
|
|
737
751
|
}
|
|
752
|
+
const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
|
|
753
|
+
const ISO_DATE_ONLY_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
754
|
+
const formatDateInput = (value) => {
|
|
755
|
+
if (value instanceof Date) {
|
|
756
|
+
if (Number.isNaN(value.getTime())) return null;
|
|
757
|
+
return value.toISOString();
|
|
758
|
+
}
|
|
759
|
+
if (typeof value === "string") {
|
|
760
|
+
if (!ISO_DATE_REGEX.test(value) && !ISO_DATE_ONLY_REGEX.test(value) || Number.isNaN(Date.parse(value))) return null;
|
|
761
|
+
if (ISO_DATE_REGEX.test(value)) return new Date(value).toISOString();
|
|
762
|
+
}
|
|
763
|
+
return value;
|
|
764
|
+
};
|
|
765
|
+
const parseDateInputs = (options) => {
|
|
766
|
+
const dates = {};
|
|
767
|
+
const keys = Object.keys(options);
|
|
768
|
+
for (const key of keys) {
|
|
769
|
+
const formatted = formatDateInput(options?.[key]);
|
|
770
|
+
if (formatted === null) return {
|
|
771
|
+
dates: null,
|
|
772
|
+
error: createValidationError(`The '${key}' value is not a valid date.`)
|
|
773
|
+
};
|
|
774
|
+
dates[key] = formatted;
|
|
775
|
+
}
|
|
776
|
+
return {
|
|
777
|
+
dates,
|
|
778
|
+
error: null
|
|
779
|
+
};
|
|
780
|
+
};
|
|
738
781
|
var Webhooks = class Webhooks {
|
|
739
782
|
mailchannels;
|
|
740
783
|
constructor(mailchannels) {
|
|
@@ -743,14 +786,14 @@ var Webhooks = class Webhooks {
|
|
|
743
786
|
async create(endpoint) {
|
|
744
787
|
let error = null;
|
|
745
788
|
if (!endpoint) {
|
|
746
|
-
error =
|
|
789
|
+
error = createValidationError("No endpoint provided.");
|
|
747
790
|
return {
|
|
748
791
|
success: false,
|
|
749
792
|
error
|
|
750
793
|
};
|
|
751
794
|
}
|
|
752
795
|
if (endpoint.length > 8e3) {
|
|
753
|
-
error =
|
|
796
|
+
error = createValidationError("The endpoint exceeds the maximum length of 8000 characters.");
|
|
754
797
|
return {
|
|
755
798
|
success: false,
|
|
756
799
|
error
|
|
@@ -827,7 +870,7 @@ var Webhooks = class Webhooks {
|
|
|
827
870
|
async validate(requestId) {
|
|
828
871
|
let error = null;
|
|
829
872
|
if (requestId && requestId.length > 28) {
|
|
830
|
-
error =
|
|
873
|
+
error = createValidationError("The request id should not exceed 28 characters.");
|
|
831
874
|
return {
|
|
832
875
|
data: null,
|
|
833
876
|
error
|
|
@@ -860,7 +903,7 @@ var Webhooks = class Webhooks {
|
|
|
860
903
|
static async verify(options) {
|
|
861
904
|
let error = null;
|
|
862
905
|
if (!await isValidWebhook(options).catch(() => false)) {
|
|
863
|
-
error =
|
|
906
|
+
error = createValidationError("Invalid webhook signature.");
|
|
864
907
|
return {
|
|
865
908
|
data: null,
|
|
866
909
|
error
|
|
@@ -869,7 +912,7 @@ var Webhooks = class Webhooks {
|
|
|
869
912
|
try {
|
|
870
913
|
const payload = JSON.parse(options.payload);
|
|
871
914
|
if (!Array.isArray(payload)) {
|
|
872
|
-
error =
|
|
915
|
+
error = createValidationError("Invalid webhook payload.");
|
|
873
916
|
return {
|
|
874
917
|
data: null,
|
|
875
918
|
error
|
|
@@ -894,7 +937,7 @@ var Webhooks = class Webhooks {
|
|
|
894
937
|
error: null
|
|
895
938
|
};
|
|
896
939
|
} catch {
|
|
897
|
-
error =
|
|
940
|
+
error = createValidationError("Invalid webhook payload.");
|
|
898
941
|
return {
|
|
899
942
|
data: null,
|
|
900
943
|
error
|
|
@@ -916,37 +959,37 @@ var Webhooks = class Webhooks {
|
|
|
916
959
|
};
|
|
917
960
|
if (options?.statuses && options.statuses.length > 6) return {
|
|
918
961
|
data: null,
|
|
919
|
-
error:
|
|
962
|
+
error: createValidationError("A maximum of 6 status filters can be provided.")
|
|
920
963
|
};
|
|
921
964
|
if (options?.statuses && new Set(options.statuses).size !== options.statuses.length) return {
|
|
922
965
|
data: null,
|
|
923
|
-
error:
|
|
966
|
+
error: createValidationError("Status filters must be unique.")
|
|
924
967
|
};
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
};
|
|
929
|
-
if (
|
|
968
|
+
const { dates, error: dateError } = parseDateInputs({
|
|
969
|
+
createdAfter: options?.createdAfter,
|
|
970
|
+
createdBefore: options?.createdBefore
|
|
971
|
+
});
|
|
972
|
+
if (!dates || dateError) return {
|
|
930
973
|
data: null,
|
|
931
|
-
error:
|
|
974
|
+
error: dateError
|
|
932
975
|
};
|
|
933
|
-
if (
|
|
934
|
-
const createdAfter = Date.parse(
|
|
935
|
-
const createdBefore = Date.parse(
|
|
976
|
+
if (dates.createdAfter && dates.createdBefore) {
|
|
977
|
+
const createdAfter = Date.parse(dates.createdAfter);
|
|
978
|
+
const createdBefore = Date.parse(dates.createdBefore);
|
|
936
979
|
const maxRangeMs = 744 * 60 * 60 * 1e3;
|
|
937
980
|
if (createdBefore <= createdAfter) return {
|
|
938
981
|
data: null,
|
|
939
|
-
error:
|
|
982
|
+
error: createValidationError("createdBefore must be later than createdAfter.")
|
|
940
983
|
};
|
|
941
984
|
if (createdBefore - createdAfter > maxRangeMs) return {
|
|
942
985
|
data: null,
|
|
943
|
-
error:
|
|
986
|
+
error: createValidationError("The time range between createdAfter and createdBefore must not exceed 31 days.")
|
|
944
987
|
};
|
|
945
988
|
}
|
|
946
989
|
const response = await this.mailchannels.get("/tx/v1/webhook-batch", {
|
|
947
990
|
query: {
|
|
948
|
-
created_after:
|
|
949
|
-
created_before:
|
|
991
|
+
created_after: dates.createdAfter,
|
|
992
|
+
created_before: dates.createdBefore,
|
|
950
993
|
statuses: options?.statuses,
|
|
951
994
|
webhook: options?.webhook,
|
|
952
995
|
limit: options?.limit,
|
|
@@ -979,7 +1022,7 @@ var Webhooks = class Webhooks {
|
|
|
979
1022
|
}
|
|
980
1023
|
async resendBatch(batchId) {
|
|
981
1024
|
let error = null;
|
|
982
|
-
const response = await this.mailchannels.post(`/tx/v1/webhook-batch/${batchId}/resend`, { onResponseError: async ({ response }) => {
|
|
1025
|
+
const response = await this.mailchannels.post(`/tx/v1/webhook-batch/${encodeURIComponent(batchId)}/resend`, { onResponseError: async ({ response }) => {
|
|
983
1026
|
error = getStatusError(response, {
|
|
984
1027
|
[400]: "Bad Request. The batch ID is invalid.",
|
|
985
1028
|
[404]: `The batch '${batchId}' is not found for the customer.`
|
|
@@ -1006,161 +1049,21 @@ var Webhooks = class Webhooks {
|
|
|
1006
1049
|
};
|
|
1007
1050
|
}
|
|
1008
1051
|
};
|
|
1009
|
-
var
|
|
1052
|
+
var SubAccountsApiKeys = class {
|
|
1010
1053
|
mailchannels;
|
|
1011
|
-
static COMPANY_PATTERN = /^.{3,128}$/;
|
|
1012
|
-
static HANDLE_PATTERN = /^[a-z0-9]{3,128}$/;
|
|
1013
1054
|
constructor(mailchannels) {
|
|
1014
1055
|
this.mailchannels = mailchannels;
|
|
1015
1056
|
}
|
|
1016
|
-
async create(
|
|
1017
|
-
let error = null;
|
|
1018
|
-
if (!SubAccounts.COMPANY_PATTERN.test(companyName)) {
|
|
1019
|
-
error = createError("Invalid company name. Company name must be between 3 and 128 characters.");
|
|
1020
|
-
return {
|
|
1021
|
-
data: null,
|
|
1022
|
-
error
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
if (handle) {
|
|
1026
|
-
if (!SubAccounts.HANDLE_PATTERN.test(handle)) {
|
|
1027
|
-
error = createError("Invalid handle. Sub-account handle must be between 3 and 128 characters and contain only lowercase letters and numbers.");
|
|
1028
|
-
return {
|
|
1029
|
-
data: null,
|
|
1030
|
-
error
|
|
1031
|
-
};
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
const response = await this.mailchannels.post("/tx/v1/sub-account", {
|
|
1035
|
-
body: {
|
|
1036
|
-
company_name: companyName,
|
|
1037
|
-
handle
|
|
1038
|
-
},
|
|
1039
|
-
onResponseError: async ({ response }) => {
|
|
1040
|
-
error = getStatusError(response, {
|
|
1041
|
-
[403]: "The parent account does not have permission to create sub-accounts.",
|
|
1042
|
-
[409]: `Sub-account with handle '${handle}' already exists.`
|
|
1043
|
-
});
|
|
1044
|
-
}
|
|
1045
|
-
}).catch((e) => {
|
|
1046
|
-
error ||= getResultError(e, "Failed to create sub-account.");
|
|
1047
|
-
return null;
|
|
1048
|
-
});
|
|
1049
|
-
if (!response) return {
|
|
1050
|
-
data: null,
|
|
1051
|
-
error
|
|
1052
|
-
};
|
|
1053
|
-
return {
|
|
1054
|
-
data: clean({
|
|
1055
|
-
companyName: response.company_name,
|
|
1056
|
-
enabled: response.enabled,
|
|
1057
|
-
handle: response.handle
|
|
1058
|
-
}),
|
|
1059
|
-
error: null
|
|
1060
|
-
};
|
|
1061
|
-
}
|
|
1062
|
-
async list(options) {
|
|
1063
|
-
let error = null;
|
|
1064
|
-
error = validatePagination({
|
|
1065
|
-
...options,
|
|
1066
|
-
max: 1e3
|
|
1067
|
-
});
|
|
1068
|
-
if (error) return {
|
|
1069
|
-
data: null,
|
|
1070
|
-
error
|
|
1071
|
-
};
|
|
1072
|
-
const response = await this.mailchannels.get("/tx/v1/sub-account", {
|
|
1073
|
-
query: options,
|
|
1074
|
-
onResponseError: async ({ response }) => {
|
|
1075
|
-
error = getStatusError(response);
|
|
1076
|
-
}
|
|
1077
|
-
}).catch((e) => {
|
|
1078
|
-
error ||= getResultError(e, "Failed to fetch sub-accounts.");
|
|
1079
|
-
return null;
|
|
1080
|
-
});
|
|
1081
|
-
if (!response) return {
|
|
1082
|
-
data: null,
|
|
1083
|
-
error
|
|
1084
|
-
};
|
|
1085
|
-
return {
|
|
1086
|
-
data: clean(response.map((account) => ({
|
|
1087
|
-
companyName: account.company_name,
|
|
1088
|
-
enabled: account.enabled,
|
|
1089
|
-
handle: account.handle
|
|
1090
|
-
}))),
|
|
1091
|
-
error: null
|
|
1092
|
-
};
|
|
1093
|
-
}
|
|
1094
|
-
async delete(handle) {
|
|
1057
|
+
async create(handle) {
|
|
1095
1058
|
let error = null;
|
|
1096
1059
|
if (!handle) {
|
|
1097
|
-
error =
|
|
1098
|
-
return {
|
|
1099
|
-
success: false,
|
|
1100
|
-
error
|
|
1101
|
-
};
|
|
1102
|
-
}
|
|
1103
|
-
await this.mailchannels.delete(`/tx/v1/sub-account/${handle}`, { onResponseError: async ({ response }) => {
|
|
1104
|
-
error = getStatusError(response);
|
|
1105
|
-
} }).catch((e) => {
|
|
1106
|
-
error ||= getResultError(e, "Failed to delete sub-account.");
|
|
1107
|
-
});
|
|
1108
|
-
return {
|
|
1109
|
-
success: !error,
|
|
1110
|
-
error
|
|
1111
|
-
};
|
|
1112
|
-
}
|
|
1113
|
-
async suspend(handle) {
|
|
1114
|
-
let error = null;
|
|
1115
|
-
if (!handle) {
|
|
1116
|
-
error = createError("No handle provided.");
|
|
1117
|
-
return {
|
|
1118
|
-
success: false,
|
|
1119
|
-
error
|
|
1120
|
-
};
|
|
1121
|
-
}
|
|
1122
|
-
await this.mailchannels.post(`/tx/v1/sub-account/${handle}/suspend`, { onResponseError: async ({ response }) => {
|
|
1123
|
-
error = getStatusError(response, { [404]: `The specified sub-account '${handle}' does not exist.` });
|
|
1124
|
-
} }).catch((e) => {
|
|
1125
|
-
error ||= getResultError(e, "Failed to suspend sub-account.");
|
|
1126
|
-
});
|
|
1127
|
-
return {
|
|
1128
|
-
success: !error,
|
|
1129
|
-
error
|
|
1130
|
-
};
|
|
1131
|
-
}
|
|
1132
|
-
async activate(handle) {
|
|
1133
|
-
let error = null;
|
|
1134
|
-
if (!handle) {
|
|
1135
|
-
error = createError("No handle provided.");
|
|
1136
|
-
return {
|
|
1137
|
-
success: false,
|
|
1138
|
-
error
|
|
1139
|
-
};
|
|
1140
|
-
}
|
|
1141
|
-
await this.mailchannels.post(`/tx/v1/sub-account/${handle}/activate`, { onResponseError: async ({ response }) => {
|
|
1142
|
-
error = getStatusError(response, {
|
|
1143
|
-
[403]: "The parent account does not have permission to activate the sub-account.",
|
|
1144
|
-
[404]: `The specified sub-account '${handle}' does not exist.`
|
|
1145
|
-
});
|
|
1146
|
-
} }).catch((e) => {
|
|
1147
|
-
error ||= getResultError(e, "Failed to activate sub-account.");
|
|
1148
|
-
});
|
|
1149
|
-
return {
|
|
1150
|
-
success: !error,
|
|
1151
|
-
error
|
|
1152
|
-
};
|
|
1153
|
-
}
|
|
1154
|
-
async createApiKey(handle) {
|
|
1155
|
-
let error = null;
|
|
1156
|
-
if (!handle) {
|
|
1157
|
-
error = createError("No handle provided.");
|
|
1060
|
+
error = createValidationError("No handle provided.");
|
|
1158
1061
|
return {
|
|
1159
1062
|
data: null,
|
|
1160
1063
|
error
|
|
1161
1064
|
};
|
|
1162
1065
|
}
|
|
1163
|
-
const response = await this.mailchannels.post(`/tx/v1/sub-account/${handle}/api-key`, { onResponseError: async ({ response }) => {
|
|
1066
|
+
const response = await this.mailchannels.post(`/tx/v1/sub-account/${encodeURIComponent(handle)}/api-key`, { onResponseError: async ({ response }) => {
|
|
1164
1067
|
error = getStatusError(response, {
|
|
1165
1068
|
[403]: "You can't create API keys for this sub-account.",
|
|
1166
1069
|
[404]: `Sub-account with handle '${handle}' not found.`,
|
|
@@ -1179,10 +1082,10 @@ var SubAccounts = class SubAccounts {
|
|
|
1179
1082
|
error: null
|
|
1180
1083
|
};
|
|
1181
1084
|
}
|
|
1182
|
-
async
|
|
1085
|
+
async list(handle, options) {
|
|
1183
1086
|
let error = null;
|
|
1184
1087
|
if (!handle) {
|
|
1185
|
-
error =
|
|
1088
|
+
error = createValidationError("No handle provided.");
|
|
1186
1089
|
return {
|
|
1187
1090
|
data: null,
|
|
1188
1091
|
error
|
|
@@ -1196,9 +1099,12 @@ var SubAccounts = class SubAccounts {
|
|
|
1196
1099
|
data: null,
|
|
1197
1100
|
error
|
|
1198
1101
|
};
|
|
1199
|
-
const response = await this.mailchannels.get(`/tx/v1/sub-account/${handle}/api-key`, {
|
|
1200
|
-
|
|
1201
|
-
|
|
1102
|
+
const response = await this.mailchannels.get(`/tx/v1/sub-account/${encodeURIComponent(handle)}/api-key`, {
|
|
1103
|
+
query: options,
|
|
1104
|
+
onResponseError: async ({ response }) => {
|
|
1105
|
+
error = getStatusError(response, { [404]: `Sub-account with handle '${handle}' not found.` });
|
|
1106
|
+
}
|
|
1107
|
+
}).catch((e) => {
|
|
1202
1108
|
error ||= getResultError(e, "Failed to fetch sub-account API keys.");
|
|
1203
1109
|
return null;
|
|
1204
1110
|
});
|
|
@@ -1214,16 +1120,16 @@ var SubAccounts = class SubAccounts {
|
|
|
1214
1120
|
error: null
|
|
1215
1121
|
};
|
|
1216
1122
|
}
|
|
1217
|
-
async
|
|
1123
|
+
async delete(handle, id) {
|
|
1218
1124
|
let error = null;
|
|
1219
1125
|
if (!handle) {
|
|
1220
|
-
error =
|
|
1126
|
+
error = createValidationError("No handle provided.");
|
|
1221
1127
|
return {
|
|
1222
1128
|
success: false,
|
|
1223
1129
|
error
|
|
1224
1130
|
};
|
|
1225
1131
|
}
|
|
1226
|
-
await this.mailchannels.delete(`/tx/v1/sub-account/${handle}/api-key/${id}`, { onResponseError: async ({ response }) => {
|
|
1132
|
+
await this.mailchannels.delete(`/tx/v1/sub-account/${encodeURIComponent(handle)}/api-key/${encodeURIComponent(id)}`, { onResponseError: async ({ response }) => {
|
|
1227
1133
|
error = getStatusError(response, { [400]: "Missing or invalid API key ID." });
|
|
1228
1134
|
} }).catch((e) => {
|
|
1229
1135
|
error ||= getResultError(e, "Failed to delete sub-account API key.");
|
|
@@ -1233,16 +1139,22 @@ var SubAccounts = class SubAccounts {
|
|
|
1233
1139
|
error
|
|
1234
1140
|
};
|
|
1235
1141
|
}
|
|
1236
|
-
|
|
1142
|
+
};
|
|
1143
|
+
var SubAccountsSmtpPasswords = class {
|
|
1144
|
+
mailchannels;
|
|
1145
|
+
constructor(mailchannels) {
|
|
1146
|
+
this.mailchannels = mailchannels;
|
|
1147
|
+
}
|
|
1148
|
+
async create(handle) {
|
|
1237
1149
|
let error = null;
|
|
1238
1150
|
if (!handle) {
|
|
1239
|
-
error =
|
|
1151
|
+
error = createValidationError("No handle provided.");
|
|
1240
1152
|
return {
|
|
1241
1153
|
data: null,
|
|
1242
1154
|
error
|
|
1243
1155
|
};
|
|
1244
1156
|
}
|
|
1245
|
-
const response = await this.mailchannels.post(`/tx/v1/sub-account/${handle}/smtp-password`, { onResponseError: async ({ response }) => {
|
|
1157
|
+
const response = await this.mailchannels.post(`/tx/v1/sub-account/${encodeURIComponent(handle)}/smtp-password`, { onResponseError: async ({ response }) => {
|
|
1246
1158
|
error = getStatusError(response, {
|
|
1247
1159
|
[403]: "You can't create SMTP passwords for this sub-account.",
|
|
1248
1160
|
[404]: `Sub-account with handle '${handle}' not found.`,
|
|
@@ -1265,16 +1177,16 @@ var SubAccounts = class SubAccounts {
|
|
|
1265
1177
|
error: null
|
|
1266
1178
|
};
|
|
1267
1179
|
}
|
|
1268
|
-
async
|
|
1180
|
+
async list(handle) {
|
|
1269
1181
|
let error = null;
|
|
1270
1182
|
if (!handle) {
|
|
1271
|
-
error =
|
|
1183
|
+
error = createValidationError("No handle provided.");
|
|
1272
1184
|
return {
|
|
1273
1185
|
data: null,
|
|
1274
1186
|
error
|
|
1275
1187
|
};
|
|
1276
1188
|
}
|
|
1277
|
-
const response = await this.mailchannels.get(`/tx/v1/sub-account/${handle}/smtp-password`, { onResponseError: async ({ response }) => {
|
|
1189
|
+
const response = await this.mailchannels.get(`/tx/v1/sub-account/${encodeURIComponent(handle)}/smtp-password`, { onResponseError: async ({ response }) => {
|
|
1278
1190
|
error = getStatusError(response, { [404]: `Sub-account with handle '${handle}' not found.` });
|
|
1279
1191
|
} }).catch((e) => {
|
|
1280
1192
|
error ||= getResultError(e, "Failed to fetch sub-account SMTP passwords.");
|
|
@@ -1293,16 +1205,16 @@ var SubAccounts = class SubAccounts {
|
|
|
1293
1205
|
error: null
|
|
1294
1206
|
};
|
|
1295
1207
|
}
|
|
1296
|
-
async
|
|
1208
|
+
async delete(handle, id) {
|
|
1297
1209
|
let error = null;
|
|
1298
1210
|
if (!handle) {
|
|
1299
|
-
error =
|
|
1211
|
+
error = createValidationError("No handle provided.");
|
|
1300
1212
|
return {
|
|
1301
1213
|
success: false,
|
|
1302
1214
|
error
|
|
1303
1215
|
};
|
|
1304
1216
|
}
|
|
1305
|
-
await this.mailchannels.delete(`/tx/v1/sub-account/${handle}/smtp-password/${id}`, { onResponseError: async ({ response }) => {
|
|
1217
|
+
await this.mailchannels.delete(`/tx/v1/sub-account/${encodeURIComponent(handle)}/smtp-password/${encodeURIComponent(id)}`, { onResponseError: async ({ response }) => {
|
|
1306
1218
|
error = getStatusError(response, { [400]: "Missing or invalid SMTP password ID." });
|
|
1307
1219
|
} }).catch((e) => {
|
|
1308
1220
|
error ||= getResultError(e, "Failed to delete sub-account SMTP password.");
|
|
@@ -1312,16 +1224,22 @@ var SubAccounts = class SubAccounts {
|
|
|
1312
1224
|
error
|
|
1313
1225
|
};
|
|
1314
1226
|
}
|
|
1315
|
-
|
|
1227
|
+
};
|
|
1228
|
+
var SubAccountsLimits = class {
|
|
1229
|
+
mailchannels;
|
|
1230
|
+
constructor(mailchannels) {
|
|
1231
|
+
this.mailchannels = mailchannels;
|
|
1232
|
+
}
|
|
1233
|
+
async get(handle) {
|
|
1316
1234
|
let error = null;
|
|
1317
1235
|
if (!handle) {
|
|
1318
|
-
error =
|
|
1236
|
+
error = createValidationError("No handle provided.");
|
|
1319
1237
|
return {
|
|
1320
1238
|
data: null,
|
|
1321
1239
|
error
|
|
1322
1240
|
};
|
|
1323
1241
|
}
|
|
1324
|
-
const response = await this.mailchannels.get(`/tx/v1/sub-account/${handle}/limit`, { onResponseError: async ({ response }) => {
|
|
1242
|
+
const response = await this.mailchannels.get(`/tx/v1/sub-account/${encodeURIComponent(handle)}/limit`, { onResponseError: async ({ response }) => {
|
|
1325
1243
|
error = getStatusError(response, { [404]: `Sub-account with handle '${handle}' not found.` });
|
|
1326
1244
|
} }).catch((e) => {
|
|
1327
1245
|
error ||= getResultError(e, "Failed to fetch sub-account limit.");
|
|
@@ -1336,24 +1254,24 @@ var SubAccounts = class SubAccounts {
|
|
|
1336
1254
|
error: null
|
|
1337
1255
|
};
|
|
1338
1256
|
}
|
|
1339
|
-
async
|
|
1257
|
+
async set(handle, options) {
|
|
1340
1258
|
let error = null;
|
|
1341
1259
|
if (!handle) {
|
|
1342
|
-
error =
|
|
1260
|
+
error = createValidationError("No handle provided.");
|
|
1343
1261
|
return {
|
|
1344
1262
|
success: false,
|
|
1345
1263
|
error
|
|
1346
1264
|
};
|
|
1347
1265
|
}
|
|
1348
|
-
if (
|
|
1349
|
-
error =
|
|
1266
|
+
if (options.sends < 0) {
|
|
1267
|
+
error = createValidationError("The sends value must be at least 0.");
|
|
1350
1268
|
return {
|
|
1351
1269
|
success: false,
|
|
1352
1270
|
error
|
|
1353
1271
|
};
|
|
1354
1272
|
}
|
|
1355
|
-
await this.mailchannels.put(`/tx/v1/sub-account/${handle}/limit`, {
|
|
1356
|
-
body:
|
|
1273
|
+
await this.mailchannels.put(`/tx/v1/sub-account/${encodeURIComponent(handle)}/limit`, {
|
|
1274
|
+
body: options,
|
|
1357
1275
|
onResponseError: async ({ response }) => {
|
|
1358
1276
|
error = getStatusError(response, {
|
|
1359
1277
|
[400]: "Bad Request.",
|
|
@@ -1368,16 +1286,16 @@ var SubAccounts = class SubAccounts {
|
|
|
1368
1286
|
error
|
|
1369
1287
|
};
|
|
1370
1288
|
}
|
|
1371
|
-
async
|
|
1289
|
+
async delete(handle) {
|
|
1372
1290
|
let error = null;
|
|
1373
1291
|
if (!handle) {
|
|
1374
|
-
error =
|
|
1292
|
+
error = createValidationError("No handle provided.");
|
|
1375
1293
|
return {
|
|
1376
1294
|
success: false,
|
|
1377
1295
|
error
|
|
1378
1296
|
};
|
|
1379
1297
|
}
|
|
1380
|
-
await this.mailchannels.delete(`/tx/v1/sub-account/${handle}/limit`, { onResponseError: async ({ response }) => {
|
|
1298
|
+
await this.mailchannels.delete(`/tx/v1/sub-account/${encodeURIComponent(handle)}/limit`, { onResponseError: async ({ response }) => {
|
|
1381
1299
|
error = getStatusError(response, { [404]: `Sub-account with handle '${handle}' not found.` });
|
|
1382
1300
|
} }).catch((e) => {
|
|
1383
1301
|
error ||= getResultError(e, "Failed to delete sub-account limit.");
|
|
@@ -1387,16 +1305,168 @@ var SubAccounts = class SubAccounts {
|
|
|
1387
1305
|
error
|
|
1388
1306
|
};
|
|
1389
1307
|
}
|
|
1308
|
+
};
|
|
1309
|
+
var SubAccounts = class SubAccounts {
|
|
1310
|
+
mailchannels;
|
|
1311
|
+
static COMPANY_PATTERN = /^.{3,128}$/;
|
|
1312
|
+
static HANDLE_PATTERN = /^[a-z0-9]{3,128}$/;
|
|
1313
|
+
apiKeys;
|
|
1314
|
+
smtpPasswords;
|
|
1315
|
+
limits;
|
|
1316
|
+
constructor(mailchannels) {
|
|
1317
|
+
this.mailchannels = mailchannels;
|
|
1318
|
+
this.apiKeys = new SubAccountsApiKeys(mailchannels);
|
|
1319
|
+
this.smtpPasswords = new SubAccountsSmtpPasswords(mailchannels);
|
|
1320
|
+
this.limits = new SubAccountsLimits(mailchannels);
|
|
1321
|
+
}
|
|
1322
|
+
async create(companyName, handle) {
|
|
1323
|
+
let error = null;
|
|
1324
|
+
if (!SubAccounts.COMPANY_PATTERN.test(companyName)) {
|
|
1325
|
+
error = createValidationError("Invalid company name. Company name must be between 3 and 128 characters.");
|
|
1326
|
+
return {
|
|
1327
|
+
data: null,
|
|
1328
|
+
error
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
if (handle) {
|
|
1332
|
+
if (!SubAccounts.HANDLE_PATTERN.test(handle)) {
|
|
1333
|
+
error = createValidationError("Invalid handle. Sub-account handle must be between 3 and 128 characters and contain only lowercase letters and numbers.");
|
|
1334
|
+
return {
|
|
1335
|
+
data: null,
|
|
1336
|
+
error
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
const response = await this.mailchannels.post("/tx/v1/sub-account", {
|
|
1341
|
+
body: {
|
|
1342
|
+
company_name: companyName,
|
|
1343
|
+
handle
|
|
1344
|
+
},
|
|
1345
|
+
onResponseError: async ({ response }) => {
|
|
1346
|
+
error = getStatusError(response, {
|
|
1347
|
+
[403]: "The parent account does not have permission to create sub-accounts.",
|
|
1348
|
+
[409]: `Sub-account with handle '${handle}' already exists.`
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
}).catch((e) => {
|
|
1352
|
+
error ||= getResultError(e, "Failed to create sub-account.");
|
|
1353
|
+
return null;
|
|
1354
|
+
});
|
|
1355
|
+
if (!response) return {
|
|
1356
|
+
data: null,
|
|
1357
|
+
error
|
|
1358
|
+
};
|
|
1359
|
+
return {
|
|
1360
|
+
data: clean({
|
|
1361
|
+
companyName: response.company_name,
|
|
1362
|
+
enabled: response.enabled,
|
|
1363
|
+
handle: response.handle
|
|
1364
|
+
}),
|
|
1365
|
+
error: null
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
async list(options) {
|
|
1369
|
+
let error = null;
|
|
1370
|
+
error = validatePagination({
|
|
1371
|
+
...options,
|
|
1372
|
+
max: 1e3
|
|
1373
|
+
});
|
|
1374
|
+
if (error) return {
|
|
1375
|
+
data: null,
|
|
1376
|
+
error
|
|
1377
|
+
};
|
|
1378
|
+
const response = await this.mailchannels.get("/tx/v1/sub-account", {
|
|
1379
|
+
query: options,
|
|
1380
|
+
onResponseError: async ({ response }) => {
|
|
1381
|
+
error = getStatusError(response);
|
|
1382
|
+
}
|
|
1383
|
+
}).catch((e) => {
|
|
1384
|
+
error ||= getResultError(e, "Failed to fetch sub-accounts.");
|
|
1385
|
+
return null;
|
|
1386
|
+
});
|
|
1387
|
+
if (!response) return {
|
|
1388
|
+
data: null,
|
|
1389
|
+
error
|
|
1390
|
+
};
|
|
1391
|
+
return {
|
|
1392
|
+
data: clean(response.map((account) => ({
|
|
1393
|
+
companyName: account.company_name,
|
|
1394
|
+
enabled: account.enabled,
|
|
1395
|
+
handle: account.handle
|
|
1396
|
+
}))),
|
|
1397
|
+
error: null
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
async delete(handle) {
|
|
1401
|
+
let error = null;
|
|
1402
|
+
if (!handle) {
|
|
1403
|
+
error = createValidationError("No handle provided.");
|
|
1404
|
+
return {
|
|
1405
|
+
success: false,
|
|
1406
|
+
error
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
await this.mailchannels.delete(`/tx/v1/sub-account/${encodeURIComponent(handle)}`, { onResponseError: async ({ response }) => {
|
|
1410
|
+
error = getStatusError(response);
|
|
1411
|
+
} }).catch((e) => {
|
|
1412
|
+
error ||= getResultError(e, "Failed to delete sub-account.");
|
|
1413
|
+
});
|
|
1414
|
+
return {
|
|
1415
|
+
success: !error,
|
|
1416
|
+
error
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
async suspend(handle) {
|
|
1420
|
+
let error = null;
|
|
1421
|
+
if (!handle) {
|
|
1422
|
+
error = createValidationError("No handle provided.");
|
|
1423
|
+
return {
|
|
1424
|
+
success: false,
|
|
1425
|
+
error
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
await this.mailchannels.post(`/tx/v1/sub-account/${encodeURIComponent(handle)}/suspend`, { onResponseError: async ({ response }) => {
|
|
1429
|
+
error = getStatusError(response, { [404]: `The specified sub-account '${handle}' does not exist.` });
|
|
1430
|
+
} }).catch((e) => {
|
|
1431
|
+
error ||= getResultError(e, "Failed to suspend sub-account.");
|
|
1432
|
+
});
|
|
1433
|
+
return {
|
|
1434
|
+
success: !error,
|
|
1435
|
+
error
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
async activate(handle) {
|
|
1439
|
+
let error = null;
|
|
1440
|
+
if (!handle) {
|
|
1441
|
+
error = createValidationError("No handle provided.");
|
|
1442
|
+
return {
|
|
1443
|
+
success: false,
|
|
1444
|
+
error
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
await this.mailchannels.post(`/tx/v1/sub-account/${encodeURIComponent(handle)}/activate`, { onResponseError: async ({ response }) => {
|
|
1448
|
+
error = getStatusError(response, {
|
|
1449
|
+
[403]: "The parent account does not have permission to activate the sub-account.",
|
|
1450
|
+
[404]: `The specified sub-account '${handle}' does not exist.`
|
|
1451
|
+
});
|
|
1452
|
+
} }).catch((e) => {
|
|
1453
|
+
error ||= getResultError(e, "Failed to activate sub-account.");
|
|
1454
|
+
});
|
|
1455
|
+
return {
|
|
1456
|
+
success: !error,
|
|
1457
|
+
error
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1390
1460
|
async getUsage(handle) {
|
|
1391
1461
|
let error = null;
|
|
1392
1462
|
if (!handle) {
|
|
1393
|
-
error =
|
|
1463
|
+
error = createValidationError("No handle provided.");
|
|
1394
1464
|
return {
|
|
1395
1465
|
data: null,
|
|
1396
1466
|
error
|
|
1397
1467
|
};
|
|
1398
1468
|
}
|
|
1399
|
-
const response = await this.mailchannels.get(`/tx/v1/sub-account/${handle}/usage`, { onResponseError: async ({ response }) => {
|
|
1469
|
+
const response = await this.mailchannels.get(`/tx/v1/sub-account/${encodeURIComponent(handle)}/usage`, { onResponseError: async ({ response }) => {
|
|
1400
1470
|
error = getStatusError(response, { [404]: `Sub-account with handle '${handle}' not found.` });
|
|
1401
1471
|
} }).catch((e) => {
|
|
1402
1472
|
error ||= getResultError(e, "Failed to fetch sub-account usage.");
|
|
@@ -1415,6 +1485,33 @@ var SubAccounts = class SubAccounts {
|
|
|
1415
1485
|
error: null
|
|
1416
1486
|
};
|
|
1417
1487
|
}
|
|
1488
|
+
createApiKey(...args) {
|
|
1489
|
+
return this.apiKeys.create(...args);
|
|
1490
|
+
}
|
|
1491
|
+
listApiKeys(...args) {
|
|
1492
|
+
return this.apiKeys.list(...args);
|
|
1493
|
+
}
|
|
1494
|
+
deleteApiKey(...args) {
|
|
1495
|
+
return this.apiKeys.delete(...args);
|
|
1496
|
+
}
|
|
1497
|
+
createSmtpPassword(...args) {
|
|
1498
|
+
return this.smtpPasswords.create(...args);
|
|
1499
|
+
}
|
|
1500
|
+
listSmtpPasswords(...args) {
|
|
1501
|
+
return this.smtpPasswords.list(...args);
|
|
1502
|
+
}
|
|
1503
|
+
deleteSmtpPassword(...args) {
|
|
1504
|
+
return this.smtpPasswords.delete(...args);
|
|
1505
|
+
}
|
|
1506
|
+
setLimit(...args) {
|
|
1507
|
+
return this.limits.set(...args);
|
|
1508
|
+
}
|
|
1509
|
+
getLimit(...args) {
|
|
1510
|
+
return this.limits.get(...args);
|
|
1511
|
+
}
|
|
1512
|
+
deleteLimit(...args) {
|
|
1513
|
+
return this.limits.delete(...args);
|
|
1514
|
+
}
|
|
1418
1515
|
};
|
|
1419
1516
|
const mapBucket = (bucket) => {
|
|
1420
1517
|
return {
|
|
@@ -1429,10 +1526,18 @@ var Metrics = class {
|
|
|
1429
1526
|
}
|
|
1430
1527
|
async engagement(options) {
|
|
1431
1528
|
let error = null;
|
|
1529
|
+
const { dates, error: dateError } = parseDateInputs({
|
|
1530
|
+
startTime: options?.startTime,
|
|
1531
|
+
endTime: options?.endTime
|
|
1532
|
+
});
|
|
1533
|
+
if (!dates || dateError) return {
|
|
1534
|
+
data: null,
|
|
1535
|
+
error: dateError
|
|
1536
|
+
};
|
|
1432
1537
|
const response = await this.mailchannels.get("/tx/v1/metrics/engagement", {
|
|
1433
1538
|
query: {
|
|
1434
|
-
start_time:
|
|
1435
|
-
end_time:
|
|
1539
|
+
start_time: dates.startTime,
|
|
1540
|
+
end_time: dates.endTime,
|
|
1436
1541
|
campaign_id: options?.campaignId,
|
|
1437
1542
|
interval: options?.interval
|
|
1438
1543
|
},
|
|
@@ -1467,10 +1572,18 @@ var Metrics = class {
|
|
|
1467
1572
|
}
|
|
1468
1573
|
async performance(options) {
|
|
1469
1574
|
let error = null;
|
|
1575
|
+
const { dates, error: dateError } = parseDateInputs({
|
|
1576
|
+
startTime: options?.startTime,
|
|
1577
|
+
endTime: options?.endTime
|
|
1578
|
+
});
|
|
1579
|
+
if (!dates || dateError) return {
|
|
1580
|
+
data: null,
|
|
1581
|
+
error: dateError
|
|
1582
|
+
};
|
|
1470
1583
|
const response = await this.mailchannels.get("/tx/v1/metrics/performance", {
|
|
1471
1584
|
query: {
|
|
1472
|
-
start_time:
|
|
1473
|
-
end_time:
|
|
1585
|
+
start_time: dates.startTime,
|
|
1586
|
+
end_time: dates.endTime,
|
|
1474
1587
|
campaign_id: options?.campaignId,
|
|
1475
1588
|
interval: options?.interval
|
|
1476
1589
|
},
|
|
@@ -1503,10 +1616,18 @@ var Metrics = class {
|
|
|
1503
1616
|
}
|
|
1504
1617
|
async recipientBehaviour(options) {
|
|
1505
1618
|
let error = null;
|
|
1619
|
+
const { dates, error: dateError } = parseDateInputs({
|
|
1620
|
+
startTime: options?.startTime,
|
|
1621
|
+
endTime: options?.endTime
|
|
1622
|
+
});
|
|
1623
|
+
if (!dates || dateError) return {
|
|
1624
|
+
data: null,
|
|
1625
|
+
error: dateError
|
|
1626
|
+
};
|
|
1506
1627
|
const response = await this.mailchannels.get("/tx/v1/metrics/recipient-behaviour", {
|
|
1507
1628
|
query: {
|
|
1508
|
-
start_time:
|
|
1509
|
-
end_time:
|
|
1629
|
+
start_time: dates.startTime,
|
|
1630
|
+
end_time: dates.endTime,
|
|
1510
1631
|
campaign_id: options?.campaignId,
|
|
1511
1632
|
interval: options?.interval
|
|
1512
1633
|
},
|
|
@@ -1537,10 +1658,18 @@ var Metrics = class {
|
|
|
1537
1658
|
}
|
|
1538
1659
|
async volume(options) {
|
|
1539
1660
|
let error = null;
|
|
1661
|
+
const { dates, error: dateError } = parseDateInputs({
|
|
1662
|
+
startTime: options?.startTime,
|
|
1663
|
+
endTime: options?.endTime
|
|
1664
|
+
});
|
|
1665
|
+
if (!dates || dateError) return {
|
|
1666
|
+
data: null,
|
|
1667
|
+
error: dateError
|
|
1668
|
+
};
|
|
1540
1669
|
const response = await this.mailchannels.get("/tx/v1/metrics/volume", {
|
|
1541
1670
|
query: {
|
|
1542
|
-
start_time:
|
|
1543
|
-
end_time:
|
|
1671
|
+
start_time: dates.startTime,
|
|
1672
|
+
end_time: dates.endTime,
|
|
1544
1673
|
campaign_id: options?.campaignId,
|
|
1545
1674
|
interval: options?.interval
|
|
1546
1675
|
},
|
|
@@ -1594,6 +1723,14 @@ var Metrics = class {
|
|
|
1594
1723
|
}
|
|
1595
1724
|
async senders(type, options) {
|
|
1596
1725
|
let error = null;
|
|
1726
|
+
const { dates, error: dateError } = parseDateInputs({
|
|
1727
|
+
startTime: options?.startTime,
|
|
1728
|
+
endTime: options?.endTime
|
|
1729
|
+
});
|
|
1730
|
+
if (!dates || dateError) return {
|
|
1731
|
+
data: null,
|
|
1732
|
+
error: dateError
|
|
1733
|
+
};
|
|
1597
1734
|
error = validatePagination({
|
|
1598
1735
|
...options,
|
|
1599
1736
|
max: 1e3
|
|
@@ -1602,10 +1739,10 @@ var Metrics = class {
|
|
|
1602
1739
|
data: null,
|
|
1603
1740
|
error
|
|
1604
1741
|
};
|
|
1605
|
-
const response = await this.mailchannels.get(`/tx/v1/metrics/senders/${type}`, {
|
|
1742
|
+
const response = await this.mailchannels.get(`/tx/v1/metrics/senders/${encodeURIComponent(type)}`, {
|
|
1606
1743
|
query: {
|
|
1607
|
-
start_time:
|
|
1608
|
-
end_time:
|
|
1744
|
+
start_time: dates.startTime,
|
|
1745
|
+
end_time: dates.endTime,
|
|
1609
1746
|
limit: options?.limit,
|
|
1610
1747
|
offset: options?.offset,
|
|
1611
1748
|
sort_order: options?.sortOrder
|
|
@@ -1643,7 +1780,7 @@ var Suppressions = class {
|
|
|
1643
1780
|
let error = null;
|
|
1644
1781
|
const { addToSubAccounts, entries } = options;
|
|
1645
1782
|
if (entries.length > 1e3) {
|
|
1646
|
-
error =
|
|
1783
|
+
error = createValidationError("The number of suppression entries must not exceed 1000.");
|
|
1647
1784
|
return {
|
|
1648
1785
|
success: false,
|
|
1649
1786
|
error
|
|
@@ -1676,7 +1813,7 @@ var Suppressions = class {
|
|
|
1676
1813
|
}
|
|
1677
1814
|
async delete(recipient, source) {
|
|
1678
1815
|
let error = null;
|
|
1679
|
-
await this.mailchannels.delete(`/tx/v1/suppression-list/recipients/${recipient}`, {
|
|
1816
|
+
await this.mailchannels.delete(`/tx/v1/suppression-list/recipients/${encodeURIComponent(recipient)}`, {
|
|
1680
1817
|
query: { source },
|
|
1681
1818
|
onResponseError: async ({ response }) => {
|
|
1682
1819
|
error = getStatusError(response, { [400]: "Bad Request." });
|
|
@@ -1692,12 +1829,20 @@ var Suppressions = class {
|
|
|
1692
1829
|
async list(options) {
|
|
1693
1830
|
let error = null;
|
|
1694
1831
|
if (options?.recipient && options.recipient.length > 255) {
|
|
1695
|
-
error =
|
|
1832
|
+
error = createValidationError("The recipient must not exceed 255 characters.");
|
|
1696
1833
|
return {
|
|
1697
1834
|
data: null,
|
|
1698
1835
|
error
|
|
1699
1836
|
};
|
|
1700
1837
|
}
|
|
1838
|
+
const { dates, error: dateError } = parseDateInputs({
|
|
1839
|
+
createdBefore: options?.createdBefore,
|
|
1840
|
+
createdAfter: options?.createdAfter
|
|
1841
|
+
});
|
|
1842
|
+
if (!dates || dateError) return {
|
|
1843
|
+
data: null,
|
|
1844
|
+
error: dateError
|
|
1845
|
+
};
|
|
1701
1846
|
error = validatePagination({
|
|
1702
1847
|
...options,
|
|
1703
1848
|
max: 1e3
|
|
@@ -1709,8 +1854,8 @@ var Suppressions = class {
|
|
|
1709
1854
|
const payload = {
|
|
1710
1855
|
recipient: options?.recipient,
|
|
1711
1856
|
source: options?.source,
|
|
1712
|
-
created_before:
|
|
1713
|
-
created_after:
|
|
1857
|
+
created_before: dates.createdBefore,
|
|
1858
|
+
created_after: dates.createdAfter,
|
|
1714
1859
|
limit: options?.limit,
|
|
1715
1860
|
offset: options?.offset
|
|
1716
1861
|
};
|
|
@@ -1758,41 +1903,12 @@ var Attachment = class Attachment {
|
|
|
1758
1903
|
disposition
|
|
1759
1904
|
};
|
|
1760
1905
|
}
|
|
1761
|
-
static async
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
});
|
|
1768
|
-
} catch (error) {
|
|
1769
|
-
throw new Error(`Unable to read attachment file: ${path}`, { cause: error });
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
static async fromUrl(url, options) {
|
|
1773
|
-
try {
|
|
1774
|
-
let contentType;
|
|
1775
|
-
const data = await $fetch(url, {
|
|
1776
|
-
responseType: "arrayBuffer",
|
|
1777
|
-
timeout: 12e4,
|
|
1778
|
-
onResponse: ({ response }) => {
|
|
1779
|
-
const contentTypeHeader = response.headers.get("content-type");
|
|
1780
|
-
if (contentTypeHeader) contentType = contentTypeHeader.split(";", 1)[0]?.trim();
|
|
1781
|
-
}
|
|
1782
|
-
});
|
|
1783
|
-
return Attachment.fromBytes(data, {
|
|
1784
|
-
type: contentType,
|
|
1785
|
-
filename: basename(new URL(url).pathname),
|
|
1786
|
-
...options
|
|
1787
|
-
});
|
|
1788
|
-
} catch (error) {
|
|
1789
|
-
throw new Error(`Unable to fetch attachment from URL: ${url}`, { cause: error });
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
static inlineFile(path, options) {
|
|
1793
|
-
return Attachment.fromFile(path, {
|
|
1794
|
-
...options,
|
|
1795
|
-
disposition: "inline"
|
|
1906
|
+
static async fromBlob(blob, options) {
|
|
1907
|
+
if (!(blob instanceof Blob)) throw new Error("Unable to create attachment: expected a Blob");
|
|
1908
|
+
const bytes = await blob.arrayBuffer();
|
|
1909
|
+
return Attachment.fromBytes(bytes, {
|
|
1910
|
+
type: blob.type,
|
|
1911
|
+
...options
|
|
1796
1912
|
});
|
|
1797
1913
|
}
|
|
1798
1914
|
};
|