mailchannels-sdk 0.8.0 → 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/README.md CHANGED
@@ -1,11 +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
+ [![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]
7
11
 
8
- > Built and tested against Email API `0.21.1`
12
+ > Built and tested against Email API `1.0.0`
9
13
 
10
14
  Node.js SDK to integrate [MailChannels Email API](https://docs.mailchannels.net/email-api) into your JavaScript or TypeScript server-side applications.
11
15
 
@@ -13,7 +17,7 @@ Node.js SDK to integrate [MailChannels Email API](https://docs.mailchannels.net/
13
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.
14
18
  <!-- #endregion overview -->
15
19
 
16
- - [✨ Release Notes](CHANGELOG.md)
20
+ - [✨ Release Notes](https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js/src/HEAD/CHANGELOG.md)
17
21
  - [📖 Documentation](https://mailchannels.yizack.com)
18
22
 
19
23
  ## Contents
@@ -180,12 +184,11 @@ The next planned expansion is outbound webhook delivery so client applications c
180
184
 
181
185
  ## <a name="license">⚖️ License</a>
182
186
 
183
- [MIT License](LICENSE)
187
+ [MIT License](https://bitbucket.org/mailchannels/mailchannels-email-api-sdk-js/src/HEAD/LICENSE)
184
188
 
185
189
  ## <a name="development">💻 Development</a>
186
190
 
187
- <details>
188
- <summary>Local development</summary>
191
+ Local development
189
192
 
190
193
  ```sh
191
194
  # Install dependencies
@@ -204,7 +207,7 @@ pnpm test:watch
204
207
  # Run typecheck
205
208
  pnpm test:types
206
209
 
207
- # Refresh API parity fixtures
210
+ # Refresh API parity fixtures, specs, and README version note
208
211
  pnpm parity:fixtures
209
212
 
210
213
  # Run the local simulator
@@ -217,11 +220,21 @@ pnpx jiti playground/emails/send.ts
217
220
  pnpm release
218
221
  ```
219
222
 
220
- </details>
221
-
222
223
  <!-- Badges -->
223
224
  [npm-version-src]: https://img.shields.io/npm/v/mailchannels-sdk.svg?style=flat&colorA=070a30&colorB=35a047
224
225
  [npm-version-href]: https://npmjs.com/package/mailchannels-sdk
225
226
 
226
227
  [npm-downloads-src]: https://img.shields.io/npm/dm/mailchannels-sdk.svg?style=flat&colorA=070a30&colorB=35a047
227
228
  [npm-downloads-href]: https://npmjs.com/package/mailchannels-sdk
229
+
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
@@ -44,6 +44,7 @@ declare class MailChannelsClient {
44
44
  put<T>(path: string, options?: Omit<FetchOptions<"json">, "method">): Promise<T>;
45
45
  patch<T>(path: string, options?: Omit<FetchOptions<"json">, "method">): Promise<T>;
46
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";
47
48
  interface ErrorResponse {
48
49
  /**
49
50
  * A human-readable description of the error.
@@ -55,6 +56,12 @@ interface ErrorResponse {
55
56
  * This field is intended for diagnostic use only and should not be relied upon.
56
57
  */
57
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;
58
65
  }
59
66
  interface SuccessResponse {
60
67
  /**
@@ -1760,9 +1767,7 @@ declare class Suppressions {
1760
1767
  type AttachmentOptions = Omit<EmailsSendAttachment, "content">;
1761
1768
  declare class Attachment {
1762
1769
  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>;
1770
+ static fromBlob(blob: Blob, options: AttachmentOptions): Promise<EmailsSendAttachment>;
1766
1771
  }
1767
1772
  declare class MailChannels extends MailChannelsClient {
1768
1773
  readonly emails: Emails;
@@ -1773,4 +1778,4 @@ declare class MailChannels extends MailChannelsClient {
1773
1778
  readonly suppressions: Suppressions;
1774
1779
  constructor(key: string, options?: MailChannelsClientOptions);
1775
1780
  }
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 };
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,10 +1,8 @@
1
1
  import { $fetch } from "ofetch";
2
2
  import { subtle } from "node:crypto";
3
3
  import { Buffer } from "node:buffer";
4
- import { basename } from "node:path";
5
- import { readFile } from "node:fs/promises";
6
4
  import mime from "mime";
7
- var version = "0.8.0";
5
+ var version = "1.0.0";
8
6
  var MailChannelsClient = class MailChannelsClient {
9
7
  static DEFAULT_BASE_URL = "https://api.mailchannels.net";
10
8
  static DEFAULT_TIMEOUT = 12e4;
@@ -70,10 +68,22 @@ var MailChannelsClient = class MailChannelsClient {
70
68
  });
71
69
  }
72
70
  };
73
- const 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) => {
74
83
  return {
75
84
  message,
76
- statusCode
85
+ statusCode,
86
+ type
77
87
  };
78
88
  };
79
89
  const getStatusError = (response, errors = {}) => {
@@ -83,15 +93,18 @@ const getStatusError = (response, errors = {}) => {
83
93
  if (typeof payload === "string") details = payload;
84
94
  else if (payload?.message) details = payload.message;
85
95
  else if (Array.isArray(payload?.errors) && payload.errors?.length) details = payload.errors.join(", ");
86
- return createError(details ? `${statusText} ${details}` : statusText, response.status ?? null);
96
+ return createError(details ? `${statusText} ${details}` : statusText, response.status ?? null, STATUS_ERROR_TYPE_MAP[response.status] || "api_error");
87
97
  };
88
98
  const getResultError = (e, fallback) => {
89
- return createError(e instanceof Error ? e.message : fallback);
99
+ return createError(e instanceof Error ? e.message : fallback, null, "application_error");
100
+ };
101
+ const createValidationError = (message) => {
102
+ return createError(message, null, "validation_error");
90
103
  };
91
104
  const validatePagination = (pagination = {}) => {
92
105
  const { limit, offset, max } = pagination;
93
- if (typeof limit === "number" && (limit < 1 || max && limit > max)) return createError("The limit value " + (max ? `must be between 1 and ${max}.` : "is invalid. Only positive values are allowed."));
94
- 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.");
95
108
  return null;
96
109
  };
97
110
  const clean = (data) => {
@@ -243,7 +256,8 @@ const buildSendPayload = async (options) => {
243
256
  const { from, html, text } = options;
244
257
  const contentTypes = options.content ? new Set(options.content.map((item) => item.type.toLowerCase())) : void 0;
245
258
  const parsedFrom = parseRecipient(from);
246
- if (!parsedFrom || !parsedFrom.email) return "No sender provided. Use the `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.";
247
261
  if (!text && !html && (!options.content || !options.content.length)) return "No email content provided";
248
262
  if (html && contentTypes?.has("text/html")) return "Cannot provide both 'html' and a 'content' entry with type 'text/html'.";
249
263
  if (text && contentTypes?.has("text/plain")) return "Cannot provide both 'text' and a 'content' entry with type 'text/plain'.";
@@ -337,7 +351,7 @@ var Emails = class {
337
351
  let error = null;
338
352
  const payload = await buildSendPayload(options);
339
353
  if (typeof payload === "string") {
340
- error = createError(payload);
354
+ error = createValidationError(payload);
341
355
  return {
342
356
  data: null,
343
357
  error
@@ -420,7 +434,7 @@ var Domains = class {
420
434
  async check(domain, options) {
421
435
  let error = null;
422
436
  if (!domain) {
423
- error = createError("No domain provided.");
437
+ error = createValidationError("No domain provided.");
424
438
  return {
425
439
  data: null,
426
440
  error
@@ -428,14 +442,14 @@ var Domains = class {
428
442
  }
429
443
  const dkimOptions = options?.dkim ? Array.isArray(options.dkim) ? options.dkim : [options.dkim] : void 0;
430
444
  if (dkimOptions && dkimOptions.length > 10) {
431
- error = createError("A maximum of 10 DKIM settings can be provided.");
445
+ error = createValidationError("A maximum of 10 DKIM settings can be provided.");
432
446
  return {
433
447
  data: null,
434
448
  error
435
449
  };
436
450
  }
437
451
  if (dkimOptions?.find(({ privateKey, selector }) => privateKey && !selector)) {
438
- 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.");
439
453
  return {
440
454
  data: null,
441
455
  error
@@ -492,14 +506,14 @@ var DomainsDkim = class {
492
506
  async create(domain, options) {
493
507
  let error = null;
494
508
  if (!domain) {
495
- error = createError("No domain provided.");
509
+ error = createValidationError("No domain provided.");
496
510
  return {
497
511
  data: null,
498
512
  error
499
513
  };
500
514
  }
501
515
  if (!options.selector || options.selector.length > 63) {
502
- error = createError("Selector must be between 1 and 63 characters.");
516
+ error = createValidationError("Selector must be between 1 and 63 characters.");
503
517
  return {
504
518
  data: null,
505
519
  error
@@ -534,14 +548,14 @@ var DomainsDkim = class {
534
548
  async list(domain, options) {
535
549
  let error = null;
536
550
  if (!domain) {
537
- error = createError("No domain provided.");
551
+ error = createValidationError("No domain provided.");
538
552
  return {
539
553
  data: null,
540
554
  error
541
555
  };
542
556
  }
543
557
  if (options?.selector && options.selector.length > 63) {
544
- error = createError("Selector must be between 1 and 63 characters.");
558
+ error = createValidationError("Selector must be between 1 and 63 characters.");
545
559
  return {
546
560
  data: null,
547
561
  error
@@ -583,14 +597,14 @@ var DomainsDkim = class {
583
597
  async updateStatus(domain, options) {
584
598
  let error = null;
585
599
  if (!domain) {
586
- error = createError("No domain provided.");
600
+ error = createValidationError("No domain provided.");
587
601
  return {
588
602
  success: false,
589
603
  error
590
604
  };
591
605
  }
592
606
  if (!options.selector || options.selector.length > 63) {
593
- error = createError("Selector must be between 1 and 63 characters.");
607
+ error = createValidationError("Selector must be between 1 and 63 characters.");
594
608
  return {
595
609
  success: false,
596
610
  error
@@ -616,21 +630,21 @@ var DomainsDkim = class {
616
630
  async rotate(domain, selector, options) {
617
631
  let error = null;
618
632
  if (!domain) {
619
- error = createError("No domain provided.");
633
+ error = createValidationError("No domain provided.");
620
634
  return {
621
635
  data: null,
622
636
  error
623
637
  };
624
638
  }
625
639
  if (!selector || selector.length > 63) {
626
- error = createError("Selector must be between 1 and 63 characters.");
640
+ error = createValidationError("Selector must be between 1 and 63 characters.");
627
641
  return {
628
642
  data: null,
629
643
  error
630
644
  };
631
645
  }
632
646
  if (!options.newKey.selector || options.newKey.selector.length > 63) {
633
- 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.");
634
648
  return {
635
649
  data: null,
636
650
  error
@@ -743,14 +757,14 @@ var Webhooks = class Webhooks {
743
757
  async create(endpoint) {
744
758
  let error = null;
745
759
  if (!endpoint) {
746
- error = createError("No endpoint provided.");
760
+ error = createValidationError("No endpoint provided.");
747
761
  return {
748
762
  success: false,
749
763
  error
750
764
  };
751
765
  }
752
766
  if (endpoint.length > 8e3) {
753
- error = createError("The endpoint exceeds the maximum length of 8000 characters.");
767
+ error = createValidationError("The endpoint exceeds the maximum length of 8000 characters.");
754
768
  return {
755
769
  success: false,
756
770
  error
@@ -827,7 +841,7 @@ var Webhooks = class Webhooks {
827
841
  async validate(requestId) {
828
842
  let error = null;
829
843
  if (requestId && requestId.length > 28) {
830
- error = createError("The request id should not exceed 28 characters.");
844
+ error = createValidationError("The request id should not exceed 28 characters.");
831
845
  return {
832
846
  data: null,
833
847
  error
@@ -860,7 +874,7 @@ var Webhooks = class Webhooks {
860
874
  static async verify(options) {
861
875
  let error = null;
862
876
  if (!await isValidWebhook(options).catch(() => false)) {
863
- error = createError("Invalid webhook signature.");
877
+ error = createValidationError("Invalid webhook signature.");
864
878
  return {
865
879
  data: null,
866
880
  error
@@ -869,7 +883,7 @@ var Webhooks = class Webhooks {
869
883
  try {
870
884
  const payload = JSON.parse(options.payload);
871
885
  if (!Array.isArray(payload)) {
872
- error = createError("Invalid webhook payload.");
886
+ error = createValidationError("Invalid webhook payload.");
873
887
  return {
874
888
  data: null,
875
889
  error
@@ -894,7 +908,7 @@ var Webhooks = class Webhooks {
894
908
  error: null
895
909
  };
896
910
  } catch {
897
- error = createError("Invalid webhook payload.");
911
+ error = createValidationError("Invalid webhook payload.");
898
912
  return {
899
913
  data: null,
900
914
  error
@@ -916,19 +930,19 @@ var Webhooks = class Webhooks {
916
930
  };
917
931
  if (options?.statuses && options.statuses.length > 6) return {
918
932
  data: null,
919
- error: createError("A maximum of 6 status filters can be provided.")
933
+ error: createValidationError("A maximum of 6 status filters can be provided.")
920
934
  };
921
935
  if (options?.statuses && new Set(options.statuses).size !== options.statuses.length) return {
922
936
  data: null,
923
- error: createError("Status filters must be unique.")
937
+ error: createValidationError("Status filters must be unique.")
924
938
  };
925
939
  if (options?.createdAfter && Number.isNaN(Date.parse(options.createdAfter))) return {
926
940
  data: null,
927
- error: createError("createdAfter must be a valid date string.")
941
+ error: createValidationError("createdAfter must be a valid date string.")
928
942
  };
929
943
  if (options?.createdBefore && Number.isNaN(Date.parse(options.createdBefore))) return {
930
944
  data: null,
931
- error: createError("createdBefore must be a valid date string.")
945
+ error: createValidationError("createdBefore must be a valid date string.")
932
946
  };
933
947
  if (options?.createdAfter && options?.createdBefore) {
934
948
  const createdAfter = Date.parse(options.createdAfter);
@@ -936,11 +950,11 @@ var Webhooks = class Webhooks {
936
950
  const maxRangeMs = 744 * 60 * 60 * 1e3;
937
951
  if (createdBefore <= createdAfter) return {
938
952
  data: null,
939
- error: createError("createdBefore must be later than createdAfter.")
953
+ error: createValidationError("createdBefore must be later than createdAfter.")
940
954
  };
941
955
  if (createdBefore - createdAfter > maxRangeMs) return {
942
956
  data: null,
943
- 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.")
944
958
  };
945
959
  }
946
960
  const response = await this.mailchannels.get("/tx/v1/webhook-batch", {
@@ -1016,7 +1030,7 @@ var SubAccounts = class SubAccounts {
1016
1030
  async create(companyName, handle) {
1017
1031
  let error = null;
1018
1032
  if (!SubAccounts.COMPANY_PATTERN.test(companyName)) {
1019
- 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.");
1020
1034
  return {
1021
1035
  data: null,
1022
1036
  error
@@ -1024,7 +1038,7 @@ var SubAccounts = class SubAccounts {
1024
1038
  }
1025
1039
  if (handle) {
1026
1040
  if (!SubAccounts.HANDLE_PATTERN.test(handle)) {
1027
- error = createError("Invalid handle. Sub-account handle must be between 3 and 128 characters and contain only lowercase letters and numbers.");
1041
+ error = createValidationError("Invalid handle. Sub-account handle must be between 3 and 128 characters and contain only lowercase letters and numbers.");
1028
1042
  return {
1029
1043
  data: null,
1030
1044
  error
@@ -1094,7 +1108,7 @@ var SubAccounts = class SubAccounts {
1094
1108
  async delete(handle) {
1095
1109
  let error = null;
1096
1110
  if (!handle) {
1097
- error = createError("No handle provided.");
1111
+ error = createValidationError("No handle provided.");
1098
1112
  return {
1099
1113
  success: false,
1100
1114
  error
@@ -1113,7 +1127,7 @@ var SubAccounts = class SubAccounts {
1113
1127
  async suspend(handle) {
1114
1128
  let error = null;
1115
1129
  if (!handle) {
1116
- error = createError("No handle provided.");
1130
+ error = createValidationError("No handle provided.");
1117
1131
  return {
1118
1132
  success: false,
1119
1133
  error
@@ -1132,7 +1146,7 @@ var SubAccounts = class SubAccounts {
1132
1146
  async activate(handle) {
1133
1147
  let error = null;
1134
1148
  if (!handle) {
1135
- error = createError("No handle provided.");
1149
+ error = createValidationError("No handle provided.");
1136
1150
  return {
1137
1151
  success: false,
1138
1152
  error
@@ -1154,7 +1168,7 @@ var SubAccounts = class SubAccounts {
1154
1168
  async createApiKey(handle) {
1155
1169
  let error = null;
1156
1170
  if (!handle) {
1157
- error = createError("No handle provided.");
1171
+ error = createValidationError("No handle provided.");
1158
1172
  return {
1159
1173
  data: null,
1160
1174
  error
@@ -1182,7 +1196,7 @@ var SubAccounts = class SubAccounts {
1182
1196
  async listApiKeys(handle, options) {
1183
1197
  let error = null;
1184
1198
  if (!handle) {
1185
- error = createError("No handle provided.");
1199
+ error = createValidationError("No handle provided.");
1186
1200
  return {
1187
1201
  data: null,
1188
1202
  error
@@ -1217,7 +1231,7 @@ var SubAccounts = class SubAccounts {
1217
1231
  async deleteApiKey(handle, id) {
1218
1232
  let error = null;
1219
1233
  if (!handle) {
1220
- error = createError("No handle provided.");
1234
+ error = createValidationError("No handle provided.");
1221
1235
  return {
1222
1236
  success: false,
1223
1237
  error
@@ -1236,7 +1250,7 @@ var SubAccounts = class SubAccounts {
1236
1250
  async createSmtpPassword(handle) {
1237
1251
  let error = null;
1238
1252
  if (!handle) {
1239
- error = createError("No handle provided.");
1253
+ error = createValidationError("No handle provided.");
1240
1254
  return {
1241
1255
  data: null,
1242
1256
  error
@@ -1268,7 +1282,7 @@ var SubAccounts = class SubAccounts {
1268
1282
  async listSmtpPasswords(handle) {
1269
1283
  let error = null;
1270
1284
  if (!handle) {
1271
- error = createError("No handle provided.");
1285
+ error = createValidationError("No handle provided.");
1272
1286
  return {
1273
1287
  data: null,
1274
1288
  error
@@ -1296,7 +1310,7 @@ var SubAccounts = class SubAccounts {
1296
1310
  async deleteSmtpPassword(handle, id) {
1297
1311
  let error = null;
1298
1312
  if (!handle) {
1299
- error = createError("No handle provided.");
1313
+ error = createValidationError("No handle provided.");
1300
1314
  return {
1301
1315
  success: false,
1302
1316
  error
@@ -1315,7 +1329,7 @@ var SubAccounts = class SubAccounts {
1315
1329
  async getLimit(handle) {
1316
1330
  let error = null;
1317
1331
  if (!handle) {
1318
- error = createError("No handle provided.");
1332
+ error = createValidationError("No handle provided.");
1319
1333
  return {
1320
1334
  data: null,
1321
1335
  error
@@ -1339,14 +1353,14 @@ var SubAccounts = class SubAccounts {
1339
1353
  async setLimit(handle, limit) {
1340
1354
  let error = null;
1341
1355
  if (!handle) {
1342
- error = createError("No handle provided.");
1356
+ error = createValidationError("No handle provided.");
1343
1357
  return {
1344
1358
  success: false,
1345
1359
  error
1346
1360
  };
1347
1361
  }
1348
1362
  if (limit.sends < 0) {
1349
- error = createError("The sends value must be at least 0.");
1363
+ error = createValidationError("The sends value must be at least 0.");
1350
1364
  return {
1351
1365
  success: false,
1352
1366
  error
@@ -1371,7 +1385,7 @@ var SubAccounts = class SubAccounts {
1371
1385
  async deleteLimit(handle) {
1372
1386
  let error = null;
1373
1387
  if (!handle) {
1374
- error = createError("No handle provided.");
1388
+ error = createValidationError("No handle provided.");
1375
1389
  return {
1376
1390
  success: false,
1377
1391
  error
@@ -1390,7 +1404,7 @@ var SubAccounts = class SubAccounts {
1390
1404
  async getUsage(handle) {
1391
1405
  let error = null;
1392
1406
  if (!handle) {
1393
- error = createError("No handle provided.");
1407
+ error = createValidationError("No handle provided.");
1394
1408
  return {
1395
1409
  data: null,
1396
1410
  error
@@ -1643,7 +1657,7 @@ var Suppressions = class {
1643
1657
  let error = null;
1644
1658
  const { addToSubAccounts, entries } = options;
1645
1659
  if (entries.length > 1e3) {
1646
- error = createError("The number of suppression entries must not exceed 1000.");
1660
+ error = createValidationError("The number of suppression entries must not exceed 1000.");
1647
1661
  return {
1648
1662
  success: false,
1649
1663
  error
@@ -1692,7 +1706,7 @@ var Suppressions = class {
1692
1706
  async list(options) {
1693
1707
  let error = null;
1694
1708
  if (options?.recipient && options.recipient.length > 255) {
1695
- error = createError("The recipient must not exceed 255 characters.");
1709
+ error = createValidationError("The recipient must not exceed 255 characters.");
1696
1710
  return {
1697
1711
  data: null,
1698
1712
  error
@@ -1758,41 +1772,12 @@ var Attachment = class Attachment {
1758
1772
  disposition
1759
1773
  };
1760
1774
  }
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"
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
1796
1781
  });
1797
1782
  }
1798
1783
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mailchannels-sdk",
3
- "version": "0.8.0",
3
+ "version": "1.0.0",
4
4
  "description": "Node.js SDK to integrate MailChannels Email API into your JavaScript or TypeScript server-side applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -42,25 +42,25 @@
42
42
  "devDependencies": {
43
43
  "@stylistic/eslint-plugin": "^5.10.0",
44
44
  "@types/markdown-it": "^14.1.2",
45
- "@types/node": "^25.9.1",
46
- "@vitest/coverage-v8": "^4.1.7",
45
+ "@types/node": "^25.9.2",
46
+ "@vitest/coverage-v8": "^4.1.8",
47
47
  "changelogen": "^0.6.2",
48
48
  "obuild": "^0.4.36",
49
- "oxlint": "^1.67.0",
49
+ "oxlint": "^1.68.0",
50
50
  "scule": "^1.3.0",
51
51
  "typescript": "^6.0.3",
52
52
  "vitepress": "^2.0.0-alpha.17",
53
53
  "vitepress-plugin-group-icons": "^1.7.5",
54
- "vitepress-plugin-llms": "^1.13.0",
55
- "vitest": "^4.1.7"
54
+ "vitepress-plugin-llms": "^1.13.1",
55
+ "vitest": "^4.1.8"
56
56
  },
57
57
  "engines": {
58
58
  "node": ">=20"
59
59
  },
60
60
  "scripts": {
61
61
  "build": "obuild",
62
- "parity:fixtures": "node scripts/generate-parity-fixtures.mjs",
63
- "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",
64
64
  "simulate": "node src/cli.ts simulate",
65
65
  "lint": "oxlint",
66
66
  "lint:fix": "oxlint --fix",