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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Yizack Rangel
3
+ Copyright (c) 2026 MailChannels
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,12 +1,15 @@
1
- ![MailChannels Node.js SDK](./docs/public/images/presentation.png)
1
+ ![MailChannels Node.js SDK](https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js/raw/HEAD/docs/public/images/presentation.png)
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
- [![codecov][codecov-coverage-src]][codecov-coverage-href]
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.21.1`
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://github.com/Yizack/mailchannels/blob/main/src/simulator/email-api.mjs) |
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
- <details>
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
- npx jiti playground/emails/send.ts
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
- [codecov-coverage-src]: https://img.shields.io/codecov/c/github/yizack/mailchannels?style=flat&colorA=070a30&token=HTSBRHSJ5M
231
- [codecov-coverage-href]: https://codecov.io/gh/Yizack/mailchannels
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
@@ -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?: FetchOptions<"json">): Promise<T>;
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: string;
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 EmailsSendAsyncResponse = DataResponse<{
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.sendAsync({
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
- sendAsync(options: EmailsSendOptions): Promise<EmailsSendAsyncResponse>;
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 DomainsDkimUpdateOptions {
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(options: DomainsCheckOptions): Promise<DomainsCheckResponse>;
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.update('example.com', {
790
+ * const { success, error } = await mailchannels.domains.dkim.updateStatus('example.com', {
764
791
  * selector: 'mailchannels',
765
792
  * status: 'retired'
766
793
  * })
767
794
  */
768
- update(domain: string, options: DomainsDkimUpdateOptions): Promise<SuccessResponse>;
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
- customer_handle: string;
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
- smtp_id?: string;
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
- request_id?: string;
888
+ requestId?: string;
862
889
  /**
863
890
  * The campaign identifier for the message that generated the event
864
891
  */
865
- campaign_id?: string;
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
- user_agent?: string;
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" | "campaign_id"> {}
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.enroll('https://example.com/api/webhooks/mailchannels')
1066
+ * const { success, error } = await mailchannels.webhooks.create('https://example.com/api/webhooks/mailchannels')
1046
1067
  * ```
1047
1068
  */
1048
- enroll(endpoint: string): Promise<SuccessResponse>;
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, DomainsDkimUpdateOptions, Emails, EmailsSendAsyncResponse, EmailsSendAttachment, EmailsSendContent, EmailsSendDkim, EmailsSendOptions, EmailsSendPersonalization, EmailsSendRecipient, EmailsSendRecipientInput, EmailsSendResponse, EmailsSendTemplate, EmailsSendTemplateType, EmailsSendTemplateValue, EmailsSendTracking, ErrorResponse, MailChannels, MailChannelsClient, MailChannelsClientOptions, Metrics, MetricsBucket, MetricsEngagement, MetricsEngagementResponse, MetricsOptions, MetricsPerformance, MetricsPerformanceResponse, MetricsRecipientBehaviour, MetricsRecipientBehaviourResponse, MetricsSenders, MetricsSendersOptions, MetricsSendersResponse, MetricsSendersType, MetricsUsageResponse, MetricsVolume, MetricsVolumeResponse, SubAccounts, SubAccountsAccount, SubAccountsApiKey, SubAccountsCreateApiKeyResponse, SubAccountsCreateResponse, SubAccountsCreateSmtpPasswordResponse, SubAccountsLimit, SubAccountsLimitResponse, SubAccountsListApiKeyOptions, SubAccountsListApiKeyResponse, SubAccountsListOptions, SubAccountsListResponse, SubAccountsListSmtpPasswordResponse, SubAccountsSmtpPassword, SubAccountsUsage, SubAccountsUsageResponse, SuccessResponse, Suppressions, SuppressionsCreateOptions, SuppressionsListEntry, SuppressionsListOptions, SuppressionsListResponse, SuppressionsSource, SuppressionsTypes, WebhookEvent, WebhookEventClick, WebhookEventComplained, WebhookEventDelivered, WebhookEventDropped, WebhookEventHardBounced, WebhookEventOpen, WebhookEventProcessed, WebhookEventSoftBounced, WebhookEventTest, WebhookEventType, WebhookEventUnsubscribed, WebhookEvents, Webhooks, WebhooksBatch, WebhooksBatchResponseStatus, WebhooksBatchStatus, WebhooksBatchesOptions, WebhooksBatchesResponse, WebhooksListResponse, WebhooksResendBatch, WebhooksResendBatchResponse, WebhooksSigningKeyResponse, WebhooksValidateResponse, WebhooksVerifyOptions, WebhooksVerifyResponse };
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 };
@@ -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
- var version = "0.8.0-1";
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
- ...options,
31
+ signal,
32
+ timeout: this.options.timeout === false ? void 0 : this.options.timeout,
33
+ ...fetchOptions,
27
34
  headers: {
28
35
  ...this.#headers,
29
- ...options?.headers
36
+ ...headers
30
37
  }
