mailchannels-sdk 0.8.0-1 → 1.0.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/LICENSE +1 -1
- package/README.md +23 -14
- package/dist/mailchannels.d.mts +58 -32
- package/dist/mailchannels.mjs +213 -99
- package/package.json +19 -17
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-

|
|
2
2
|
|
|
3
3
|
# MailChannels Node.js SDK
|
|
4
4
|
|
|
5
5
|
[![npm version][npm-version-src]][npm-version-href]
|
|
6
6
|
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
7
|
-
[![
|
|
7
|
+
[![Build Status][build-status-src]][build-status-href]
|
|
8
|
+
[![License][license-src]][license-href]
|
|
9
|
+
[![TypeScript][typescript-src]][typescript-href]
|
|
10
|
+
[![Node.js][node-src]][node-href]
|
|
8
11
|
|
|
9
|
-
> Built and tested against Email API `0.
|
|
12
|
+
> Built and tested against Email API `1.0.0`
|
|
10
13
|
|
|
11
14
|
Node.js SDK to integrate [MailChannels Email API](https://docs.mailchannels.net/email-api) into your JavaScript or TypeScript server-side applications.
|
|
12
15
|
|
|
@@ -14,7 +17,7 @@ Node.js SDK to integrate [MailChannels Email API](https://docs.mailchannels.net/
|
|
|
14
17
|
This library provides a simple way to interact with the [MailChannels Email API](https://docs.mailchannels.net/email-api). It is written in TypeScript and can be used in both JavaScript and TypeScript projects and in different runtimes.
|
|
15
18
|
<!-- #endregion overview -->
|
|
16
19
|
|
|
17
|
-
- [✨ Release Notes](CHANGELOG.md)
|
|
20
|
+
- [✨ Release Notes](https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js/src/HEAD/CHANGELOG.md)
|
|
18
21
|
- [📖 Documentation](https://mailchannels.yizack.com)
|
|
19
22
|
|
|
20
23
|
## Contents
|
|
@@ -109,7 +112,7 @@ This package includes a local MailChannels simulator you can run via the CLI. It
|
|
|
109
112
|
|
|
110
113
|
| API | Source |
|
|
111
114
|
| ----------- | -------------------------------------------------------------------------------------------------------------- |
|
|
112
|
-
| Email API | [`src/simulator/email-api.mjs`](https://
|
|
115
|
+
| Email API | [`src/simulator/email-api.mjs`](https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js/src/main/src/simulator/email-api.mjs) |
|
|
113
116
|
|
|
114
117
|
> [!IMPORTANT]
|
|
115
118
|
> The simulator approximates the MailChannels service for local development and testing. It is not a production implementation and may differ from the live service.
|
|
@@ -181,12 +184,11 @@ The next planned expansion is outbound webhook delivery so client applications c
|
|
|
181
184
|
|
|
182
185
|
## <a name="license">⚖️ License</a>
|
|
183
186
|
|
|
184
|
-
[MIT License](LICENSE)
|
|
187
|
+
[MIT License](https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js/src/HEAD/LICENSE)
|
|
185
188
|
|
|
186
189
|
## <a name="development">💻 Development</a>
|
|
187
190
|
|
|
188
|
-
|
|
189
|
-
<summary>Local development</summary>
|
|
191
|
+
Local development
|
|
190
192
|
|
|
191
193
|
```sh
|
|
192
194
|
# Install dependencies
|
|
@@ -205,21 +207,19 @@ pnpm test:watch
|
|
|
205
207
|
# Run typecheck
|
|
206
208
|
pnpm test:types
|
|
207
209
|
|
|
208
|
-
# Refresh API parity fixtures
|
|
210
|
+
# Refresh API parity fixtures, specs, and README version note
|
|
209
211
|
pnpm parity:fixtures
|
|
210
212
|
|
|
211
213
|
# Run the local simulator
|
|
212
214
|
pnpm simulate
|
|
213
215
|
|
|
214
216
|
# Run a playground script
|
|
215
|
-
|
|
217
|
+
pnpx jiti playground/emails/send.ts
|
|
216
218
|
|
|
217
219
|
# Release new version
|
|
218
220
|
pnpm release
|
|
219
221
|
```
|
|
220
222
|
|
|
221
|
-
</details>
|
|
222
|
-
|
|
223
223
|
<!-- Badges -->
|
|
224
224
|
[npm-version-src]: https://img.shields.io/npm/v/mailchannels-sdk.svg?style=flat&colorA=070a30&colorB=35a047
|
|
225
225
|
[npm-version-href]: https://npmjs.com/package/mailchannels-sdk
|
|
@@ -227,5 +227,14 @@ pnpm release
|
|
|
227
227
|
[npm-downloads-src]: https://img.shields.io/npm/dm/mailchannels-sdk.svg?style=flat&colorA=070a30&colorB=35a047
|
|
228
228
|
[npm-downloads-href]: https://npmjs.com/package/mailchannels-sdk
|
|
229
229
|
|
|
230
|
-
[
|
|
231
|
-
[
|
|
230
|
+
[build-status-src]: https://img.shields.io/bitbucket/pipelines/mailchannels/mailchannels-email-api-sdk-js/main.svg?style=flat&colorA=070a30
|
|
231
|
+
[build-status-href]: https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js/pipelines
|
|
232
|
+
|
|
233
|
+
[license-src]: https://img.shields.io/npm/l/mailchannels-sdk.svg?style=flat&colorA=070a30&colorB=35a047
|
|
234
|
+
[license-href]: https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js/src/HEAD/LICENSE
|
|
235
|
+
|
|
236
|
+
[typescript-src]: https://img.shields.io/badge/TypeScript-supported-35a047?style=flat&colorA=070a30
|
|
237
|
+
[typescript-href]: https://www.typescriptlang.org
|
|
238
|
+
|
|
239
|
+
[node-src]: https://img.shields.io/node/v/mailchannels-sdk.svg?style=flat&colorA=070a30&colorB=35a047
|
|
240
|
+
[node-href]: https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js/src/HEAD/package.json
|
package/dist/mailchannels.d.mts
CHANGED
|
@@ -20,19 +20,31 @@ interface MailChannelsClientOptions {
|
|
|
20
20
|
* @default false
|
|
21
21
|
*/
|
|
22
22
|
retry?: number | false;
|
|
23
|
+
/**
|
|
24
|
+
* Request timeout in milliseconds.
|
|
25
|
+
* Set to `false` or `0` to disable timeout handling.
|
|
26
|
+
* @default 120000
|
|
27
|
+
*/
|
|
28
|
+
timeout?: number | false;
|
|
29
|
+
/**
|
|
30
|
+
* Abort signal applied to requests made by the client.
|
|
31
|
+
*/
|
|
32
|
+
signal?: AbortSignal;
|
|
23
33
|
}
|
|
24
34
|
declare class MailChannelsClient {
|
|
25
35
|
#private;
|
|
26
36
|
private static readonly DEFAULT_BASE_URL;
|
|
37
|
+
private static readonly DEFAULT_TIMEOUT;
|
|
27
38
|
private readonly options;
|
|
28
39
|
constructor(key: string, options?: MailChannelsClientOptions);
|
|
29
|
-
protected _fetch<T>(path: string, options
|
|
40
|
+
protected _fetch<T>(path: string, options: FetchOptions<"json">): Promise<T>;
|
|
30
41
|
post<T>(path: string, options?: Omit<FetchOptions<"json">, "method">): Promise<T>;
|
|
31
42
|
get<T>(path: string, options?: Omit<FetchOptions<"json">, "method">): Promise<T>;
|
|
32
43
|
delete<T>(path: string, options?: Omit<FetchOptions<"json">, "method">): Promise<T>;
|
|
33
44
|
put<T>(path: string, options?: Omit<FetchOptions<"json">, "method">): Promise<T>;
|
|
34
45
|
patch<T>(path: string, options?: Omit<FetchOptions<"json">, "method">): Promise<T>;
|
|
35
46
|
}
|
|
47
|
+
type ErrorType = "invalid_request_error" | "authentication_error" | "permission_error" | "not_found" | "conflict_error" | "payload_too_large_error" | "unprocessable_entity_error" | "rate_limit_error" | "internal_server_error" | "validation_error" | "application_error" | "api_error";
|
|
36
48
|
interface ErrorResponse {
|
|
37
49
|
/**
|
|
38
50
|
* A human-readable description of the error.
|
|
@@ -44,6 +56,12 @@ interface ErrorResponse {
|
|
|
44
56
|
* This field is intended for diagnostic use only and should not be relied upon.
|
|
45
57
|
*/
|
|
46
58
|
statusCode: number | null;
|
|
59
|
+
/**
|
|
60
|
+
* A string identifier for the type of error.
|
|
61
|
+
*
|
|
62
|
+
* This field is intended for diagnostic use only and should not be relied upon.
|
|
63
|
+
*/
|
|
64
|
+
type: ErrorType;
|
|
47
65
|
}
|
|
48
66
|
interface SuccessResponse {
|
|
49
67
|
/**
|
|
@@ -96,7 +114,15 @@ interface EmailsSendAttachment {
|
|
|
96
114
|
/**
|
|
97
115
|
* The MIME type of the attachment.
|
|
98
116
|
*/
|
|
99
|
-
type
|
|
117
|
+
type?: string;
|
|
118
|
+
/**
|
|
119
|
+
* The `Content-ID` header value for inline attachments, referenced from HTML with `cid:`.
|
|
120
|
+
*/
|
|
121
|
+
contentId?: string;
|
|
122
|
+
/**
|
|
123
|
+
* The `Content-Disposition` header value for the attachment.
|
|
124
|
+
*/
|
|
125
|
+
disposition?: "attachment" | "inline";
|
|
100
126
|
}
|
|
101
127
|
interface EmailsSendTracking {
|
|
102
128
|
/**
|
|
@@ -211,7 +237,7 @@ interface EmailsSendOptionsBase {
|
|
|
211
237
|
/**
|
|
212
238
|
* An array of attachments to be sent with the email.
|
|
213
239
|
*/
|
|
214
|
-
attachments?: EmailsSendAttachment[];
|
|
240
|
+
attachments?: (EmailsSendAttachment | Promise<EmailsSendAttachment>)[];
|
|
215
241
|
/**
|
|
216
242
|
* The campaign identifier. If specified, this ID will be included in all relevant webhooks. It can be up to 48 UTF-8 characters long and must not contain spaces.
|
|
217
243
|
*/
|
|
@@ -438,7 +464,7 @@ type EmailsSendResponse = DataResponse<{
|
|
|
438
464
|
status: "sent" | "failed";
|
|
439
465
|
}[];
|
|
440
466
|
}>;
|
|
441
|
-
type
|
|
467
|
+
type EmailsQueueResponse = DataResponse<{
|
|
442
468
|
/**
|
|
443
469
|
* ISO 8601 timestamp when the request was queued for processing.
|
|
444
470
|
*/
|
|
@@ -448,6 +474,7 @@ type EmailsSendAsyncResponse = DataResponse<{
|
|
|
448
474
|
*/
|
|
449
475
|
requestId: string;
|
|
450
476
|
}>;
|
|
477
|
+
type EmailsSendAsyncResponse = EmailsQueueResponse;
|
|
451
478
|
declare class Emails {
|
|
452
479
|
protected mailchannels: MailChannelsClient;
|
|
453
480
|
constructor(mailchannels: MailChannelsClient);
|
|
@@ -478,7 +505,7 @@ declare class Emails {
|
|
|
478
505
|
* @example
|
|
479
506
|
* ```ts
|
|
480
507
|
* const mailchannels = new MailChannels('your-api-key')
|
|
481
|
-
* const { data, error } = await mailchannels.emails.
|
|
508
|
+
* const { data, error } = await mailchannels.emails.queue({
|
|
482
509
|
* to: 'to@example.com',
|
|
483
510
|
* from: 'from@example.com',
|
|
484
511
|
* subject: 'Test',
|
|
@@ -486,7 +513,11 @@ declare class Emails {
|
|
|
486
513
|
* })
|
|
487
514
|
* ```
|
|
488
515
|
*/
|
|
489
|
-
|
|
516
|
+
queue(options: EmailsSendOptions): Promise<EmailsQueueResponse>;
|
|
517
|
+
/**
|
|
518
|
+
* @deprecated Use `queue` instead.
|
|
519
|
+
*/
|
|
520
|
+
sendAsync(options: EmailsSendOptions): Promise<EmailsQueueResponse>;
|
|
490
521
|
}
|
|
491
522
|
interface DomainsDkimCreateOptions {
|
|
492
523
|
/**
|
|
@@ -578,10 +609,6 @@ interface DomainsCheckOptions {
|
|
|
578
609
|
* 6. If `selector` is present and `domain` is not, the domain will be taken from the domain field of the request.
|
|
579
610
|
*/
|
|
580
611
|
dkim?: DomainsCheck[] | DomainsCheck;
|
|
581
|
-
/**
|
|
582
|
-
* Domain used for sending emails. If `dkim` settings are not provided, or `dkim` settings are provided with no `domain`, the stored dkim settings for this domain will be used.
|
|
583
|
-
*/
|
|
584
|
-
domain: string;
|
|
585
612
|
/**
|
|
586
613
|
* Used exclusively for [Domain Lockdown](https://support.mailchannels.com/hc/en-us/articles/16918954360845-Secure-your-domain-name-against-spoofing-with-Domain-Lockdown) verification. If you're not using senderid to associate your domain with your account, you can disregard this field. The corresponding value is included in the `X-MailChannels-SenderId` header of emails sent via MailChannels.
|
|
587
614
|
*/
|
|
@@ -676,7 +703,7 @@ interface DomainsDkimListOptions {
|
|
|
676
703
|
}
|
|
677
704
|
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
678
705
|
type DomainsDkimListResponse = DataResponse<Optional<DomainsDkimKey, "dnsRecords">[]>;
|
|
679
|
-
interface
|
|
706
|
+
interface DomainsDkimUpdateStatusOptions {
|
|
680
707
|
/**
|
|
681
708
|
* Selector of the DKIM key pair to update. Must be a maximum of 63 characters.
|
|
682
709
|
*/
|
|
@@ -707,22 +734,22 @@ declare class Domains {
|
|
|
707
734
|
constructor(mailchannels: MailChannelsClient);
|
|
708
735
|
/**
|
|
709
736
|
* Validates a domain's email authentication setup by retrieving its DKIM, SPF, and Domain Lockdown status. This endpoint checks whether the domain is properly configured for secure email delivery.
|
|
737
|
+
* @param domain - Domain used for sending emails. If `dkim` settings are not provided, or `dkim` settings are provided with no `domain`, the stored dkim settings for this domain will be used.
|
|
710
738
|
* @param options - The domain options to check.
|
|
711
739
|
* @example
|
|
712
740
|
* ```ts
|
|
713
741
|
* const mailchannels = new MailChannels('your-api-key')
|
|
714
|
-
* const { data, error } = await mailchannels.domains.check({
|
|
742
|
+
* const { data, error } = await mailchannels.domains.check('example.com', {
|
|
715
743
|
* dkim: [{
|
|
716
744
|
* domain: 'example.com',
|
|
717
745
|
* privateKey: 'your-private-key',
|
|
718
746
|
* selector: 'mailchannels'
|
|
719
747
|
* }],
|
|
720
|
-
* domain: 'example.com',
|
|
721
748
|
* senderId: 'sender-id'
|
|
722
749
|
* })
|
|
723
750
|
* ```
|
|
724
751
|
*/
|
|
725
|
-
check(
|
|
752
|
+
check(domain: string, options?: DomainsCheckOptions): Promise<DomainsCheckResponse>;
|
|
726
753
|
}
|
|
727
754
|
declare class DomainsDkim {
|
|
728
755
|
private mailchannels;
|
|
@@ -760,12 +787,12 @@ declare class DomainsDkim {
|
|
|
760
787
|
* @example
|
|
761
788
|
* ```ts
|
|
762
789
|
* const mailchannels = new MailChannels('your-api-key')
|
|
763
|
-
* const { success, error } = await mailchannels.domains.dkim.
|
|
790
|
+
* const { success, error } = await mailchannels.domains.dkim.updateStatus('example.com', {
|
|
764
791
|
* selector: 'mailchannels',
|
|
765
792
|
* status: 'retired'
|
|
766
793
|
* })
|
|
767
794
|
*/
|
|
768
|
-
|
|
795
|
+
updateStatus(domain: string, options: DomainsDkimUpdateStatusOptions): Promise<SuccessResponse>;
|
|
769
796
|
/**
|
|
770
797
|
* Rotate an active DKIM key pair. Mark the original key as `rotated`, and create a new key pair with the required new key selector, reusing the same algorithm and key length. The rotated key remains valid for signing for a 3-day grace period, and is automatically changed to `retired` 2 weeks after rotation. Publish the new key to its DNS TXT record before rotated key expires for signing as emails sent with an unpublished key will fail DKIM validation by receiving providers. After the grace period, only the new key is valid for signing if published.
|
|
771
798
|
* @param domain - The domain the DKIM key belongs to.
|
|
@@ -842,7 +869,7 @@ interface WebhookEventBase<T extends WebhookEventType> {
|
|
|
842
869
|
* The MailChannels account ID that generated the webhook.
|
|
843
870
|
* If the message was sent by a sub-account, this field contains the sub-account handle.
|
|
844
871
|
*/
|
|
845
|
-
|
|
872
|
+
customerHandle: string;
|
|
846
873
|
/**
|
|
847
874
|
* The Unix timestamp (in seconds) when the event occurred; the timezone is always UTC
|
|
848
875
|
*/
|
|
@@ -850,7 +877,7 @@ interface WebhookEventBase<T extends WebhookEventType> {
|
|
|
850
877
|
/**
|
|
851
878
|
* The Message-Id of the message that generated the event
|
|
852
879
|
*/
|
|
853
|
-
|
|
880
|
+
smtpId?: string;
|
|
854
881
|
/**
|
|
855
882
|
* The type of event that occurred
|
|
856
883
|
*/
|
|
@@ -858,11 +885,11 @@ interface WebhookEventBase<T extends WebhookEventType> {
|
|
|
858
885
|
/**
|
|
859
886
|
* A unique identifier generated to track the original HTTP request
|
|
860
887
|
*/
|
|
861
|
-
|
|
888
|
+
requestId?: string;
|
|
862
889
|
/**
|
|
863
890
|
* The campaign identifier for the message that generated the event
|
|
864
891
|
*/
|
|
865
|
-
|
|
892
|
+
campaignId?: string;
|
|
866
893
|
/**
|
|
867
894
|
* The recipients of the message
|
|
868
895
|
*/
|
|
@@ -874,7 +901,7 @@ interface WebhookEventWithTracking {
|
|
|
874
901
|
/**
|
|
875
902
|
* The User-Agent header given when the recipient opened the message
|
|
876
903
|
*/
|
|
877
|
-
|
|
904
|
+
userAgent?: string;
|
|
878
905
|
/**
|
|
879
906
|
* The IP address of the host that made the HTTP request
|
|
880
907
|
*/
|
|
@@ -902,9 +929,8 @@ interface WebhookEventSoftBounced extends WebhookEventBase<"soft-bounced">, Webh
|
|
|
902
929
|
interface WebhookEventDropped extends WebhookEventBase<"dropped">, WebhookEventWithStatus {}
|
|
903
930
|
interface WebhookEventComplained extends WebhookEventBase<"complained"> {}
|
|
904
931
|
interface WebhookEventUnsubscribed extends WebhookEventBase<"unsubscribed"> {}
|
|
905
|
-
interface WebhookEventTest extends Omit<WebhookEventBase<"test">, "recipients" | "
|
|
932
|
+
interface WebhookEventTest extends Omit<WebhookEventBase<"test">, "recipients" | "campaignId"> {}
|
|
906
933
|
type WebhookEvent = WebhookEventProcessed | WebhookEventDelivered | WebhookEventOpen | WebhookEventClick | WebhookEventHardBounced | WebhookEventSoftBounced | WebhookEventDropped | WebhookEventComplained | WebhookEventUnsubscribed | WebhookEventTest;
|
|
907
|
-
type WebhookEvents = WebhookEvent[];
|
|
908
934
|
interface WebhooksVerifyOptions {
|
|
909
935
|
/**
|
|
910
936
|
* The raw body of the incoming webhook request as a string. This should be the exact payload received from the webhook, without any modifications or parsing, to ensure accurate signature verification.
|
|
@@ -928,12 +954,7 @@ interface WebhooksVerifyOptions {
|
|
|
928
954
|
*/
|
|
929
955
|
cache?: boolean;
|
|
930
956
|
}
|
|
931
|
-
type WebhooksVerifyResponse = DataResponse<
|
|
932
|
-
/**
|
|
933
|
-
* The type of event that occurred.
|
|
934
|
-
*/
|
|
935
|
-
event: WebhookEventType;
|
|
936
|
-
}[]>;
|
|
957
|
+
type WebhooksVerifyResponse = DataResponse<WebhookEvent[]>;
|
|
937
958
|
type WebhooksBatchStatus = "1xx" | "2xx" | "3xx" | "4xx" | "5xx" | "no_response";
|
|
938
959
|
type WebhooksBatchResponseStatus = "1xx_response" | "2xx_response" | "3xx_response" | "4xx_response" | "5xx_response" | "no_response";
|
|
939
960
|
interface WebhooksBatchesOptions {
|
|
@@ -1042,10 +1063,10 @@ declare class Webhooks {
|
|
|
1042
1063
|
* @example
|
|
1043
1064
|
* ```ts
|
|
1044
1065
|
* const mailchannels = new MailChannels('your-api-key')
|
|
1045
|
-
* const { success, error } = mailchannels.webhooks.
|
|
1066
|
+
* const { success, error } = await mailchannels.webhooks.create('https://example.com/api/webhooks/mailchannels')
|
|
1046
1067
|
* ```
|
|
1047
1068
|
*/
|
|
1048
|
-
|
|
1069
|
+
create(endpoint: string): Promise<SuccessResponse>;
|
|
1049
1070
|
/**
|
|
1050
1071
|
* Retrieves all registered webhook endpoints associated with the customer.
|
|
1051
1072
|
* @example
|
|
@@ -1743,6 +1764,11 @@ declare class Suppressions {
|
|
|
1743
1764
|
*/
|
|
1744
1765
|
list(options?: SuppressionsListOptions): Promise<SuppressionsListResponse>;
|
|
1745
1766
|
}
|
|
1767
|
+
type AttachmentOptions = Omit<EmailsSendAttachment, "content">;
|
|
1768
|
+
declare class Attachment {
|
|
1769
|
+
static fromBytes(data: ArrayBuffer | Uint8Array, options: AttachmentOptions): EmailsSendAttachment;
|
|
1770
|
+
static fromBlob(blob: Blob, options: AttachmentOptions): Promise<EmailsSendAttachment>;
|
|
1771
|
+
}
|
|
1746
1772
|
declare class MailChannels extends MailChannelsClient {
|
|
1747
1773
|
readonly emails: Emails;
|
|
1748
1774
|
readonly domains: Domains;
|
|
@@ -1752,4 +1778,4 @@ declare class MailChannels extends MailChannelsClient {
|
|
|
1752
1778
|
readonly suppressions: Suppressions;
|
|
1753
1779
|
constructor(key: string, options?: MailChannelsClientOptions);
|
|
1754
1780
|
}
|
|
1755
|
-
export { DataResponse, Domains, DomainsCheckOptions, DomainsCheckResponse, DomainsCheckVerdict, DomainsDkimCreateOptions, DomainsDkimCreateResponse, DomainsDkimKey, DomainsDkimKeyStatus, DomainsDkimListOptions, DomainsDkimListResponse, DomainsDkimRotateOptions, DomainsDkimRotateResponse,
|
|
1781
|
+
export { Attachment, type DataResponse, Domains, type DomainsCheckOptions, type DomainsCheckResponse, type DomainsCheckVerdict, type DomainsDkimCreateOptions, type DomainsDkimCreateResponse, type DomainsDkimKey, type DomainsDkimKeyStatus, type DomainsDkimListOptions, type DomainsDkimListResponse, type DomainsDkimRotateOptions, type DomainsDkimRotateResponse, type DomainsDkimUpdateStatusOptions, Emails, type EmailsQueueResponse, type EmailsSendAsyncResponse, type EmailsSendAttachment, type EmailsSendContent, type EmailsSendDkim, type EmailsSendOptions, type EmailsSendPersonalization, type EmailsSendRecipient, type EmailsSendRecipientInput, type EmailsSendResponse, type EmailsSendTemplate, type EmailsSendTemplateType, type EmailsSendTemplateValue, type EmailsSendTracking, type ErrorResponse, type ErrorType, MailChannels, MailChannelsClient, type MailChannelsClientOptions, Metrics, type MetricsBucket, type MetricsEngagement, type MetricsEngagementResponse, type MetricsOptions, type MetricsPerformance, type MetricsPerformanceResponse, type MetricsRecipientBehaviour, type MetricsRecipientBehaviourResponse, type MetricsSenders, type MetricsSendersOptions, type MetricsSendersResponse, type MetricsSendersType, type MetricsUsageResponse, type MetricsVolume, type MetricsVolumeResponse, SubAccounts, type SubAccountsAccount, type SubAccountsApiKey, type SubAccountsCreateApiKeyResponse, type SubAccountsCreateResponse, type SubAccountsCreateSmtpPasswordResponse, type SubAccountsLimit, type SubAccountsLimitResponse, type SubAccountsListApiKeyOptions, type SubAccountsListApiKeyResponse, type SubAccountsListOptions, type SubAccountsListResponse, type SubAccountsListSmtpPasswordResponse, type SubAccountsSmtpPassword, type SubAccountsUsage, type SubAccountsUsageResponse, type SuccessResponse, Suppressions, type SuppressionsCreateOptions, type SuppressionsListEntry, type SuppressionsListOptions, type SuppressionsListResponse, type SuppressionsSource, type SuppressionsTypes, type WebhookEvent, type WebhookEventClick, type WebhookEventComplained, type WebhookEventDelivered, type WebhookEventDropped, type WebhookEventHardBounced, type WebhookEventOpen, type WebhookEventProcessed, type WebhookEventSoftBounced, type WebhookEventTest, type WebhookEventType, type WebhookEventUnsubscribed, Webhooks, type WebhooksBatch, type WebhooksBatchResponseStatus, type WebhooksBatchStatus, type WebhooksBatchesOptions, type WebhooksBatchesResponse, type WebhooksListResponse, type WebhooksResendBatch, type WebhooksResendBatchResponse, type WebhooksSigningKeyResponse, type WebhooksValidateResponse, type WebhooksVerifyOptions, type WebhooksVerifyResponse };
|
package/dist/mailchannels.mjs
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { $fetch } from "ofetch";
|
|
2
2
|
import { subtle } from "node:crypto";
|
|
3
3
|
import { Buffer } from "node:buffer";
|
|
4
|
-
|
|
4
|
+
import mime from "mime";
|
|
5
|
+
var version = "1.0.0";
|
|
5
6
|
var MailChannelsClient = class MailChannelsClient {
|
|
6
7
|
static DEFAULT_BASE_URL = "https://api.mailchannels.net";
|
|
8
|
+
static DEFAULT_TIMEOUT = 12e4;
|
|
7
9
|
options;
|
|
8
10
|
#headers;
|
|
9
11
|
constructor(key, options = {}) {
|
|
10
12
|
if (!key) throw new Error("Missing MailChannels API key.");
|
|
11
13
|
this.options = {
|
|
12
14
|
baseUrl: options.baseUrl || MailChannelsClient.DEFAULT_BASE_URL,
|
|
13
|
-
retry: options.retry ?? false
|
|
15
|
+
retry: options.retry ?? false,
|
|
16
|
+
signal: options.signal,
|
|
17
|
+
timeout: options.timeout ?? MailChannelsClient.DEFAULT_TIMEOUT
|
|
14
18
|
};
|
|
15
19
|
this.#headers = {
|
|
16
20
|
"X-API-Key": key,
|
|
@@ -20,13 +24,16 @@ var MailChannelsClient = class MailChannelsClient {
|
|
|
20
24
|
};
|
|
21
25
|
}
|
|
22
26
|
async _fetch(path, options) {
|
|
27
|
+
const { headers, signal = this.options.signal, ...fetchOptions } = options;
|
|
23
28
|
return $fetch(path, {
|
|
24
29
|
baseURL: this.options.baseUrl,
|
|
25
30
|
retry: this.options.retry,
|
|
26
|
-
|
|
31
|
+
signal,
|
|
32
|
+
timeout: this.options.timeout === false ? void 0 : this.options.timeout,
|
|
33
|
+
...fetchOptions,
|
|
27
34
|
headers: {
|
|
28
35
|
...this.#headers,
|
|
29
|
-
...
|
|
36
|
+
...headers
|
|
30
37
|
}
|
|
31
38
|
});
|
|
32
39
|
}
|
|
@@ -61,10 +68,22 @@ var MailChannelsClient = class MailChannelsClient {
|
|
|
61
68
|
});
|
|
62
69
|
}
|
|
63
70
|
};
|
|
64
|
-
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) => {
|
|
65
83
|
return {
|
|
66
84
|
message,
|
|
67
|
-
statusCode
|
|
85
|
+
statusCode,
|
|
86
|
+
type
|
|
68
87
|
};
|
|
69
88
|
};
|
|
70
89
|
const getStatusError = (response, errors = {}) => {
|
|
@@ -74,18 +93,20 @@ const getStatusError = (response, errors = {}) => {
|
|
|
74
93
|
if (typeof payload === "string") details = payload;
|
|
75
94
|
else if (payload?.message) details = payload.message;
|
|
76
95
|
else if (Array.isArray(payload?.errors) && payload.errors?.length) details = payload.errors.join(", ");
|
|
77
|
-
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");
|
|
78
97
|
};
|
|
79
98
|
const getResultError = (e, fallback) => {
|
|
80
|
-
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");
|
|
81
103
|
};
|
|
82
104
|
const validatePagination = (pagination = {}) => {
|
|
83
105
|
const { limit, offset, max } = pagination;
|
|
84
|
-
if (typeof limit === "number" && (limit < 1 || max && limit > max)) return
|
|
85
|
-
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.");
|
|
86
108
|
return null;
|
|
87
109
|
};
|
|
88
|
-
const stripPemHeaders = (pem) => pem.replace(/-----[^-]+-----|\s|#.*$/gm, "");
|
|
89
110
|
const clean = (data) => {
|
|
90
111
|
if (Array.isArray(data)) {
|
|
91
112
|
const result = [];
|
|
@@ -108,25 +129,6 @@ const clean = (data) => {
|
|
|
108
129
|
}
|
|
109
130
|
return data;
|
|
110
131
|
};
|
|
111
|
-
const mapBuckets = (arr) => {
|
|
112
|
-
return arr.map(({ count, period_start }) => ({
|
|
113
|
-
count,
|
|
114
|
-
periodStart: period_start
|
|
115
|
-
}));
|
|
116
|
-
};
|
|
117
|
-
const mapDkimKey = (key) => ({
|
|
118
|
-
algorithm: key.algorithm,
|
|
119
|
-
createdAt: key.created_at,
|
|
120
|
-
dnsRecords: key.dkim_dns_records,
|
|
121
|
-
domain: key.domain,
|
|
122
|
-
gracePeriodExpiresAt: key.gracePeriodExpiresAt,
|
|
123
|
-
length: key.key_length,
|
|
124
|
-
publicKey: key.public_key,
|
|
125
|
-
retiresAt: key.retiresAt,
|
|
126
|
-
selector: key.selector,
|
|
127
|
-
status: key.status,
|
|
128
|
-
statusModifiedAt: key.status_modified_at
|
|
129
|
-
});
|
|
130
132
|
const isValidEmail = (email) => {
|
|
131
133
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
132
134
|
};
|
|
@@ -157,6 +159,7 @@ const parseArrayRecipients = (recipients) => {
|
|
|
157
159
|
const filtered = (typeof recipients === "string" ? [parseRecipientString(recipients)] : Array.isArray(recipients) ? recipients.map(parseRecipient) : [recipients]).filter((recipient) => Boolean(recipient));
|
|
158
160
|
return filtered.length > 0 ? filtered : void 0;
|
|
159
161
|
};
|
|
162
|
+
const stripPemHeaders = (pem) => pem.replace(/-----[^-]+-----|\s|#.*$/gm, "");
|
|
160
163
|
const RESERVED_HEADER_NAMES = new Set([
|
|
161
164
|
"authentication-results",
|
|
162
165
|
"bcc",
|
|
@@ -207,6 +210,13 @@ const mapDkim = (dkim) => ({
|
|
|
207
210
|
dkim_private_key: dkim?.privateKey ? stripPemHeaders(dkim.privateKey) : void 0,
|
|
208
211
|
dkim_selector: dkim?.selector
|
|
209
212
|
});
|
|
213
|
+
const mapAttachment = (attachment) => ({
|
|
214
|
+
content: attachment.content,
|
|
215
|
+
filename: attachment.filename,
|
|
216
|
+
type: attachment.type,
|
|
217
|
+
content_id: attachment.contentId,
|
|
218
|
+
disposition: attachment.disposition
|
|
219
|
+
});
|
|
210
220
|
const mapPersonalization = (personalization, index, rootTemplateData) => {
|
|
211
221
|
const to = parseArrayRecipients(personalization.to);
|
|
212
222
|
if (!to || !to.length) return `Personalization at index ${index} must include at least one recipient in the 'to' field.`;
|
|
@@ -238,11 +248,16 @@ const mapPersonalization = (personalization, index, rootTemplateData) => {
|
|
|
238
248
|
to
|
|
239
249
|
};
|
|
240
250
|
};
|
|
241
|
-
const
|
|
251
|
+
const resolveAttachments = async (attachments) => {
|
|
252
|
+
if (!attachments) return;
|
|
253
|
+
return Promise.all(attachments.map((a) => Promise.resolve(a))).catch((e) => e.message);
|
|
254
|
+
};
|
|
255
|
+
const buildSendPayload = async (options) => {
|
|
242
256
|
const { from, html, text } = options;
|
|
243
257
|
const contentTypes = options.content ? new Set(options.content.map((item) => item.type.toLowerCase())) : void 0;
|
|
244
258
|
const parsedFrom = parseRecipient(from);
|
|
245
|
-
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.";
|
|
246
261
|
if (!text && !html && (!options.content || !options.content.length)) return "No email content provided";
|
|
247
262
|
if (html && contentTypes?.has("text/html")) return "Cannot provide both 'html' and a 'content' entry with type 'text/html'.";
|
|
248
263
|
if (text && contentTypes?.has("text/plain")) return "Cannot provide both 'text' and a 'content' entry with type 'text/plain'.";
|
|
@@ -307,8 +322,10 @@ const buildSendPayload = (options) => {
|
|
|
307
322
|
value: item.value,
|
|
308
323
|
template_type
|
|
309
324
|
});
|
|
325
|
+
const resolvedAttachments = await resolveAttachments(options.attachments);
|
|
326
|
+
if (typeof resolvedAttachments === "string") return resolvedAttachments;
|
|
310
327
|
return {
|
|
311
|
-
attachments:
|
|
328
|
+
attachments: resolvedAttachments?.map((a) => mapAttachment(a)),
|
|
312
329
|
campaign_id: options.campaignId,
|
|
313
330
|
...mapDkim(options.dkim),
|
|
314
331
|
envelope_from: parseRecipient(options.envelopeFrom),
|
|
@@ -332,9 +349,9 @@ var Emails = class {
|
|
|
332
349
|
}
|
|
333
350
|
async _sendEmail(options, flags) {
|
|
334
351
|
let error = null;
|
|
335
|
-
const payload = buildSendPayload(options);
|
|
352
|
+
const payload = await buildSendPayload(options);
|
|
336
353
|
if (typeof payload === "string") {
|
|
337
|
-
error =
|
|
354
|
+
error = createValidationError(payload);
|
|
338
355
|
return {
|
|
339
356
|
data: null,
|
|
340
357
|
error
|
|
@@ -387,10 +404,26 @@ var Emails = class {
|
|
|
387
404
|
async send(options, dryRun = false) {
|
|
388
405
|
return this._sendEmail(options, { dryRun });
|
|
389
406
|
}
|
|
390
|
-
async
|
|
407
|
+
async queue(options) {
|
|
391
408
|
return this._sendEmail(options, { async: true });
|
|
392
409
|
}
|
|
410
|
+
async sendAsync(options) {
|
|
411
|
+
return this.queue(options);
|
|
412
|
+
}
|
|
393
413
|
};
|
|
414
|
+
const mapDkimKey = (key) => ({
|
|
415
|
+
algorithm: key.algorithm,
|
|
416
|
+
createdAt: key.created_at,
|
|
417
|
+
dnsRecords: key.dkim_dns_records,
|
|
418
|
+
domain: key.domain,
|
|
419
|
+
gracePeriodExpiresAt: key.gracePeriodExpiresAt,
|
|
420
|
+
length: key.key_length,
|
|
421
|
+
publicKey: key.public_key,
|
|
422
|
+
retiresAt: key.retiresAt,
|
|
423
|
+
selector: key.selector,
|
|
424
|
+
status: key.status,
|
|
425
|
+
statusModifiedAt: key.status_modified_at
|
|
426
|
+
});
|
|
394
427
|
var Domains = class {
|
|
395
428
|
mailchannels;
|
|
396
429
|
dkim;
|
|
@@ -398,32 +431,38 @@ var Domains = class {
|
|
|
398
431
|
this.mailchannels = mailchannels;
|
|
399
432
|
this.dkim = new DomainsDkim(mailchannels);
|
|
400
433
|
}
|
|
401
|
-
async check(options) {
|
|
434
|
+
async check(domain, options) {
|
|
402
435
|
let error = null;
|
|
403
|
-
|
|
404
|
-
|
|
436
|
+
if (!domain) {
|
|
437
|
+
error = createValidationError("No domain provided.");
|
|
438
|
+
return {
|
|
439
|
+
data: null,
|
|
440
|
+
error
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
const dkimOptions = options?.dkim ? Array.isArray(options.dkim) ? options.dkim : [options.dkim] : void 0;
|
|
405
444
|
if (dkimOptions && dkimOptions.length > 10) {
|
|
406
|
-
error =
|
|
445
|
+
error = createValidationError("A maximum of 10 DKIM settings can be provided.");
|
|
407
446
|
return {
|
|
408
447
|
data: null,
|
|
409
448
|
error
|
|
410
449
|
};
|
|
411
450
|
}
|
|
412
451
|
if (dkimOptions?.find(({ privateKey, selector }) => privateKey && !selector)) {
|
|
413
|
-
error =
|
|
452
|
+
error = createValidationError("DKIM settings with a privateKey must also include a selector.");
|
|
414
453
|
return {
|
|
415
454
|
data: null,
|
|
416
455
|
error
|
|
417
456
|
};
|
|
418
457
|
}
|
|
419
458
|
const payload = {
|
|
420
|
-
dkim_settings: dkimOptions?.map((
|
|
421
|
-
dkim_domain: domain,
|
|
422
|
-
dkim_private_key: privateKey ? stripPemHeaders(privateKey) : void 0,
|
|
423
|
-
dkim_selector: selector
|
|
459
|
+
dkim_settings: dkimOptions?.map((dkim) => ({
|
|
460
|
+
dkim_domain: dkim.domain,
|
|
461
|
+
dkim_private_key: dkim.privateKey ? stripPemHeaders(dkim.privateKey) : void 0,
|
|
462
|
+
dkim_selector: dkim.selector
|
|
424
463
|
})),
|
|
425
464
|
domain,
|
|
426
|
-
sender_id: senderId
|
|
465
|
+
sender_id: options?.senderId
|
|
427
466
|
};
|
|
428
467
|
const response = await this.mailchannels.post("/tx/v1/check-domain", {
|
|
429
468
|
body: payload,
|
|
@@ -466,8 +505,15 @@ var DomainsDkim = class {
|
|
|
466
505
|
}
|
|
467
506
|
async create(domain, options) {
|
|
468
507
|
let error = null;
|
|
508
|
+
if (!domain) {
|
|
509
|
+
error = createValidationError("No domain provided.");
|
|
510
|
+
return {
|
|
511
|
+
data: null,
|
|
512
|
+
error
|
|
513
|
+
};
|
|
514
|
+
}
|
|
469
515
|
if (!options.selector || options.selector.length > 63) {
|
|
470
|
-
error =
|
|
516
|
+
error = createValidationError("Selector must be between 1 and 63 characters.");
|
|
471
517
|
return {
|
|
472
518
|
data: null,
|
|
473
519
|
error
|
|
@@ -501,8 +547,15 @@ var DomainsDkim = class {
|
|
|
501
547
|
}
|
|
502
548
|
async list(domain, options) {
|
|
503
549
|
let error = null;
|
|
550
|
+
if (!domain) {
|
|
551
|
+
error = createValidationError("No domain provided.");
|
|
552
|
+
return {
|
|
553
|
+
data: null,
|
|
554
|
+
error
|
|
555
|
+
};
|
|
556
|
+
}
|
|
504
557
|
if (options?.selector && options.selector.length > 63) {
|
|
505
|
-
error =
|
|
558
|
+
error = createValidationError("Selector must be between 1 and 63 characters.");
|
|
506
559
|
return {
|
|
507
560
|
data: null,
|
|
508
561
|
error
|
|
@@ -541,10 +594,17 @@ var DomainsDkim = class {
|
|
|
541
594
|
error: null
|
|
542
595
|
};
|
|
543
596
|
}
|
|
544
|
-
async
|
|
597
|
+
async updateStatus(domain, options) {
|
|
545
598
|
let error = null;
|
|
599
|
+
if (!domain) {
|
|
600
|
+
error = createValidationError("No domain provided.");
|
|
601
|
+
return {
|
|
602
|
+
success: false,
|
|
603
|
+
error
|
|
604
|
+
};
|
|
605
|
+
}
|
|
546
606
|
if (!options.selector || options.selector.length > 63) {
|
|
547
|
-
error =
|
|
607
|
+
error = createValidationError("Selector must be between 1 and 63 characters.");
|
|
548
608
|
return {
|
|
549
609
|
success: false,
|
|
550
610
|
error
|
|
@@ -560,7 +620,7 @@ var DomainsDkim = class {
|
|
|
560
620
|
});
|
|
561
621
|
}
|
|
562
622
|
}).catch((e) => {
|
|
563
|
-
error ||= getResultError(e, "Failed to update DKIM key.");
|
|
623
|
+
error ||= getResultError(e, "Failed to update status of DKIM key.");
|
|
564
624
|
});
|
|
565
625
|
return {
|
|
566
626
|
success: !error,
|
|
@@ -569,15 +629,22 @@ var DomainsDkim = class {
|
|
|
569
629
|
}
|
|
570
630
|
async rotate(domain, selector, options) {
|
|
571
631
|
let error = null;
|
|
632
|
+
if (!domain) {
|
|
633
|
+
error = createValidationError("No domain provided.");
|
|
634
|
+
return {
|
|
635
|
+
data: null,
|
|
636
|
+
error
|
|
637
|
+
};
|
|
638
|
+
}
|
|
572
639
|
if (!selector || selector.length > 63) {
|
|
573
|
-
error =
|
|
640
|
+
error = createValidationError("Selector must be between 1 and 63 characters.");
|
|
574
641
|
return {
|
|
575
642
|
data: null,
|
|
576
643
|
error
|
|
577
644
|
};
|
|
578
645
|
}
|
|
579
646
|
if (!options.newKey.selector || options.newKey.selector.length > 63) {
|
|
580
|
-
error =
|
|
647
|
+
error = createValidationError("New key selector must be between 1 and 63 characters.");
|
|
581
648
|
return {
|
|
582
649
|
data: null,
|
|
583
650
|
error
|
|
@@ -687,17 +754,17 @@ var Webhooks = class Webhooks {
|
|
|
687
754
|
constructor(mailchannels) {
|
|
688
755
|
this.mailchannels = mailchannels;
|
|
689
756
|
}
|
|
690
|
-
async
|
|
757
|
+
async create(endpoint) {
|
|
691
758
|
let error = null;
|
|
692
759
|
if (!endpoint) {
|
|
693
|
-
error =
|
|
760
|
+
error = createValidationError("No endpoint provided.");
|
|
694
761
|
return {
|
|
695
762
|
success: false,
|
|
696
763
|
error
|
|
697
764
|
};
|
|
698
765
|
}
|
|
699
766
|
if (endpoint.length > 8e3) {
|
|
700
|
-
error =
|
|
767
|
+
error = createValidationError("The endpoint exceeds the maximum length of 8000 characters.");
|
|
701
768
|
return {
|
|
702
769
|
success: false,
|
|
703
770
|
error
|
|
@@ -709,7 +776,7 @@ var Webhooks = class Webhooks {
|
|
|
709
776
|
error = getStatusError(response, { [409]: `Endpoint '${endpoint}' is already enrolled to receive notifications.` });
|
|
710
777
|
}
|
|
711
778
|
}).catch((e) => {
|
|
712
|
-
error ||= getResultError(e, "Failed to
|
|
779
|
+
error ||= getResultError(e, "Failed to create webhook.");
|
|
713
780
|
});
|
|
714
781
|
return {
|
|
715
782
|
success: !error,
|
|
@@ -774,7 +841,7 @@ var Webhooks = class Webhooks {
|
|
|
774
841
|
async validate(requestId) {
|
|
775
842
|
let error = null;
|
|
776
843
|
if (requestId && requestId.length > 28) {
|
|
777
|
-
error =
|
|
844
|
+
error = createValidationError("The request id should not exceed 28 characters.");
|
|
778
845
|
return {
|
|
779
846
|
data: null,
|
|
780
847
|
error
|
|
@@ -807,7 +874,7 @@ var Webhooks = class Webhooks {
|
|
|
807
874
|
static async verify(options) {
|
|
808
875
|
let error = null;
|
|
809
876
|
if (!await isValidWebhook(options).catch(() => false)) {
|
|
810
|
-
error =
|
|
877
|
+
error = createValidationError("Invalid webhook signature.");
|
|
811
878
|
return {
|
|
812
879
|
data: null,
|
|
813
880
|
error
|
|
@@ -816,18 +883,32 @@ var Webhooks = class Webhooks {
|
|
|
816
883
|
try {
|
|
817
884
|
const payload = JSON.parse(options.payload);
|
|
818
885
|
if (!Array.isArray(payload)) {
|
|
819
|
-
error =
|
|
886
|
+
error = createValidationError("Invalid webhook payload.");
|
|
820
887
|
return {
|
|
821
888
|
data: null,
|
|
822
889
|
error
|
|
823
890
|
};
|
|
824
891
|
}
|
|
825
892
|
return {
|
|
826
|
-
data: clean(payload.map((event) => ({
|
|
893
|
+
data: clean(payload.map((event) => ({
|
|
894
|
+
email: event.email,
|
|
895
|
+
customerHandle: event.customer_handle,
|
|
896
|
+
timestamp: event.timestamp,
|
|
897
|
+
smtpId: event.smtp_id,
|
|
898
|
+
event: event.event,
|
|
899
|
+
requestId: event.request_id,
|
|
900
|
+
campaignId: "campaign_id" in event ? event.campaign_id : void 0,
|
|
901
|
+
recipients: "recipients" in event ? event.recipients : void 0,
|
|
902
|
+
userAgent: "user_agent" in event ? event.user_agent : void 0,
|
|
903
|
+
ip: "ip" in event ? event.ip : void 0,
|
|
904
|
+
url: "url" in event ? event.url : void 0,
|
|
905
|
+
status: "status" in event ? event.status : void 0,
|
|
906
|
+
reason: "reason" in event ? event.reason : void 0
|
|
907
|
+
}))),
|
|
827
908
|
error: null
|
|
828
909
|
};
|
|
829
910
|
} catch {
|
|
830
|
-
error =
|
|
911
|
+
error = createValidationError("Invalid webhook payload.");
|
|
831
912
|
return {
|
|
832
913
|
data: null,
|
|
833
914
|
error
|
|
@@ -849,19 +930,19 @@ var Webhooks = class Webhooks {
|
|
|
849
930
|
};
|
|
850
931
|
if (options?.statuses && options.statuses.length > 6) return {
|
|
851
932
|
data: null,
|
|
852
|
-
error:
|
|
933
|
+
error: createValidationError("A maximum of 6 status filters can be provided.")
|
|
853
934
|
};
|
|
854
935
|
if (options?.statuses && new Set(options.statuses).size !== options.statuses.length) return {
|
|
855
936
|
data: null,
|
|
856
|
-
error:
|
|
937
|
+
error: createValidationError("Status filters must be unique.")
|
|
857
938
|
};
|
|
858
939
|
if (options?.createdAfter && Number.isNaN(Date.parse(options.createdAfter))) return {
|
|
859
940
|
data: null,
|
|
860
|
-
error:
|
|
941
|
+
error: createValidationError("createdAfter must be a valid date string.")
|
|
861
942
|
};
|
|
862
943
|
if (options?.createdBefore && Number.isNaN(Date.parse(options.createdBefore))) return {
|
|
863
944
|
data: null,
|
|
864
|
-
error:
|
|
945
|
+
error: createValidationError("createdBefore must be a valid date string.")
|
|
865
946
|
};
|
|
866
947
|
if (options?.createdAfter && options?.createdBefore) {
|
|
867
948
|
const createdAfter = Date.parse(options.createdAfter);
|
|
@@ -869,11 +950,11 @@ var Webhooks = class Webhooks {
|
|
|
869
950
|
const maxRangeMs = 744 * 60 * 60 * 1e3;
|
|
870
951
|
if (createdBefore <= createdAfter) return {
|
|
871
952
|
data: null,
|
|
872
|
-
error:
|
|
953
|
+
error: createValidationError("createdBefore must be later than createdAfter.")
|
|
873
954
|
};
|
|
874
955
|
if (createdBefore - createdAfter > maxRangeMs) return {
|
|
875
956
|
data: null,
|
|
876
|
-
error:
|
|
957
|
+
error: createValidationError("The time range between createdAfter and createdBefore must not exceed 31 days.")
|
|
877
958
|
};
|
|
878
959
|
}
|
|
879
960
|
const response = await this.mailchannels.get("/tx/v1/webhook-batch", {
|
|
@@ -949,7 +1030,7 @@ var SubAccounts = class SubAccounts {
|
|
|
949
1030
|
async create(companyName, handle) {
|
|
950
1031
|
let error = null;
|
|
951
1032
|
if (!SubAccounts.COMPANY_PATTERN.test(companyName)) {
|
|
952
|
-
error =
|
|
1033
|
+
error = createValidationError("Invalid company name. Company name must be between 3 and 128 characters.");
|
|
953
1034
|
return {
|
|
954
1035
|
data: null,
|
|
955
1036
|
error
|
|
@@ -957,7 +1038,7 @@ var SubAccounts = class SubAccounts {
|
|
|
957
1038
|
}
|
|
958
1039
|
if (handle) {
|
|
959
1040
|
if (!SubAccounts.HANDLE_PATTERN.test(handle)) {
|
|
960
|
-
error =
|
|
1041
|
+
error = createValidationError("Invalid handle. Sub-account handle must be between 3 and 128 characters and contain only lowercase letters and numbers.");
|
|
961
1042
|
return {
|
|
962
1043
|
data: null,
|
|
963
1044
|
error
|
|
@@ -1027,7 +1108,7 @@ var SubAccounts = class SubAccounts {
|
|
|
1027
1108
|
async delete(handle) {
|
|
1028
1109
|
let error = null;
|
|
1029
1110
|
if (!handle) {
|
|
1030
|
-
error =
|
|
1111
|
+
error = createValidationError("No handle provided.");
|
|
1031
1112
|
return {
|
|
1032
1113
|
success: false,
|
|
1033
1114
|
error
|
|
@@ -1046,7 +1127,7 @@ var SubAccounts = class SubAccounts {
|
|
|
1046
1127
|
async suspend(handle) {
|
|
1047
1128
|
let error = null;
|
|
1048
1129
|
if (!handle) {
|
|
1049
|
-
error =
|
|
1130
|
+
error = createValidationError("No handle provided.");
|
|
1050
1131
|
return {
|
|
1051
1132
|
success: false,
|
|
1052
1133
|
error
|
|
@@ -1065,7 +1146,7 @@ var SubAccounts = class SubAccounts {
|
|
|
1065
1146
|
async activate(handle) {
|
|
1066
1147
|
let error = null;
|
|
1067
1148
|
if (!handle) {
|
|
1068
|
-
error =
|
|
1149
|
+
error = createValidationError("No handle provided.");
|
|
1069
1150
|
return {
|
|
1070
1151
|
success: false,
|
|
1071
1152
|
error
|
|
@@ -1087,7 +1168,7 @@ var SubAccounts = class SubAccounts {
|
|
|
1087
1168
|
async createApiKey(handle) {
|
|
1088
1169
|
let error = null;
|
|
1089
1170
|
if (!handle) {
|
|
1090
|
-
error =
|
|
1171
|
+
error = createValidationError("No handle provided.");
|
|
1091
1172
|
return {
|
|
1092
1173
|
data: null,
|
|
1093
1174
|
error
|
|
@@ -1115,7 +1196,7 @@ var SubAccounts = class SubAccounts {
|
|
|
1115
1196
|
async listApiKeys(handle, options) {
|
|
1116
1197
|
let error = null;
|
|
1117
1198
|
if (!handle) {
|
|
1118
|
-
error =
|
|
1199
|
+
error = createValidationError("No handle provided.");
|
|
1119
1200
|
return {
|
|
1120
1201
|
data: null,
|
|
1121
1202
|
error
|
|
@@ -1150,7 +1231,7 @@ var SubAccounts = class SubAccounts {
|
|
|
1150
1231
|
async deleteApiKey(handle, id) {
|
|
1151
1232
|
let error = null;
|
|
1152
1233
|
if (!handle) {
|
|
1153
|
-
error =
|
|
1234
|
+
error = createValidationError("No handle provided.");
|
|
1154
1235
|
return {
|
|
1155
1236
|
success: false,
|
|
1156
1237
|
error
|
|
@@ -1169,7 +1250,7 @@ var SubAccounts = class SubAccounts {
|
|
|
1169
1250
|
async createSmtpPassword(handle) {
|
|
1170
1251
|
let error = null;
|
|
1171
1252
|
if (!handle) {
|
|
1172
|
-
error =
|
|
1253
|
+
error = createValidationError("No handle provided.");
|
|
1173
1254
|
return {
|
|
1174
1255
|
data: null,
|
|
1175
1256
|
error
|
|
@@ -1201,7 +1282,7 @@ var SubAccounts = class SubAccounts {
|
|
|
1201
1282
|
async listSmtpPasswords(handle) {
|
|
1202
1283
|
let error = null;
|
|
1203
1284
|
if (!handle) {
|
|
1204
|
-
error =
|
|
1285
|
+
error = createValidationError("No handle provided.");
|
|
1205
1286
|
return {
|
|
1206
1287
|
data: null,
|
|
1207
1288
|
error
|
|
@@ -1229,7 +1310,7 @@ var SubAccounts = class SubAccounts {
|
|
|
1229
1310
|
async deleteSmtpPassword(handle, id) {
|
|
1230
1311
|
let error = null;
|
|
1231
1312
|
if (!handle) {
|
|
1232
|
-
error =
|
|
1313
|
+
error = createValidationError("No handle provided.");
|
|
1233
1314
|
return {
|
|
1234
1315
|
success: false,
|
|
1235
1316
|
error
|
|
@@ -1248,7 +1329,7 @@ var SubAccounts = class SubAccounts {
|
|
|
1248
1329
|
async getLimit(handle) {
|
|
1249
1330
|
let error = null;
|
|
1250
1331
|
if (!handle) {
|
|
1251
|
-
error =
|
|
1332
|
+
error = createValidationError("No handle provided.");
|
|
1252
1333
|
return {
|
|
1253
1334
|
data: null,
|
|
1254
1335
|
error
|
|
@@ -1272,14 +1353,14 @@ var SubAccounts = class SubAccounts {
|
|
|
1272
1353
|
async setLimit(handle, limit) {
|
|
1273
1354
|
let error = null;
|
|
1274
1355
|
if (!handle) {
|
|
1275
|
-
error =
|
|
1356
|
+
error = createValidationError("No handle provided.");
|
|
1276
1357
|
return {
|
|
1277
1358
|
success: false,
|
|
1278
1359
|
error
|
|
1279
1360
|
};
|
|
1280
1361
|
}
|
|
1281
1362
|
if (limit.sends < 0) {
|
|
1282
|
-
error =
|
|
1363
|
+
error = createValidationError("The sends value must be at least 0.");
|
|
1283
1364
|
return {
|
|
1284
1365
|
success: false,
|
|
1285
1366
|
error
|
|
@@ -1304,7 +1385,7 @@ var SubAccounts = class SubAccounts {
|
|
|
1304
1385
|
async deleteLimit(handle) {
|
|
1305
1386
|
let error = null;
|
|
1306
1387
|
if (!handle) {
|
|
1307
|
-
error =
|
|
1388
|
+
error = createValidationError("No handle provided.");
|
|
1308
1389
|
return {
|
|
1309
1390
|
success: false,
|
|
1310
1391
|
error
|
|
@@ -1323,7 +1404,7 @@ var SubAccounts = class SubAccounts {
|
|
|
1323
1404
|
async getUsage(handle) {
|
|
1324
1405
|
let error = null;
|
|
1325
1406
|
if (!handle) {
|
|
1326
|
-
error =
|
|
1407
|
+
error = createValidationError("No handle provided.");
|
|
1327
1408
|
return {
|
|
1328
1409
|
data: null,
|
|
1329
1410
|
error
|
|
@@ -1349,6 +1430,12 @@ var SubAccounts = class SubAccounts {
|
|
|
1349
1430
|
};
|
|
1350
1431
|
}
|
|
1351
1432
|
};
|
|
1433
|
+
const mapBucket = (bucket) => {
|
|
1434
|
+
return {
|
|
1435
|
+
count: bucket.count,
|
|
1436
|
+
periodStart: bucket.period_start
|
|
1437
|
+
};
|
|
1438
|
+
};
|
|
1352
1439
|
var Metrics = class {
|
|
1353
1440
|
mailchannels;
|
|
1354
1441
|
constructor(mailchannels) {
|
|
@@ -1377,10 +1464,10 @@ var Metrics = class {
|
|
|
1377
1464
|
return {
|
|
1378
1465
|
data: clean({
|
|
1379
1466
|
buckets: {
|
|
1380
|
-
click:
|
|
1381
|
-
clickTrackingDelivered:
|
|
1382
|
-
open:
|
|
1383
|
-
openTrackingDelivered:
|
|
1467
|
+
click: response.buckets.click.map(mapBucket),
|
|
1468
|
+
clickTrackingDelivered: response.buckets.click_tracking_delivered.map(mapBucket),
|
|
1469
|
+
open: response.buckets.open.map(mapBucket),
|
|
1470
|
+
openTrackingDelivered: response.buckets.open_tracking_delivered.map(mapBucket)
|
|
1384
1471
|
},
|
|
1385
1472
|
click: response.click,
|
|
1386
1473
|
clickTrackingDelivered: response.click_tracking_delivered,
|
|
@@ -1416,9 +1503,9 @@ var Metrics = class {
|
|
|
1416
1503
|
data: clean({
|
|
1417
1504
|
bounced: response.bounced,
|
|
1418
1505
|
buckets: {
|
|
1419
|
-
bounced:
|
|
1420
|
-
delivered:
|
|
1421
|
-
processed:
|
|
1506
|
+
bounced: response.buckets.bounced.map(mapBucket),
|
|
1507
|
+
delivered: response.buckets.delivered.map(mapBucket),
|
|
1508
|
+
processed: response.buckets.processed.map(mapBucket)
|
|
1422
1509
|
},
|
|
1423
1510
|
delivered: response.delivered,
|
|
1424
1511
|
endTime: response.end_time,
|
|
@@ -1451,8 +1538,8 @@ var Metrics = class {
|
|
|
1451
1538
|
return {
|
|
1452
1539
|
data: clean({
|
|
1453
1540
|
buckets: {
|
|
1454
|
-
unsubscribeDelivered:
|
|
1455
|
-
unsubscribed:
|
|
1541
|
+
unsubscribeDelivered: response.buckets.unsubscribe_delivered.map(mapBucket),
|
|
1542
|
+
unsubscribed: response.buckets.unsubscribed.map(mapBucket)
|
|
1456
1543
|
},
|
|
1457
1544
|
endTime: response.end_time,
|
|
1458
1545
|
startTime: response.start_time,
|
|
@@ -1485,9 +1572,9 @@ var Metrics = class {
|
|
|
1485
1572
|
return {
|
|
1486
1573
|
data: clean({
|
|
1487
1574
|
buckets: {
|
|
1488
|
-
delivered:
|
|
1489
|
-
dropped:
|
|
1490
|
-
processed:
|
|
1575
|
+
delivered: response.buckets.delivered.map(mapBucket),
|
|
1576
|
+
dropped: response.buckets.dropped.map(mapBucket),
|
|
1577
|
+
processed: response.buckets.processed.map(mapBucket)
|
|
1491
1578
|
},
|
|
1492
1579
|
delivered: response.delivered,
|
|
1493
1580
|
dropped: response.dropped,
|
|
@@ -1570,7 +1657,7 @@ var Suppressions = class {
|
|
|
1570
1657
|
let error = null;
|
|
1571
1658
|
const { addToSubAccounts, entries } = options;
|
|
1572
1659
|
if (entries.length > 1e3) {
|
|
1573
|
-
error =
|
|
1660
|
+
error = createValidationError("The number of suppression entries must not exceed 1000.");
|
|
1574
1661
|
return {
|
|
1575
1662
|
success: false,
|
|
1576
1663
|
error
|
|
@@ -1619,7 +1706,7 @@ var Suppressions = class {
|
|
|
1619
1706
|
async list(options) {
|
|
1620
1707
|
let error = null;
|
|
1621
1708
|
if (options?.recipient && options.recipient.length > 255) {
|
|
1622
|
-
error =
|
|
1709
|
+
error = createValidationError("The recipient must not exceed 255 characters.");
|
|
1623
1710
|
return {
|
|
1624
1711
|
data: null,
|
|
1625
1712
|
error
|
|
@@ -1667,6 +1754,33 @@ var Suppressions = class {
|
|
|
1667
1754
|
};
|
|
1668
1755
|
}
|
|
1669
1756
|
};
|
|
1757
|
+
const base64Content = (data) => {
|
|
1758
|
+
if (data instanceof ArrayBuffer) return Buffer.from(data).toString("base64");
|
|
1759
|
+
return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("base64");
|
|
1760
|
+
};
|
|
1761
|
+
const guessContentType = (filename) => {
|
|
1762
|
+
return mime.getType(filename) || void 0;
|
|
1763
|
+
};
|
|
1764
|
+
var Attachment = class Attachment {
|
|
1765
|
+
static fromBytes(data, options) {
|
|
1766
|
+
const { filename, type, contentId, disposition = "attachment" } = options;
|
|
1767
|
+
return {
|
|
1768
|
+
content: base64Content(data),
|
|
1769
|
+
filename: decodeURIComponent(filename) || "attachment",
|
|
1770
|
+
type: type || guessContentType(filename),
|
|
1771
|
+
contentId,
|
|
1772
|
+
disposition
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
static async fromBlob(blob, options) {
|
|
1776
|
+
if (!(blob instanceof Blob)) throw new Error("Unable to create attachment: expected a Blob");
|
|
1777
|
+
const bytes = await blob.arrayBuffer();
|
|
1778
|
+
return Attachment.fromBytes(bytes, {
|
|
1779
|
+
type: blob.type,
|
|
1780
|
+
...options
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1670
1784
|
var MailChannels = class extends MailChannelsClient {
|
|
1671
1785
|
emails = new Emails(this);
|
|
1672
1786
|
domains = new Domains(this);
|
|
@@ -1678,4 +1792,4 @@ var MailChannels = class extends MailChannelsClient {
|
|
|
1678
1792
|
super(key, options);
|
|
1679
1793
|
}
|
|
1680
1794
|
};
|
|
1681
|
-
export { Domains, Emails, MailChannels, MailChannelsClient, Metrics, SubAccounts, Suppressions, Webhooks };
|
|
1795
|
+
export { Attachment, Domains, Emails, MailChannels, MailChannelsClient, Metrics, SubAccounts, Suppressions, Webhooks };
|
package/package.json
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailchannels-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Node.js SDK to integrate MailChannels Email API into your JavaScript or TypeScript server-side applications.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"mailchannels",
|
|
9
|
-
"javascript",
|
|
10
9
|
"typescript",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
10
|
+
"email",
|
|
11
|
+
"transactional-email",
|
|
12
|
+
"sdk"
|
|
14
13
|
],
|
|
15
14
|
"repository": {
|
|
16
15
|
"type": "git",
|
|
17
|
-
"url": "git+https://
|
|
16
|
+
"url": "git+https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js.git"
|
|
18
17
|
},
|
|
19
18
|
"homepage": "https://mailchannels.yizack.com",
|
|
20
19
|
"author": {
|
|
21
|
-
"name": "
|
|
22
|
-
"email": "
|
|
23
|
-
"url": "https://yizack.com"
|
|
20
|
+
"name": "MailChannels",
|
|
21
|
+
"email": "dev@mailchannels.com"
|
|
24
22
|
},
|
|
25
23
|
"main": "./dist/mailchannels.mjs",
|
|
26
24
|
"exports": {
|
|
@@ -38,27 +36,31 @@
|
|
|
38
36
|
"dist"
|
|
39
37
|
],
|
|
40
38
|
"dependencies": {
|
|
39
|
+
"mime": "^4.1.0",
|
|
41
40
|
"ofetch": "^2.0.0-alpha.3"
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
43
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
45
44
|
"@types/markdown-it": "^14.1.2",
|
|
46
|
-
"@types/node": "^25.
|
|
47
|
-
"@vitest/coverage-v8": "^4.1.
|
|
45
|
+
"@types/node": "^25.9.2",
|
|
46
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
48
47
|
"changelogen": "^0.6.2",
|
|
49
|
-
"obuild": "^0.4.
|
|
50
|
-
"oxlint": "^1.
|
|
48
|
+
"obuild": "^0.4.36",
|
|
49
|
+
"oxlint": "^1.68.0",
|
|
51
50
|
"scule": "^1.3.0",
|
|
52
51
|
"typescript": "^6.0.3",
|
|
53
52
|
"vitepress": "^2.0.0-alpha.17",
|
|
54
53
|
"vitepress-plugin-group-icons": "^1.7.5",
|
|
55
|
-
"vitepress-plugin-llms": "^1.
|
|
56
|
-
"vitest": "^4.1.
|
|
54
|
+
"vitepress-plugin-llms": "^1.13.1",
|
|
55
|
+
"vitest": "^4.1.8"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=20"
|
|
57
59
|
},
|
|
58
60
|
"scripts": {
|
|
59
61
|
"build": "obuild",
|
|
60
|
-
"parity:fixtures": "node scripts/generate-parity-fixtures.
|
|
61
|
-
"release": "pnpm lint && pnpm test && pnpm build && changelogen --
|
|
62
|
+
"parity:fixtures": "node scripts/generate-parity-fixtures.ts",
|
|
63
|
+
"release": "pnpm lint && pnpm test && pnpm build && changelogen --bump",
|
|
62
64
|
"simulate": "node src/cli.ts simulate",
|
|
63
65
|
"lint": "oxlint",
|
|
64
66
|
"lint:fix": "oxlint --fix",
|