mailchannels-sdk 0.8.0-1 → 0.8.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
@@ -4,7 +4,6 @@
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]
8
7
 
9
8
  > Built and tested against Email API `0.21.1`
10
9
 
@@ -109,7 +108,7 @@ This package includes a local MailChannels simulator you can run via the CLI. It
109
108
 
110
109
  | API | Source |
111
110
  | ----------- | -------------------------------------------------------------------------------------------------------------- |
112
- | Email API | [`src/simulator/email-api.mjs`](https://github.com/Yizack/mailchannels/blob/main/src/simulator/email-api.mjs) |
111
+ | Email API | [`src/simulator/email-api.mjs`](https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js/src/main/src/simulator/email-api.mjs) |
113
112
 
114
113
  > [!IMPORTANT]
115
114
  > The simulator approximates the MailChannels service for local development and testing. It is not a production implementation and may differ from the live service.
@@ -212,7 +211,7 @@ pnpm parity:fixtures
212
211
  pnpm simulate
213
212
 
214
213
  # Run a playground script
215
- npx jiti playground/emails/send.ts
214
+ pnpx jiti playground/emails/send.ts
216
215
 
217
216
  # Release new version
218
217
  pnpm release
@@ -226,6 +225,3 @@ pnpm release
226
225
 
227
226
  [npm-downloads-src]: https://img.shields.io/npm/dm/mailchannels-sdk.svg?style=flat&colorA=070a30&colorB=35a047
228
227
  [npm-downloads-href]: https://npmjs.com/package/mailchannels-sdk
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
@@ -20,13 +20,24 @@ 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>;
@@ -96,7 +107,15 @@ interface EmailsSendAttachment {
96
107
  /**
97
108
  * The MIME type of the attachment.
98
109
  */
99
- type: string;
110
+ type?: string;
111
+ /**
112
+ * The `Content-ID` header value for inline attachments, referenced from HTML with `cid:`.
113
+ */
114
+ contentId?: string;
115
+ /**
116
+ * The `Content-Disposition` header value for the attachment.
117
+ */
118
+ disposition?: "attachment" | "inline";
100
119
  }
101
120
  interface EmailsSendTracking {
102
121
  /**
@@ -211,7 +230,7 @@ interface EmailsSendOptionsBase {
211
230
  /**
212
231
  * An array of attachments to be sent with the email.
213
232
  */
214
- attachments?: EmailsSendAttachment[];
233
+ attachments?: (EmailsSendAttachment | Promise<EmailsSendAttachment>)[];
215
234
  /**
216
235
  * 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
236
  */
@@ -438,7 +457,7 @@ type EmailsSendResponse = DataResponse<{
438
457
  status: "sent" | "failed";
439
458
  }[];
440
459
  }>;
441
- type EmailsSendAsyncResponse = DataResponse<{
460
+ type EmailsQueueResponse = DataResponse<{
442
461
  /**
443
462
  * ISO 8601 timestamp when the request was queued for processing.
444
463
  */
@@ -448,6 +467,7 @@ type EmailsSendAsyncResponse = DataResponse<{
448
467
  */
449
468
  requestId: string;
450
469
  }>;
470
+ type EmailsSendAsyncResponse = EmailsQueueResponse;
451
471
  declare class Emails {
452
472
  protected mailchannels: MailChannelsClient;
453
473
  constructor(mailchannels: MailChannelsClient);
@@ -478,7 +498,7 @@ declare class Emails {
478
498
  * @example
479
499
  * ```ts
480
500
  * const mailchannels = new MailChannels('your-api-key')
481
- * const { data, error } = await mailchannels.emails.sendAsync({
501
+ * const { data, error } = await mailchannels.emails.queue({
482
502
  * to: 'to@example.com',
483
503
  * from: 'from@example.com',
484
504
  * subject: 'Test',
@@ -486,7 +506,11 @@ declare class Emails {
486
506
  * })
487
507
  * ```
488
508
  */
489
- sendAsync(options: EmailsSendOptions): Promise<EmailsSendAsyncResponse>;
509
+ queue(options: EmailsSendOptions): Promise<EmailsQueueResponse>;
510
+ /**
511
+ * @deprecated Use `queue` instead.
512
+ */
513
+ sendAsync(options: EmailsSendOptions): Promise<EmailsQueueResponse>;
490
514
  }
491
515
  interface DomainsDkimCreateOptions {
492
516
  /**
@@ -578,10 +602,6 @@ interface DomainsCheckOptions {
578
602
  * 6. If `selector` is present and `domain` is not, the domain will be taken from the domain field of the request.
579
603
  */
580
604
  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
605
  /**
586
606
  * 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
607
  */
@@ -676,7 +696,7 @@ interface DomainsDkimListOptions {
676
696
  }
677
697
  type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
678
698
  type DomainsDkimListResponse = DataResponse<Optional<DomainsDkimKey, "dnsRecords">[]>;
679
- interface DomainsDkimUpdateOptions {
699
+ interface DomainsDkimUpdateStatusOptions {
680
700
  /**
681
701
  * Selector of the DKIM key pair to update. Must be a maximum of 63 characters.
682
702
  */
@@ -707,22 +727,22 @@ declare class Domains {
707
727
  constructor(mailchannels: MailChannelsClient);
708
728
  /**
709
729
  * 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.
730
+ * @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
731
  * @param options - The domain options to check.
711
732
  * @example
712
733
  * ```ts
713
734
  * const mailchannels = new MailChannels('your-api-key')
714
- * const { data, error } = await mailchannels.domains.check({
735
+ * const { data, error } = await mailchannels.domains.check('example.com', {
715
736
  * dkim: [{
716
737
  * domain: 'example.com',
717
738
  * privateKey: 'your-private-key',
718
739
  * selector: 'mailchannels'
719
740
  * }],
720
- * domain: 'example.com',
721
741
  * senderId: 'sender-id'
722
742
  * })
723
743
  * ```
724
744
  */
725
- check(options: DomainsCheckOptions): Promise<DomainsCheckResponse>;
745
+ check(domain: string, options?: DomainsCheckOptions): Promise<DomainsCheckResponse>;
726
746
  }
727
747
  declare class DomainsDkim {
728
748
  private mailchannels;
@@ -760,12 +780,12 @@ declare class DomainsDkim {
760
780
  * @example
761
781
  * ```ts
762
782
  * const mailchannels = new MailChannels('your-api-key')
763
- * const { success, error } = await mailchannels.domains.dkim.update('example.com', {
783
+ * const { success, error } = await mailchannels.domains.dkim.updateStatus('example.com', {
764
784
  * selector: 'mailchannels',
765
785
  * status: 'retired'
766
786
  * })
767
787
  */
768
- update(domain: string, options: DomainsDkimUpdateOptions): Promise<SuccessResponse>;
788
+ updateStatus(domain: string, options: DomainsDkimUpdateStatusOptions): Promise<SuccessResponse>;
769
789
  /**
770
790
  * 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
791
  * @param domain - The domain the DKIM key belongs to.
@@ -842,7 +862,7 @@ interface WebhookEventBase<T extends WebhookEventType> {
842
862
  * The MailChannels account ID that generated the webhook.
843
863
  * If the message was sent by a sub-account, this field contains the sub-account handle.
844
864
  */
845
- customer_handle: string;
865
+ customerHandle: string;
846
866
  /**
847
867
  * The Unix timestamp (in seconds) when the event occurred; the timezone is always UTC
848
868
  */
@@ -850,7 +870,7 @@ interface WebhookEventBase<T extends WebhookEventType> {
850
870
  /**
851
871
  * The Message-Id of the message that generated the event
852
872
  */
853
- smtp_id?: string;
873
+ smtpId?: string;
854
874
  /**
855
875
  * The type of event that occurred
856
876
  */
@@ -858,11 +878,11 @@ interface WebhookEventBase<T extends WebhookEventType> {
858
878
  /**
859
879
  * A unique identifier generated to track the original HTTP request
860
880
  */
861
- request_id?: string;
881
+ requestId?: string;
862
882
  /**
863
883
  * The campaign identifier for the message that generated the event
864
884
  */
865
- campaign_id?: string;
885
+ campaignId?: string;
866
886
  /**
867
887
  * The recipients of the message
868
888
  */
@@ -874,7 +894,7 @@ interface WebhookEventWithTracking {
874
894
  /**
875
895
  * The User-Agent header given when the recipient opened the message
876
896
  */
877
- user_agent?: string;
897
+ userAgent?: string;
878
898
  /**
879
899
  * The IP address of the host that made the HTTP request
880
900
  */
@@ -902,9 +922,8 @@ interface WebhookEventSoftBounced extends WebhookEventBase<"soft-bounced">, Webh
902
922
  interface WebhookEventDropped extends WebhookEventBase<"dropped">, WebhookEventWithStatus {}
903
923
  interface WebhookEventComplained extends WebhookEventBase<"complained"> {}
904
924
  interface WebhookEventUnsubscribed extends WebhookEventBase<"unsubscribed"> {}
905
- interface WebhookEventTest extends Omit<WebhookEventBase<"test">, "recipients" | "campaign_id"> {}
925
+ interface WebhookEventTest extends Omit<WebhookEventBase<"test">, "recipients" | "campaignId"> {}
906
926
  type WebhookEvent = WebhookEventProcessed | WebhookEventDelivered | WebhookEventOpen | WebhookEventClick | WebhookEventHardBounced | WebhookEventSoftBounced | WebhookEventDropped | WebhookEventComplained | WebhookEventUnsubscribed | WebhookEventTest;
907
- type WebhookEvents = WebhookEvent[];
908
927
  interface WebhooksVerifyOptions {
909
928
  /**
910
929
  * 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 +947,7 @@ interface WebhooksVerifyOptions {
928
947
  */
929
948
  cache?: boolean;
930
949
  }
931
- type WebhooksVerifyResponse = DataResponse<{
932
- /**
933
- * The type of event that occurred.
934
- */
935
- event: WebhookEventType;
936
- }[]>;
950
+ type WebhooksVerifyResponse = DataResponse<WebhookEvent[]>;
937
951
  type WebhooksBatchStatus = "1xx" | "2xx" | "3xx" | "4xx" | "5xx" | "no_response";
938
952
  type WebhooksBatchResponseStatus = "1xx_response" | "2xx_response" | "3xx_response" | "4xx_response" | "5xx_response" | "no_response";
939
953
  interface WebhooksBatchesOptions {
@@ -1042,10 +1056,10 @@ declare class Webhooks {
1042
1056
  * @example
1043
1057
  * ```ts
1044
1058
  * const mailchannels = new MailChannels('your-api-key')
1045
- * const { success, error } = mailchannels.webhooks.enroll('https://example.com/api/webhooks/mailchannels')
1059
+ * const { success, error } = await mailchannels.webhooks.create('https://example.com/api/webhooks/mailchannels')
1046
1060
  * ```
1047
1061
  */
1048
- enroll(endpoint: string): Promise<SuccessResponse>;
1062
+ create(endpoint: string): Promise<SuccessResponse>;
1049
1063
  /**
1050
1064
  * Retrieves all registered webhook endpoints associated with the customer.
1051
1065
  * @example
@@ -1743,6 +1757,13 @@ declare class Suppressions {
1743
1757
  */
1744
1758
  list(options?: SuppressionsListOptions): Promise<SuppressionsListResponse>;
1745
1759
  }
1760
+ type AttachmentOptions = Omit<EmailsSendAttachment, "content">;
1761
+ declare class Attachment {
1762
+ static fromBytes(data: ArrayBuffer | Uint8Array, options: AttachmentOptions): EmailsSendAttachment;
1763
+ static fromFile(path: string | URL, options?: Partial<AttachmentOptions>): Promise<EmailsSendAttachment>;
1764
+ static fromUrl(url: string, options?: Partial<AttachmentOptions>): Promise<EmailsSendAttachment>;
1765
+ static inlineFile(path: string | URL, options?: Partial<Omit<AttachmentOptions, "disposition">>): Promise<EmailsSendAttachment>;
1766
+ }
1746
1767
  declare class MailChannels extends MailChannelsClient {
1747
1768
  readonly emails: Emails;
1748
1769
  readonly domains: Domains;
@@ -1752,4 +1773,4 @@ declare class MailChannels extends MailChannelsClient {
1752
1773
  readonly suppressions: Suppressions;
1753
1774
  constructor(key: string, options?: MailChannelsClientOptions);
1754
1775
  }
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 };
1776
+ 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, 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,22 @@
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 { basename } from "node:path";
5
+ import { readFile } from "node:fs/promises";
6
+ import mime from "mime";
7
+ var version = "0.8.0";
5
8
  var MailChannelsClient = class MailChannelsClient {
6
9
  static DEFAULT_BASE_URL = "https://api.mailchannels.net";
10
+ static DEFAULT_TIMEOUT = 12e4;
7
11
  options;
8
12
  #headers;
9
13
  constructor(key, options = {}) {
10
14
  if (!key) throw new Error("Missing MailChannels API key.");
11
15
  this.options = {
12
16
  baseUrl: options.baseUrl || MailChannelsClient.DEFAULT_BASE_URL,
13
- retry: options.retry ?? false
17
+ retry: options.retry ?? false,
18
+ signal: options.signal,
19
+ timeout: options.timeout ?? MailChannelsClient.DEFAULT_TIMEOUT
14
20
  };
15
21
  this.#headers = {
16
22
  "X-API-Key": key,
@@ -20,13 +26,16 @@ var MailChannelsClient = class MailChannelsClient {
20
26
  };
21
27
  }
22
28
  async _fetch(path, options) {
29
+ const { headers, signal = this.options.signal, ...fetchOptions } = options;
23
30
  return $fetch(path, {
24
31
  baseURL: this.options.baseUrl,
25
32
  retry: this.options.retry,
26
- ...options,
33
+ signal,
34
+ timeout: this.options.timeout === false ? void 0 : this.options.timeout,
35
+ ...fetchOptions,
27
36
  headers: {
28
37
  ...this.#headers,
29
- ...options?.headers
38
+ ...headers
30
39
  }
31
40
  });
32
41
  }
@@ -85,7 +94,6 @@ const validatePagination = (pagination = {}) => {
85
94
  if (typeof offset === "number" && offset < 0) return createError("Offset must be greater than or equal to 0.");
86
95
  return null;
87
96
  };
88
- const stripPemHeaders = (pem) => pem.replace(/-----[^-]+-----|\s|#.*$/gm, "");
89
97
  const clean = (data) => {
90
98
  if (Array.isArray(data)) {
91
99
  const result = [];
@@ -108,25 +116,6 @@ const clean = (data) => {
108
116
  }
109
117
  return data;
110
118
  };
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
119
  const isValidEmail = (email) => {
131
120
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
132
121
  };
@@ -157,6 +146,7 @@ const parseArrayRecipients = (recipients) => {
157
146
  const filtered = (typeof recipients === "string" ? [parseRecipientString(recipients)] : Array.isArray(recipients) ? recipients.map(parseRecipient) : [recipients]).filter((recipient) => Boolean(recipient));
158
147
  return filtered.length > 0 ? filtered : void 0;
159
148
  };
149
+ const stripPemHeaders = (pem) => pem.replace(/-----[^-]+-----|\s|#.*$/gm, "");
160
150
  const RESERVED_HEADER_NAMES = new Set([
161
151
  "authentication-results",
162
152
  "bcc",
@@ -207,6 +197,13 @@ const mapDkim = (dkim) => ({
207
197
  dkim_private_key: dkim?.privateKey ? stripPemHeaders(dkim.privateKey) : void 0,
208
198
  dkim_selector: dkim?.selector
209
199
  });
200
+ const mapAttachment = (attachment) => ({
201
+ content: attachment.content,
202
+ filename: attachment.filename,
203
+ type: attachment.type,
204
+ content_id: attachment.contentId,
205
+ disposition: attachment.disposition
206
+ });
210
207
  const mapPersonalization = (personalization, index, rootTemplateData) => {
211
208
  const to = parseArrayRecipients(personalization.to);
212
209
  if (!to || !to.length) return `Personalization at index ${index} must include at least one recipient in the 'to' field.`;
@@ -238,7 +235,11 @@ const mapPersonalization = (personalization, index, rootTemplateData) => {
238
235
  to
239
236
  };
240
237
  };
241
- const buildSendPayload = (options) => {
238
+ const resolveAttachments = async (attachments) => {
239
+ if (!attachments) return;
240
+ return Promise.all(attachments.map((a) => Promise.resolve(a))).catch((e) => e.message);
241
+ };
242
+ const buildSendPayload = async (options) => {
242
243
  const { from, html, text } = options;
243
244
  const contentTypes = options.content ? new Set(options.content.map((item) => item.type.toLowerCase())) : void 0;
244
245
  const parsedFrom = parseRecipient(from);
@@ -307,8 +308,10 @@ const buildSendPayload = (options) => {
307
308
  value: item.value,
308
309
  template_type
309
310
  });
311
+ const resolvedAttachments = await resolveAttachments(options.attachments);
312
+ if (typeof resolvedAttachments === "string") return resolvedAttachments;
310
313
  return {
311
- attachments: options.attachments,
314
+ attachments: resolvedAttachments?.map((a) => mapAttachment(a)),
312
315
  campaign_id: options.campaignId,
313
316
  ...mapDkim(options.dkim),
314
317
  envelope_from: parseRecipient(options.envelopeFrom),
@@ -332,7 +335,7 @@ var Emails = class {
332
335
  }
333
336
  async _sendEmail(options, flags) {
334
337
  let error = null;
335
- const payload = buildSendPayload(options);
338
+ const payload = await buildSendPayload(options);
336
339
  if (typeof payload === "string") {
337
340
  error = createError(payload);
338
341
  return {
@@ -387,10 +390,26 @@ var Emails = class {
387
390
  async send(options, dryRun = false) {
388
391
  return this._sendEmail(options, { dryRun });
389
392
  }
390
- async sendAsync(options) {
393
+ async queue(options) {
391
394
  return this._sendEmail(options, { async: true });
392
395
  }
396
+ async sendAsync(options) {
397
+ return this.queue(options);
398
+ }
393
399
  };
400
+ const mapDkimKey = (key) => ({
401
+ algorithm: key.algorithm,
402
+ createdAt: key.created_at,
403
+ dnsRecords: key.dkim_dns_records,
404
+ domain: key.domain,
405
+ gracePeriodExpiresAt: key.gracePeriodExpiresAt,
406
+ length: key.key_length,
407
+ publicKey: key.public_key,
408
+ retiresAt: key.retiresAt,
409
+ selector: key.selector,
410
+ status: key.status,
411
+ statusModifiedAt: key.status_modified_at
412
+ });
394
413
  var Domains = class {
395
414
  mailchannels;
396
415
  dkim;
@@ -398,10 +417,16 @@ var Domains = class {
398
417
  this.mailchannels = mailchannels;
399
418
  this.dkim = new DomainsDkim(mailchannels);
400
419
  }
401
- async check(options) {
420
+ async check(domain, options) {
402
421
  let error = null;
403
- const { dkim, domain, senderId } = options;
404
- const dkimOptions = dkim ? Array.isArray(dkim) ? dkim : [dkim] : void 0;
422
+ if (!domain) {
423
+ error = createError("No domain provided.");
424
+ return {
425
+ data: null,
426
+ error
427
+ };
428
+ }
429
+ const dkimOptions = options?.dkim ? Array.isArray(options.dkim) ? options.dkim : [options.dkim] : void 0;
405
430
  if (dkimOptions && dkimOptions.length > 10) {
406
431
  error = createError("A maximum of 10 DKIM settings can be provided.");
407
432
  return {
@@ -417,13 +442,13 @@ var Domains = class {
417
442
  };
418
443
  }
419
444
  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
445
+ dkim_settings: dkimOptions?.map((dkim) => ({
446
+ dkim_domain: dkim.domain,
447
+ dkim_private_key: dkim.privateKey ? stripPemHeaders(dkim.privateKey) : void 0,
448
+ dkim_selector: dkim.selector
424
449
  })),
425
450
  domain,
426
- sender_id: senderId
451
+ sender_id: options?.senderId
427
452
  };
428
453
  const response = await this.mailchannels.post("/tx/v1/check-domain", {
429
454
  body: payload,
@@ -466,6 +491,13 @@ var DomainsDkim = class {
466
491
  }
467
492
  async create(domain, options) {
468
493
  let error = null;
494
+ if (!domain) {
495
+ error = createError("No domain provided.");
496
+ return {
497
+ data: null,
498
+ error
499
+ };
500
+ }
469
501
  if (!options.selector || options.selector.length > 63) {
470
502
  error = createError("Selector must be between 1 and 63 characters.");
471
503
  return {
@@ -501,6 +533,13 @@ var DomainsDkim = class {
501
533
  }
502
534
  async list(domain, options) {
503
535
  let error = null;
536
+ if (!domain) {
537
+ error = createError("No domain provided.");
538
+ return {
539
+ data: null,
540
+ error
541
+ };
542
+ }
504
543
  if (options?.selector && options.selector.length > 63) {
505
544
  error = createError("Selector must be between 1 and 63 characters.");
506
545
  return {
@@ -541,8 +580,15 @@ var DomainsDkim = class {
541
580
  error: null
542
581
  };
543
582
  }
544
- async update(domain, options) {
583
+ async updateStatus(domain, options) {
545
584
  let error = null;
585
+ if (!domain) {
586
+ error = createError("No domain provided.");
587
+ return {
588
+ success: false,
589
+ error
590
+ };
591
+ }
546
592
  if (!options.selector || options.selector.length > 63) {
547
593
  error = createError("Selector must be between 1 and 63 characters.");
548
594
  return {
@@ -560,7 +606,7 @@ var DomainsDkim = class {
560
606
  });
561
607
  }
562
608
  }).catch((e) => {
563
- error ||= getResultError(e, "Failed to update DKIM key.");
609
+ error ||= getResultError(e, "Failed to update status of DKIM key.");
564
610
  });
565
611
  return {
566
612
  success: !error,
@@ -569,6 +615,13 @@ var DomainsDkim = class {
569
615
  }
570
616
  async rotate(domain, selector, options) {
571
617
  let error = null;
618
+ if (!domain) {
619
+ error = createError("No domain provided.");
620
+ return {
621
+ data: null,
622
+ error
623
+ };
624
+ }
572
625
  if (!selector || selector.length > 63) {
573
626
  error = createError("Selector must be between 1 and 63 characters.");
574
627
  return {
@@ -687,7 +740,7 @@ var Webhooks = class Webhooks {
687
740
  constructor(mailchannels) {
688
741
  this.mailchannels = mailchannels;
689
742
  }
690
- async enroll(endpoint) {
743
+ async create(endpoint) {
691
744
  let error = null;
692
745
  if (!endpoint) {
693
746
  error = createError("No endpoint provided.");
@@ -709,7 +762,7 @@ var Webhooks = class Webhooks {
709
762
  error = getStatusError(response, { [409]: `Endpoint '${endpoint}' is already enrolled to receive notifications.` });
710
763
  }
711
764
  }).catch((e) => {
712
- error ||= getResultError(e, "Failed to enroll webhook.");
765
+ error ||= getResultError(e, "Failed to create webhook.");
713
766
  });
714
767
  return {
715
768
  success: !error,
@@ -823,7 +876,21 @@ var Webhooks = class Webhooks {
823
876
  };
824
877
  }
825
878
  return {
826
- data: clean(payload.map((event) => ({ event: event.event }))),
879
+ data: clean(payload.map((event) => ({
880
+ email: event.email,
881
+ customerHandle: event.customer_handle,
882
+ timestamp: event.timestamp,
883
+ smtpId: event.smtp_id,
884
+ event: event.event,
885
+ requestId: event.request_id,
886
+ campaignId: "campaign_id" in event ? event.campaign_id : void 0,
887
+ recipients: "recipients" in event ? event.recipients : void 0,
888
+ userAgent: "user_agent" in event ? event.user_agent : void 0,
889
+ ip: "ip" in event ? event.ip : void 0,
890
+ url: "url" in event ? event.url : void 0,
891
+ status: "status" in event ? event.status : void 0,
892
+ reason: "reason" in event ? event.reason : void 0
893
+ }))),
827
894
  error: null
828
895
  };
829
896
  } catch {
@@ -1349,6 +1416,12 @@ var SubAccounts = class SubAccounts {
1349
1416
  };
1350
1417
  }
1351
1418
  };
1419
+ const mapBucket = (bucket) => {
1420
+ return {
1421
+ count: bucket.count,
1422
+ periodStart: bucket.period_start
1423
+ };
1424
+ };
1352
1425
  var Metrics = class {
1353
1426
  mailchannels;
1354
1427
  constructor(mailchannels) {
@@ -1377,10 +1450,10 @@ var Metrics = class {
1377
1450
  return {
1378
1451
  data: clean({
1379
1452
  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)
1453
+ click: response.buckets.click.map(mapBucket),
1454
+ clickTrackingDelivered: response.buckets.click_tracking_delivered.map(mapBucket),
1455
+ open: response.buckets.open.map(mapBucket),
1456
+ openTrackingDelivered: response.buckets.open_tracking_delivered.map(mapBucket)
1384
1457
  },
1385
1458
  click: response.click,
1386
1459
  clickTrackingDelivered: response.click_tracking_delivered,
@@ -1416,9 +1489,9 @@ var Metrics = class {
1416
1489
  data: clean({
1417
1490
  bounced: response.bounced,
1418
1491
  buckets: {
1419
- bounced: mapBuckets(response.buckets.bounced),
1420
- delivered: mapBuckets(response.buckets.delivered),
1421
- processed: mapBuckets(response.buckets.processed)
1492
+ bounced: response.buckets.bounced.map(mapBucket),
1493
+ delivered: response.buckets.delivered.map(mapBucket),
1494
+ processed: response.buckets.processed.map(mapBucket)
1422
1495
  },
1423
1496
  delivered: response.delivered,
1424
1497
  endTime: response.end_time,
@@ -1451,8 +1524,8 @@ var Metrics = class {
1451
1524
  return {
1452
1525
  data: clean({
1453
1526
  buckets: {
1454
- unsubscribeDelivered: mapBuckets(response.buckets.unsubscribe_delivered),
1455
- unsubscribed: mapBuckets(response.buckets.unsubscribed)
1527
+ unsubscribeDelivered: response.buckets.unsubscribe_delivered.map(mapBucket),
1528
+ unsubscribed: response.buckets.unsubscribed.map(mapBucket)
1456
1529
  },
1457
1530
  endTime: response.end_time,
1458
1531
  startTime: response.start_time,
@@ -1485,9 +1558,9 @@ var Metrics = class {
1485
1558
  return {
1486
1559
  data: clean({
1487
1560
  buckets: {
1488
- delivered: mapBuckets(response.buckets.delivered),
1489
- dropped: mapBuckets(response.buckets.dropped),
1490
- processed: mapBuckets(response.buckets.processed)
1561
+ delivered: response.buckets.delivered.map(mapBucket),
1562
+ dropped: response.buckets.dropped.map(mapBucket),
1563
+ processed: response.buckets.processed.map(mapBucket)
1491
1564
  },
1492
1565
  delivered: response.delivered,
1493
1566
  dropped: response.dropped,
@@ -1667,6 +1740,62 @@ var Suppressions = class {
1667
1740
  };
1668
1741
  }
1669
1742
  };
1743
+ const base64Content = (data) => {
1744
+ if (data instanceof ArrayBuffer) return Buffer.from(data).toString("base64");
1745
+ return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("base64");
1746
+ };
1747
+ const guessContentType = (filename) => {
1748
+ return mime.getType(filename) || void 0;
1749
+ };
1750
+ var Attachment = class Attachment {
1751
+ static fromBytes(data, options) {
1752
+ const { filename, type, contentId, disposition = "attachment" } = options;
1753
+ return {
1754
+ content: base64Content(data),
1755
+ filename: decodeURIComponent(filename) || "attachment",
1756
+ type: type || guessContentType(filename),
1757
+ contentId,
1758
+ disposition
1759
+ };
1760
+ }
1761
+ static async fromFile(path, options) {
1762
+ try {
1763
+ const data = await readFile(path);
1764
+ return Attachment.fromBytes(data, {
1765
+ filename: basename(path.toString()),
1766
+ ...options
1767
+ });
1768
+ } catch (error) {
1769
+ throw new Error(`Unable to read attachment file: ${path}`, { cause: error });
1770
+ }
1771
+ }
1772
+ static async fromUrl(url, options) {
1773
+ try {
1774
+ let contentType;
1775
+ const data = await $fetch(url, {
1776
+ responseType: "arrayBuffer",
1777
+ timeout: 12e4,
1778
+ onResponse: ({ response }) => {
1779
+ const contentTypeHeader = response.headers.get("content-type");
1780
+ if (contentTypeHeader) contentType = contentTypeHeader.split(";", 1)[0]?.trim();
1781
+ }
1782
+ });
1783
+ return Attachment.fromBytes(data, {
1784
+ type: contentType,
1785
+ filename: basename(new URL(url).pathname),
1786
+ ...options
1787
+ });
1788
+ } catch (error) {
1789
+ throw new Error(`Unable to fetch attachment from URL: ${url}`, { cause: error });
1790
+ }
1791
+ }
1792
+ static inlineFile(path, options) {
1793
+ return Attachment.fromFile(path, {
1794
+ ...options,
1795
+ disposition: "inline"
1796
+ });
1797
+ }
1798
+ };
1670
1799
  var MailChannels = class extends MailChannelsClient {
1671
1800
  emails = new Emails(this);
1672
1801
  domains = new Domains(this);
@@ -1678,4 +1807,4 @@ var MailChannels = class extends MailChannelsClient {
1678
1807
  super(key, options);
1679
1808
  }
1680
1809
  };
1681
- export { Domains, Emails, MailChannels, MailChannelsClient, Metrics, SubAccounts, Suppressions, Webhooks };
1810
+ 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": "0.8.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,22 +36,26 @@
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.1",
46
+ "@vitest/coverage-v8": "^4.1.7",
48
47
  "changelogen": "^0.6.2",
49
- "obuild": "^0.4.35",
50
- "oxlint": "^1.65.0",
48
+ "obuild": "^0.4.36",
49
+ "oxlint": "^1.67.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.0",
55
+ "vitest": "^4.1.7"
56
+ },
57
+ "engines": {
58
+ "node": ">=20"
57
59
  },
58
60
  "scripts": {
59
61
  "build": "obuild",