31
38
  });
32
39
  }
@@ -61,10 +68,22 @@ var MailChannelsClient = class MailChannelsClient {
61
68
  });
62
69
  }
63
70
  };
64
- const createError = (message, statusCode = null) => {
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 createError("The limit value " + (max ? `must be between 1 and ${max}.` : "is invalid. Only positive values are allowed."));
85
- if (typeof offset === "number" && offset < 0) return createError("Offset must be greater than or equal to 0.");
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 buildSendPayload = (options) => {
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 `from` option to specify a sender";
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: options.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 = createError(payload);
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 sendAsync(options) {
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
- const { dkim, domain, senderId } = options;
404
- const dkimOptions = dkim ? Array.isArray(dkim) ? dkim : [dkim] : void 0;
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 = createError("A maximum of 10 DKIM settings can be provided.");
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 = createError("DKIM settings with a privateKey must also include a selector.");
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(({ domain, privateKey, selector }) => ({
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 = createError("Selector must be between 1 and 63 characters.");
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 = createError("Selector must be between 1 and 63 characters.");
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 update(domain, options) {
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 = createError("Selector must be between 1 and 63 characters.");
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 = createError("Selector must be between 1 and 63 characters.");
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 = createError("New key selector must be between 1 and 63 characters.");
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 enroll(endpoint) {
757
+ async create(endpoint) {
691
758
  let error = null;
692
759
  if (!endpoint) {
693
- error = createError("No endpoint provided.");
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 = createError("The endpoint exceeds the maximum length of 8000 characters.");
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 enroll webhook.");
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 = createError("The request id should not exceed 28 characters.");
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 = createError("Invalid webhook signature.");
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 = createError("Invalid webhook payload.");
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) => ({ event: event.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 = createError("Invalid webhook payload.");
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: createError("A maximum of 6 status filters can be provided.")
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: createError("Status filters must be unique.")
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: createError("createdAfter must be a valid date string.")
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: createError("createdBefore must be a valid date string.")
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: createError("createdBefore must be later than createdAfter.")
953
+ error: createValidationError("createdBefore must be later than createdAfter.")
873
954
  };
874
955
  if (createdBefore - createdAfter > maxRangeMs) return {
875
956
  data: null,
876
- error: createError("The time range between createdAfter and createdBefore must not exceed 31 days.")
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 = createError("Invalid company name. Company name must be between 3 and 128 characters.");
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 = createError("Invalid handle. Sub-account handle must be between 3 and 128 characters and contain only lowercase letters and numbers.");
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 = createError("No handle provided.");
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 = createError("No handle provided.");
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 = createError("No handle provided.");
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 = createError("No handle provided.");
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 = createError("No handle provided.");
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 = createError("No handle provided.");
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 = createError("No handle provided.");
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 = createError("No handle provided.");
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 = createError("No handle provided.");
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 = createError("No handle provided.");
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 = createError("No handle provided.");
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 = createError("The sends value must be at least 0.");
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 = createError("No handle provided.");
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 = createError("No handle provided.");
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: mapBuckets(response.buckets.click),
1381
- clickTrackingDelivered: mapBuckets(response.buckets.click_tracking_delivered),
1382
- open: mapBuckets(response.buckets.open),
1383
- openTrackingDelivered: mapBuckets(response.buckets.open_tracking_delivered)
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: mapBuckets(response.buckets.bounced),
1420
- delivered: mapBuckets(response.buckets.delivered),
1421
- processed: mapBuckets(response.buckets.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: mapBuckets(response.buckets.unsubscribe_delivered),
1455
- unsubscribed: mapBuckets(response.buckets.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: mapBuckets(response.buckets.delivered),
1489
- dropped: mapBuckets(response.buckets.dropped),
1490
- processed: mapBuckets(response.buckets.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 = createError("The number of suppression entries must not exceed 1000.");
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 = createError("The recipient must not exceed 255 characters.");
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.8.0-1",
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
- "node",
12
- "sdk",
13
- "email"
10
+ "email",
11
+ "transactional-email",
12
+ "sdk"
14
13
  ],
15
14
  "repository": {
16
15
  "type": "git",
17
- "url": "git+https://github.com/Yizack/mailchannels.git"
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": "Yizack Rangel",
22
- "email": "yizackr@gmail.com",
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.8.0",
47
- "@vitest/coverage-v8": "^4.1.6",
45
+ "@types/node": "^25.9.2",
46
+ "@vitest/coverage-v8": "^4.1.8",
48
47
  "changelogen": "^0.6.2",
49
- "obuild": "^0.4.35",
50
- "oxlint": "^1.65.0",
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.12.2",
56
- "vitest": "^4.1.6"
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.mjs",
61
- "release": "pnpm lint && pnpm test && pnpm build && changelogen --release && git push --follow-tags",
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",