mailchannels-sdk 0.6.0 → 0.6.1

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
@@ -43,8 +43,8 @@ This library provides a simple way to interact with the [MailChannels API](https
43
43
 
44
44
  ## <a name="requirements">📏 Requirements</a>
45
45
 
46
- - MailChannels account
47
- - Email API key
46
+ - [Create a MailChannels account](https://www.mailchannels.com/pricing/#for_devs)
47
+ - [Create an API key](https://console.mailchannels.net/settings/accountSettings#APIKeys)
48
48
 
49
49
  ## <a name="quick-setup">🏃 Quick setup</a>
50
50
 
@@ -17,11 +17,20 @@ interface SuccessResponse {
17
17
  * Whether the operation was successful.
18
18
  */
19
19
  success: boolean;
20
+ /**
21
+ * Error message if the operation failed.
22
+ */
20
23
  error: string | null;
21
24
  }
22
25
 
23
26
  interface DataResponse<T> {
27
+ /**
28
+ * The response data.
29
+ */
24
30
  data: T | null;
31
+ /**
32
+ * Error message if the operation failed.
33
+ */
25
34
  error: string | null;
26
35
  }
27
36
 
@@ -17,11 +17,20 @@ interface SuccessResponse {
17
17
  * Whether the operation was successful.
18
18
  */
19
19
  success: boolean;
20
+ /**
21
+ * Error message if the operation failed.
22
+ */
20
23
  error: string | null;
21
24
  }
22
25
 
23
26
  interface DataResponse<T> {
27
+ /**
28
+ * The response data.
29
+ */
24
30
  data: T | null;
31
+ /**
32
+ * Error message if the operation failed.
33
+ */
25
34
  error: string | null;
26
35
  }
27
36
 
@@ -63,33 +63,37 @@ const getStatusError = (response, errors = {}) => {
63
63
  }
64
64
  return details ? `${statusText} ${details}` : statusText;
65
65
  };
66
+ function getResultError(result, error, fallback) {
67
+ if (result.error) return result.error;
68
+ return error instanceof Error ? error.message : fallback;
69
+ }
66
70
 
71
+ const isValidEmail = (email) => {
72
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
73
+ };
67
74
  const parseRecipientString = (input) => {
68
75
  const trimmed = input.trim();
69
- const match = trimmed.match(/^([^<]*)<([^@\s]+@[^>\s]+)>$/);
76
+ const match = trimmed.match(/^([^<]*)<([^>]*)>$/);
70
77
  if (match) {
71
78
  const [, name, email] = match;
72
- return { email: email?.trim() || "", name: name?.trim() };
79
+ if (!email?.trim() || !isValidEmail(email.trim())) return void 0;
80
+ return { email: email.trim(), name: name?.trim() };
73
81
  }
82
+ if (!isValidEmail(trimmed)) return void 0;
74
83
  return { email: trimmed };
75
84
  };
76
85
  const parseRecipient = (recipient) => {
77
86
  if (typeof recipient === "string") {
78
87
  return parseRecipientString(recipient);
79
88
  }
80
- if (recipient?.email) {
81
- return { email: recipient.email, name: recipient.name };
82
- }
89
+ if (!recipient?.email || !isValidEmail(recipient.email)) return void 0;
90
+ return { email: recipient.email, name: recipient.name };
83
91
  };
84
92
  const parseArrayRecipients = (recipients) => {
85
- if (!recipients) return;
86
- if (typeof recipients === "string") {
87
- return [parseRecipientString(recipients)];
88
- }
89
- if (Array.isArray(recipients)) {
90
- return recipients.map((recipient) => parseRecipient(recipient)).filter((recipient) => Boolean(recipient));
91
- }
92
- return [recipients];
93
+ if (!recipients) return void 0;
94
+ const arr = typeof recipients === "string" ? [parseRecipientString(recipients)] : Array.isArray(recipients) ? recipients.map(parseRecipient) : [recipients];
95
+ const filtered = arr.filter((recipient) => Boolean(recipient));
96
+ return filtered.length > 0 ? filtered : void 0;
93
97
  };
94
98
 
95
99
  const stripPemHeaders = (pem) => pem.replace(/-----[^-]+-----|\s|#.*$/gm, "");
@@ -119,6 +123,21 @@ const clean = (data) => {
119
123
  }
120
124
  return data;
121
125
  };
126
+ const validateLimit = (limit, max) => {
127
+ if (typeof limit === "number" && (limit < 1 || max && limit > max)) {
128
+ return "The limit value " + (max ? `must be between 1 and ${max}.` : "is invalid. Only positive values are allowed.");
129
+ }
130
+ return null;
131
+ };
132
+ const validateOffset = (offset) => {
133
+ if (typeof offset === "number" && offset < 0) {
134
+ return "Offset must be greater than or equal to 0.";
135
+ }
136
+ return null;
137
+ };
138
+ const mapBuckets = (arr) => {
139
+ return arr.map(({ count, period_start }) => ({ count, periodStart: period_start }));
140
+ };
122
141
 
123
142
  class Emails {
124
143
  constructor(mailchannels) {
@@ -197,7 +216,10 @@ class Emails {
197
216
  [ErrorCode.PayloadTooLarge]: "The total message size should not exceed 30MB. This includes the message itself, headers, and the combined size of any attachments."
198
217
  });
199
218
  }
200
- }).catch(() => null);
219
+ }).catch((error) => {
220
+ result.error = getResultError(result, error, "Failed to send email.");
221
+ return null;
222
+ });
201
223
  if (!response) return result;
202
224
  result.data = clean({
203
225
  rendered: response.data,
@@ -249,7 +271,10 @@ class Emails {
249
271
  [ErrorCode.Forbidden]: "User does not have access to this feature."
250
272
  });
251
273
  }
252
- }).catch(() => null);
274
+ }).catch((error) => {
275
+ result.error = getResultError(result, error, "Failed to check domain.");
276
+ return null;
277
+ });
253
278
  if (!response) return result;
254
279
  result.data = clean({
255
280
  dkim: response.check_results.dkim.map((dkimResults) => ({
@@ -297,7 +322,10 @@ class Emails {
297
322
  [ErrorCode.Conflict]: "Key pair already created for domain, and selector."
298
323
  });
299
324
  }
300
- }).catch(() => null);
325
+ }).catch((error) => {
326
+ result.error = getResultError(result, error, "Failed to create DKIM key.");
327
+ return null;
328
+ });
301
329
  if (!response) return result;
302
330
  result.data = clean({
303
331
  algorithm: response.algorithm,
@@ -329,17 +357,11 @@ class Emails {
329
357
  async getDkimKeys(domain, options) {
330
358
  const result = { data: null, error: null };
331
359
  if (options?.selector && options.selector.length > 63) {
332
- result.error = "Selector must be a maximum of 63 characters.";
333
- return result;
334
- }
335
- if (typeof options?.limit === "number" && (options.limit < 1 || options.limit > 100)) {
336
- result.error = "Limit must be between 1 and 100.";
337
- return result;
338
- }
339
- if (typeof options?.offset === "number" && options.offset < 0) {
340
- result.error = "Offset value is invalid. Only positive values are allowed.";
360
+ result.error = "Selector must be between 1 and 63 characters.";
341
361
  return result;
342
362
  }
363
+ result.error = validateLimit(options?.limit, 100) || validateOffset(options?.offset);
364
+ if (result.error) return result;
343
365
  const payload = {
344
366
  selector: options?.selector,
345
367
  status: options?.status,
@@ -354,7 +376,10 @@ class Emails {
354
376
  [ErrorCode.BadRequest]: "Bad Request."
355
377
  });
356
378
  }
357
- }).catch(() => null);
379
+ }).catch((error) => {
380
+ result.error = getResultError(result, error, "Failed to fetch DKIM keys.");
381
+ return null;
382
+ });
358
383
  if (!response) return result;
359
384
  result.data = clean(response.keys.map((key) => ({
360
385
  algorithm: key.algorithm,
@@ -394,6 +419,7 @@ class Emails {
394
419
  };
395
420
  await this.mailchannels.patch(`/tx/v1/domains/${domain}/dkim-keys/${options.selector}`, {
396
421
  body: payload,
422
+ ignoreResponseError: true,
397
423
  onResponse: async ({ response }) => {
398
424
  if (response.ok) {
399
425
  result.success = true;
@@ -405,7 +431,7 @@ class Emails {
405
431
  });
406
432
  }
407
433
  }).catch((error) => {
408
- result.error = error instanceof Error ? error.message : "Failed to update DKIM key.";
434
+ result.error = getResultError(result, error, "Failed to update DKIM key.");
409
435
  });
410
436
  return result;
411
437
  }
@@ -450,9 +476,7 @@ class Emails {
450
476
  });
451
477
  }
452
478
  }).catch((error) => {
453
- if (!result.error) {
454
- result.error = error instanceof Error ? error.message : "Failed to rotate DKIM key.";
455
- }
479
+ result.error = getResultError(result, error, "Failed to rotate DKIM key.");
456
480
  return null;
457
481
  });
458
482
  if (!response) return result;
@@ -525,6 +549,8 @@ class Webhooks {
525
549
  [ErrorCode.Conflict]: `Endpoint '${endpoint}' is already enrolled to receive notifications.`
526
550
  });
527
551
  }
552
+ }).catch((error) => {
553
+ result.error = getResultError(result, error, "Failed to enroll webhook.");
528
554
  });
529
555
  return result;
530
556
  }
@@ -543,9 +569,7 @@ class Webhooks {
543
569
  result.error = getStatusError(response2);
544
570
  }
545
571
  }).catch((error) => {
546
- if (!result.error) {
547
- result.error = error instanceof Error ? error.message : "Failed to fetch webhooks.";
548
- }
572
+ result.error = getResultError(result, error, "Failed to fetch webhooks.");
549
573
  return null;
550
574
  });
551
575
  if (!response) return result;
@@ -571,6 +595,8 @@ class Webhooks {
571
595
  }
572
596
  result.success = true;
573
597
  }
598
+ }).catch((error) => {
599
+ result.error = getResultError(result, error, "Failed to delete webhooks.");
574
600
  });
575
601
  return result;
576
602
  }
@@ -589,13 +615,16 @@ class Webhooks {
589
615
  query: {
590
616
  id
591
617
  },
592
- onResponseError: ({ response: response2 }) => {
618
+ onResponseError: async ({ response: response2 }) => {
593
619
  result.error = getStatusError(response2, {
594
620
  [ErrorCode.BadRequest]: "Bad Request.",
595
621
  [ErrorCode.NotFound]: `The key '${id}' is not found.`
596
622
  });
597
623
  }
598
- }).catch(() => null);
624
+ }).catch((error) => {
625
+ result.error = getResultError(result, error, "Failed to get signing key.");
626
+ return null;
627
+ });
599
628
  if (!response) return result;
600
629
  result.data = clean({ key: response.key });
601
630
  return result;
@@ -619,13 +648,16 @@ class Webhooks {
619
648
  body: {
620
649
  request_id: requestId
621
650
  },
622
- onResponseError: ({ response: response2 }) => {
651
+ onResponseError: async ({ response: response2 }) => {
623
652
  result.error = getStatusError(response2, {
624
653
  [ErrorCode.BadRequest]: "Bad Request.",
625
654
  [ErrorCode.NotFound]: "No webhooks found for the account."
626
655
  });
627
656
  }
628
- }).catch(() => null);
657
+ }).catch((error) => {
658
+ result.error = getResultError(result, error, "Failed to validate webhooks.");
659
+ return null;
660
+ });
629
661
  if (!response) return result;
630
662
  result.data = clean({
631
663
  allPassed: response.all_passed,
@@ -670,13 +702,16 @@ class SubAccounts {
670
702
  company_name: companyName,
671
703
  handle
672
704
  },
673
- onResponseError: ({ response: response2 }) => {
705
+ onResponseError: async ({ response: response2 }) => {
674
706
  result.error = getStatusError(response2, {
675
707
  [ErrorCode.Forbidden]: "The parent account does not have permission to create sub-accounts.",
676
708
  [ErrorCode.Conflict]: `Sub-account with handle '${handle}' already exists.`
677
709
  });
678
710
  }
679
- }).catch(() => null);
711
+ }).catch((error) => {
712
+ result.error = getResultError(result, error, "Failed to create sub-account.");
713
+ return null;
714
+ });
680
715
  if (!response) return result;
681
716
  result.data = clean({
682
717
  companyName: response.company_name,
@@ -696,23 +731,15 @@ class SubAccounts {
696
731
  */
697
732
  async list(options) {
698
733
  const result = { data: null, error: null };
699
- if (typeof options?.limit === "number" && (options.limit < 1 || options.limit > 1e3)) {
700
- result.error = "The limit value is invalid. Possible limit values are 1 to 1000.";
701
- return result;
702
- }
703
- if (typeof options?.offset === "number" && options.offset < 0) {
704
- result.error = "Offset must be greater than or equal to 0.";
705
- return result;
706
- }
734
+ result.error = validateLimit(options?.limit, 1e3) || validateOffset(options?.offset);
735
+ if (result.error) return result;
707
736
  const response = await this.mailchannels.get("/tx/v1/sub-account", {
708
737
  query: options,
709
738
  onResponseError: async ({ response: response2 }) => {
710
739
  result.error = getStatusError(response2);
711
740
  }
712
741
  }).catch((error) => {
713
- if (!result.error) {
714
- result.error = error instanceof Error ? error.message : "Failed to fetch sub-accounts.";
715
- }
742
+ result.error = getResultError(result, error, "Failed to fetch sub-accounts.");
716
743
  return null;
717
744
  });
718
745
  if (!response) return result;
@@ -746,6 +773,8 @@ class SubAccounts {
746
773
  }
747
774
  result.success = true;
748
775
  }
776
+ }).catch((error) => {
777
+ result.error = getResultError(result, error, "Failed to delete sub-account.");
749
778
  });
750
779
  return result;
751
780
  }
@@ -775,6 +804,8 @@ class SubAccounts {
775
804
  [ErrorCode.NotFound]: `The specified sub-account '${handle}' does not exist.`
776
805
  });
777
806
  }
807
+ }).catch((error) => {
808
+ result.error = getResultError(result, error, "Failed to suspend sub-account.");
778
809
  });
779
810
  return result;
780
811
  }
@@ -805,6 +836,8 @@ class SubAccounts {
805
836
  [ErrorCode.NotFound]: `The specified sub-account '${handle}' does not exist.`
806
837
  });
807
838
  }
839
+ }).catch((error) => {
840
+ result.error = getResultError(result, error, "Failed to activate sub-account.");
808
841
  });
809
842
  return result;
810
843
  }
@@ -831,7 +864,10 @@ class SubAccounts {
831
864
  [ErrorCode.UnprocessableEntity]: "You have reached the limit of API keys you can create for this sub-account."
832
865
  });
833
866
  }
834
- }).catch(() => null);
867
+ }).catch((error) => {
868
+ result.error = getResultError(result, error, "Failed to create sub-account API key.");
869
+ return null;
870
+ });
835
871
  if (!response) return result;
836
872
  result.data = clean({
837
873
  id: response.id,
@@ -855,21 +891,18 @@ class SubAccounts {
855
891
  result.error = "No handle provided.";
856
892
  return result;
857
893
  }
858
- if (typeof options?.limit === "number" && (options.limit < 1 || options.limit > 1e3)) {
859
- result.error = "The limit value is invalid. Possible limit values are 1 to 1000.";
860
- return result;
861
- }
862
- if (typeof options?.offset === "number" && options.offset < 0) {
863
- result.error = "Offset must be greater than or equal to 0.";
864
- return result;
865
- }
894
+ result.error = validateLimit(options?.limit, 1e3) || validateOffset(options?.offset);
895
+ if (result.error) return result;
866
896
  const response = await this.mailchannels.get(`/tx/v1/sub-account/${handle}/api-key`, {
867
897
  onResponseError: async ({ response: response2 }) => {
868
898
  result.error = getStatusError(response2, {
869
899
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`
870
900
  });
871
901
  }
872
- }).catch(() => null);
902
+ }).catch((error) => {
903
+ result.error = getResultError(result, error, "Failed to fetch sub-account API keys.");
904
+ return null;
905
+ });
873
906
  if (!response) return result;
874
907
  result.data = clean(response.map((key) => ({
875
908
  id: key.id,
@@ -904,6 +937,8 @@ class SubAccounts {
904
937
  [ErrorCode.BadRequest]: "Missing or invalid API key ID."
905
938
  });
906
939
  }
940
+ }).catch((error) => {
941
+ result.error = getResultError(result, error, "Failed to delete sub-account API key.");
907
942
  });
908
943
  return result;
909
944
  }
@@ -930,7 +965,10 @@ class SubAccounts {
930
965
  [ErrorCode.UnprocessableEntity]: "You have reached the limit of SMTP passwords you can create for this sub-account."
931
966
  });
932
967
  }
933
- }).catch(() => null);
968
+ }).catch((error) => {
969
+ result.error = getResultError(result, error, "Failed to create sub-account SMTP password.");
970
+ return null;
971
+ });
934
972
  if (!response) return result;
935
973
  result.data = clean({
936
974
  enabled: response.enabled,
@@ -960,7 +998,10 @@ class SubAccounts {
960
998
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`
961
999
  });
962
1000
  }
963
- }).catch(() => null);
1001
+ }).catch((error) => {
1002
+ result.error = getResultError(result, error, "Failed to fetch sub-account SMTP passwords.");
1003
+ return null;
1004
+ });
964
1005
  if (!response) return result;
965
1006
  result.data = clean(response.map((password) => ({
966
1007
  enabled: password.enabled,
@@ -996,6 +1037,8 @@ class SubAccounts {
996
1037
  [ErrorCode.BadRequest]: "Missing or invalid SMTP password ID."
997
1038
  });
998
1039
  }
1040
+ }).catch((error) => {
1041
+ result.error = getResultError(result, error, "Failed to delete sub-account SMTP password.");
999
1042
  });
1000
1043
  return result;
1001
1044
  }
@@ -1020,7 +1063,10 @@ class SubAccounts {
1020
1063
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`
1021
1064
  });
1022
1065
  }
1023
- }).catch(() => null);
1066
+ }).catch((error) => {
1067
+ result.error = getResultError(result, error, "Failed to fetch sub-account limit.");
1068
+ return null;
1069
+ });
1024
1070
  if (!response) return result;
1025
1071
  result.data = clean(response);
1026
1072
  return result;
@@ -1054,6 +1100,8 @@ class SubAccounts {
1054
1100
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`
1055
1101
  });
1056
1102
  }
1103
+ }).catch((error) => {
1104
+ result.error = getResultError(result, error, "Failed to set sub-account limit.");
1057
1105
  });
1058
1106
  return result;
1059
1107
  }
@@ -1083,6 +1131,8 @@ class SubAccounts {
1083
1131
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`
1084
1132
  });
1085
1133
  }
1134
+ }).catch((error) => {
1135
+ result.error = getResultError(result, error, "Failed to delete sub-account limit.");
1086
1136
  });
1087
1137
  return result;
1088
1138
  }
@@ -1107,7 +1157,10 @@ class SubAccounts {
1107
1157
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`
1108
1158
  });
1109
1159
  }
1110
- }).catch(() => null);
1160
+ }).catch((error) => {
1161
+ result.error = getResultError(result, error, "Failed to fetch sub-account usage.");
1162
+ return null;
1163
+ });
1111
1164
  if (!response) return result;
1112
1165
  result.data = clean({
1113
1166
  endDate: response.period_end_date,
@@ -1118,9 +1171,6 @@ class SubAccounts {
1118
1171
  }
1119
1172
  }
1120
1173
 
1121
- const mapBuckets = (arr) => {
1122
- return arr.map(({ count, period_start }) => ({ count, periodStart: period_start }));
1123
- };
1124
1174
  class Metrics {
1125
1175
  constructor(mailchannels) {
1126
1176
  this.mailchannels = mailchannels;
@@ -1149,9 +1199,7 @@ class Metrics {
1149
1199
  });
1150
1200
  }
1151
1201
  }).catch((error) => {
1152
- if (!result.error) {
1153
- result.error = error instanceof Error ? error.message : "Failed to fetch engagement metrics.";
1154
- }
1202
+ result.error = getResultError(result, error, "Failed to fetch engagement metrics.");
1155
1203
  return null;
1156
1204
  });
1157
1205
  if (!response) return result;
@@ -1195,9 +1243,7 @@ class Metrics {
1195
1243
  });
1196
1244
  }
1197
1245
  }).catch((error) => {
1198
- if (!result.error) {
1199
- result.error = error instanceof Error ? error.message : "Failed to fetch performance metrics.";
1200
- }
1246
+ result.error = getResultError(result, error, "Failed to fetch performance metrics.");
1201
1247
  return null;
1202
1248
  });
1203
1249
  if (!response) return result;
@@ -1239,9 +1285,7 @@ class Metrics {
1239
1285
  });
1240
1286
  }
1241
1287
  }).catch((error) => {
1242
- if (!result.error) {
1243
- result.error = error instanceof Error ? error.message : "Failed to fetch recipient behaviour metrics.";
1244
- }
1288
+ result.error = getResultError(result, error, "Failed to fetch recipient behaviour metrics.");
1245
1289
  return null;
1246
1290
  });
1247
1291
  if (!response) return result;
@@ -1281,9 +1325,7 @@ class Metrics {
1281
1325
  });
1282
1326
  }
1283
1327
  }).catch((error) => {
1284
- if (!result.error) {
1285
- result.error = error instanceof Error ? error.message : "Failed to fetch volume metrics.";
1286
- }
1328
+ result.error = getResultError(result, error, "Failed to fetch volume metrics.");
1287
1329
  return null;
1288
1330
  });
1289
1331
  if (!response) return result;
@@ -1316,9 +1358,7 @@ class Metrics {
1316
1358
  result.error = getStatusError(response2);
1317
1359
  }
1318
1360
  }).catch((error) => {
1319
- if (!result.error) {
1320
- result.error = error instanceof Error ? error.message : "Failed to fetch usage metrics.";
1321
- }
1361
+ result.error = getResultError(result, error, "Failed to fetch usage metrics.");
1322
1362
  return null;
1323
1363
  });
1324
1364
  if (!response) return result;
@@ -1341,6 +1381,8 @@ class Metrics {
1341
1381
  */
1342
1382
  async senders(type, options) {
1343
1383
  const result = { data: null, error: null };
1384
+ result.error = validateLimit(options?.limit, 1e3) || validateOffset(options?.offset);
1385
+ if (result.error) return result;
1344
1386
  const response = await this.mailchannels.get(`/tx/v1/metrics/senders/${type}`, {
1345
1387
  query: {
1346
1388
  start_time: options?.startTime,
@@ -1355,9 +1397,7 @@ class Metrics {
1355
1397
  });
1356
1398
  }
1357
1399
  }).catch((error) => {
1358
- if (!result.error) {
1359
- result.error = error instanceof Error ? error.message : "Failed to fetch senders metrics.";
1360
- }
1400
+ result.error = getResultError(result, error, "Failed to fetch senders metrics.");
1361
1401
  return null;
1362
1402
  });
1363
1403
  if (!response) return result;
@@ -1413,6 +1453,8 @@ class Suppressions {
1413
1453
  [ErrorCode.PayloadTooLarge]: "Payload too large. The request exceeds the maximum allowed total of 1000 suppression entries for the parent account and/or its sub-accounts."
1414
1454
  });
1415
1455
  }
1456
+ }).catch((error) => {
1457
+ result.error = getResultError(result, error, "Failed to create suppression entries.");
1416
1458
  });
1417
1459
  return result;
1418
1460
  }
@@ -1442,6 +1484,8 @@ class Suppressions {
1442
1484
  [ErrorCode.BadRequest]: "Bad Request."
1443
1485
  });
1444
1486
  }
1487
+ }).catch((error) => {
1488
+ result.error = getResultError(result, error, "Failed to delete suppression entry.");
1445
1489
  });
1446
1490
  return result;
1447
1491
  }
@@ -1456,14 +1500,8 @@ class Suppressions {
1456
1500
  */
1457
1501
  async list(options) {
1458
1502
  const result = { data: null, error: null };
1459
- if (typeof options?.limit === "number" && (options.limit < 1 || options.limit > 1e3)) {
1460
- result.error = "The limit must be between 1 and 1000.";
1461
- return result;
1462
- }
1463
- if (typeof options?.offset === "number" && options.offset < 0) {
1464
- result.error = "Offset must be greater than or equal to 0.";
1465
- return result;
1466
- }
1503
+ result.error = validateLimit(options?.limit, 1e3) || validateOffset(options?.offset);
1504
+ if (result.error) return result;
1467
1505
  const payload = {
1468
1506
  recipient: options?.recipient,
1469
1507
  source: options?.source,
@@ -1479,7 +1517,10 @@ class Suppressions {
1479
1517
  [ErrorCode.BadRequest]: "Bad Request."
1480
1518
  });
1481
1519
  }
1482
- }).catch(() => null);
1520
+ }).catch((error) => {
1521
+ result.error = getResultError(result, error, "Failed to fetch suppression entries.");
1522
+ return null;
1523
+ });
1483
1524
  if (!response) return result;
1484
1525
  result.data = clean(response.suppression_list.map((entry) => ({
1485
1526
  createdAt: entry.created_at,
@@ -1526,9 +1567,7 @@ class Domains {
1526
1567
  });
1527
1568
  }
1528
1569
  }).catch((error) => {
1529
- if (!result.error) {
1530
- result.error = error instanceof Error ? error.message : "Failed to provision domain.";
1531
- }
1570
+ result.error = getResultError(result, error, "Failed to provision domain.");
1532
1571
  return null;
1533
1572
  });
1534
1573
  result.data = clean(response);
@@ -1579,9 +1618,7 @@ class Domains {
1579
1618
  });
1580
1619
  }
1581
1620
  }).catch((error) => {
1582
- if (!result.error) {
1583
- result.error = error instanceof Error ? error.message : "Failed to provision domains.";
1584
- }
1621
+ result.error = getResultError(result, error, "Failed to provision domains.");
1585
1622
  return null;
1586
1623
  });
1587
1624
  if (!response) return result;
@@ -1599,20 +1636,17 @@ class Domains {
1599
1636
  */
1600
1637
  async list(options) {
1601
1638
  const result = { data: null, error: null };
1602
- if (typeof options?.limit === "number" && (options.limit < 1 || options.limit > 5e3)) {
1603
- result.error = "The limit value is invalid. Possible limit values are 1 to 5000.";
1604
- return result;
1605
- }
1606
- if (typeof options?.offset === "number" && options.offset < 0) {
1607
- result.error = "Offset must be greater than or equal to 0.";
1608
- return result;
1609
- }
1639
+ result.error = validateLimit(options?.limit, 5e3) || validateOffset(options?.offset);
1640
+ if (result.error) return result;
1610
1641
  const response = await this.mailchannels.get("/inbound/v1/domains", {
1611
1642
  query: options,
1612
1643
  onResponseError: async ({ response: response2 }) => {
1613
1644
  result.error = getStatusError(response2);
1614
1645
  }
1615
- }).catch(() => null);
1646
+ }).catch((error) => {
1647
+ result.error = getResultError(result, error, "Failed to fetch domains.");
1648
+ return null;
1649
+ });
1616
1650
  if (!response) return result;
1617
1651
  result.data = clean({
1618
1652
  domains: response.domains,
@@ -1647,6 +1681,8 @@ class Domains {
1647
1681
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1648
1682
  });
1649
1683
  }
1684
+ }).catch((error) => {
1685
+ result.error = getResultError(result, error, "Failed to delete domain.");
1650
1686
  });
1651
1687
  return result;
1652
1688
  }
@@ -1682,7 +1718,10 @@ class Domains {
1682
1718
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1683
1719
  });
1684
1720
  }
1685
- }).catch(() => null);
1721
+ }).catch((error) => {
1722
+ result.error = getResultError(result, error, "Failed to add domain list entry.");
1723
+ return null;
1724
+ });
1686
1725
  if (!response) return result;
1687
1726
  result.data = clean({
1688
1727
  action: response.action,
@@ -1718,7 +1757,10 @@ class Domains {
1718
1757
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1719
1758
  });
1720
1759
  }
1721
- }).catch(() => null);
1760
+ }).catch((error) => {
1761
+ result.error = getResultError(result, error, "Failed to fetch domain list entries.");
1762
+ return null;
1763
+ });
1722
1764
  if (!response) return result;
1723
1765
  result.data = clean(response.map(({ action, item, item_type }) => ({
1724
1766
  action,
@@ -1764,6 +1806,8 @@ class Domains {
1764
1806
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1765
1807
  });
1766
1808
  }
1809
+ }).catch((error) => {
1810
+ result.error = getResultError(result, error, "Failed to delete domain list entry.");
1767
1811
  });
1768
1812
  return result;
1769
1813
  }
@@ -1790,7 +1834,10 @@ class Domains {
1790
1834
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1791
1835
  });
1792
1836
  }
1793
- }).catch(() => null);
1837
+ }).catch((error) => {
1838
+ result.error = getResultError(result, error, "Failed to create login link.");
1839
+ return null;
1840
+ });
1794
1841
  if (!response) return result;
1795
1842
  result.data = clean({
1796
1843
  link: response.loginLink
@@ -1837,6 +1884,8 @@ class Domains {
1837
1884
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1838
1885
  });
1839
1886
  }
1887
+ }).catch((error) => {
1888
+ result.error = getResultError(result, error, "Failed to set downstream address.");
1840
1889
  });
1841
1890
  return result;
1842
1891
  }
@@ -1856,14 +1905,8 @@ class Domains {
1856
1905
  result.error = "No domain provided.";
1857
1906
  return result;
1858
1907
  }
1859
- if (typeof options?.limit === "number" && options.limit < 1) {
1860
- result.error = "The limit value is invalid. Only positive values are allowed.";
1861
- return result;
1862
- }
1863
- if (typeof options?.offset === "number" && options.offset < 0) {
1864
- result.error = "Offset must be greater than or equal to 0.";
1865
- return result;
1866
- }
1908
+ result.error = validateLimit(options?.limit) || validateOffset(options?.offset);
1909
+ if (result.error) return result;
1867
1910
  const response = await this.mailchannels.get(`/inbound/v1/domains/${domain}/downstream-address`, {
1868
1911
  query: options,
1869
1912
  onResponseError: async ({ response: response2 }) => {
@@ -1872,7 +1915,10 @@ class Domains {
1872
1915
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1873
1916
  });
1874
1917
  }
1875
- }).catch(() => null);
1918
+ }).catch((error) => {
1919
+ result.error = getResultError(result, error, "Failed to list downstream addresses.");
1920
+ return null;
1921
+ });
1876
1922
  if (!response) return result;
1877
1923
  result.data = clean(response.records);
1878
1924
  return result;
@@ -1910,6 +1956,8 @@ class Domains {
1910
1956
  [ErrorCode.NotFound]: "The domain does not exist."
1911
1957
  });
1912
1958
  }
1959
+ }).catch((error) => {
1960
+ result.error = getResultError(result, error, "Failed to update domain API key.");
1913
1961
  });
1914
1962
  return result;
1915
1963
  }
@@ -1941,7 +1989,10 @@ class Domains {
1941
1989
  [ErrorCode.BadRequest]: "Bad Request."
1942
1990
  });
1943
1991
  }
1944
- }).catch(() => null);
1992
+ }).catch((error) => {
1993
+ result.error = getResultError(result, error, "Failed to create login links.");
1994
+ return null;
1995
+ });
1945
1996
  if (!response) return result;
1946
1997
  result.data = clean(response);
1947
1998
  return result;
@@ -1976,7 +2027,10 @@ class Lists {
1976
2027
  onResponseError: async ({ response: response2 }) => {
1977
2028
  result.error = getStatusError(response2);
1978
2029
  }
1979
- }).catch(() => null);
2030
+ }).catch((error) => {
2031
+ result.error = getResultError(result, error, "Failed to add list entry.");
2032
+ return null;
2033
+ });
1980
2034
  if (!response) return result;
1981
2035
  result.data = clean({
1982
2036
  action: response.action,
@@ -2004,7 +2058,10 @@ class Lists {
2004
2058
  onResponseError: async ({ response: response2 }) => {
2005
2059
  result.error = getStatusError(response2);
2006
2060
  }
2007
- }).catch(() => null);
2061
+ }).catch((error) => {
2062
+ result.error = getResultError(result, error, "Failed to fetch list entries.");
2063
+ return null;
2064
+ });
2008
2065
  if (!response) return result;
2009
2066
  result.data = clean(response.map(({ action, item, item_type }) => ({
2010
2067
  action,
@@ -2042,6 +2099,8 @@ class Lists {
2042
2099
  }
2043
2100
  result.error = getStatusError(response);
2044
2101
  }
2102
+ }).catch((error) => {
2103
+ result.error = getResultError(result, error, "Failed to delete list entry.");
2045
2104
  });
2046
2105
  return result;
2047
2106
  }
@@ -2084,7 +2143,10 @@ class Users {
2084
2143
  [ErrorCode.BadRequest]: `The email address '${email}' is invalid.`
2085
2144
  });
2086
2145
  }
2087
- }).catch(() => null);
2146
+ }).catch((error) => {
2147
+ result.error = getResultError(result, error, "Failed to create user.");
2148
+ return null;
2149
+ });
2088
2150
  if (!response) return result;
2089
2151
  result.data = clean({
2090
2152
  email: response.recipient.email_address,
@@ -2130,7 +2192,10 @@ class Users {
2130
2192
  [ErrorCode.NotFound]: `The recipient '${email}' was not found.`
2131
2193
  });
2132
2194
  }
2133
- }).catch(() => null);
2195
+ }).catch((error) => {
2196
+ result.error = getResultError(result, error, "Failed to add user list entry.");
2197
+ return null;
2198
+ });
2134
2199
  if (!response) return result;
2135
2200
  result.data = clean({
2136
2201
  action: response.action,
@@ -2166,7 +2231,10 @@ class Users {
2166
2231
  [ErrorCode.NotFound]: `The recipient '${email}' was not found.`
2167
2232
  });
2168
2233
  }
2169
- }).catch(() => null);
2234
+ }).catch((error) => {
2235
+ result.error = getResultError(result, error, "Failed to fetch user list entries.");
2236
+ return null;
2237
+ });
2170
2238
  if (!response) return result;
2171
2239
  result.data = clean(response.map(({ action, item, item_type }) => ({
2172
2240
  action,
@@ -2212,6 +2280,8 @@ class Users {
2212
2280
  [ErrorCode.NotFound]: `The recipient '${email}' was not found.`
2213
2281
  });
2214
2282
  }
2283
+ }).catch((error) => {
2284
+ result.error = getResultError(result, error, "Failed to delete user list entry.");
2215
2285
  });
2216
2286
  return result;
2217
2287
  }
@@ -2240,6 +2310,8 @@ class Service {
2240
2310
  }
2241
2311
  result.error = getStatusError(response);
2242
2312
  }
2313
+ }).catch((error) => {
2314
+ result.error = getResultError(result, error, "Failed to fetch service status.");
2243
2315
  });
2244
2316
  return result;
2245
2317
  }
@@ -2260,9 +2332,7 @@ class Service {
2260
2332
  });
2261
2333
  }
2262
2334
  }).catch((error) => {
2263
- if (!result.error) {
2264
- result.error = error instanceof Error ? error.message : "Failed to fetch subscriptions.";
2265
- }
2335
+ result.error = getResultError(result, error, "Failed to fetch subscriptions.");
2266
2336
  return null;
2267
2337
  });
2268
2338
  if (!response) return result;
@@ -2288,6 +2358,7 @@ class Service {
2288
2358
  report_type: type
2289
2359
  },
2290
2360
  body: payload,
2361
+ ignoreResponseError: true,
2291
2362
  onResponse: async ({ response }) => {
2292
2363
  if (response.ok) {
2293
2364
  result.success = true;
@@ -2295,6 +2366,8 @@ class Service {
2295
2366
  }
2296
2367
  result.error = getStatusError(response);
2297
2368
  }
2369
+ }).catch((error) => {
2370
+ result.error = getResultError(result, error, "Failed to submit report.");
2298
2371
  });
2299
2372
  return result;
2300
2373
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mailchannels-sdk",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Node.js SDK to integrate MailChannels API into your JavaScript or TypeScript server-side applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,10 +40,10 @@
40
40
  "@eslint/compat": "^2.0.0",
41
41
  "@stylistic/eslint-plugin": "^5.6.1",
42
42
  "@types/markdown-it": "^14.1.2",
43
- "@types/node": "^24.10.1",
44
- "@typescript-eslint/eslint-plugin": "^8.48.1",
45
- "@typescript-eslint/parser": "^8.48.1",
46
- "@vitest/coverage-v8": "^4.0.14",
43
+ "@types/node": "^25.0.1",
44
+ "@typescript-eslint/eslint-plugin": "^8.49.0",
45
+ "@typescript-eslint/parser": "^8.49.0",
46
+ "@vitest/coverage-v8": "^4.0.15",
47
47
  "changelogen": "^0.6.2",
48
48
  "eslint": "^9.39.1",
49
49
  "eslint-plugin-import-x": "^4.16.1",
@@ -54,7 +54,7 @@
54
54
  "vitepress": "^1.6.4",
55
55
  "vitepress-plugin-group-icons": "^1.6.5",
56
56
  "vitepress-plugin-llms": "^1.9.3",
57
- "vitest": "^4.0.14"
57
+ "vitest": "^4.0.15"
58
58
  },
59
59
  "scripts": {
60
60
  "build": "unbuild",
@@ -65,7 +65,7 @@
65
65
  "test:types": "tsc --noEmit",
66
66
  "test:watch": "vitest watch",
67
67
  "docs:dev": "vitepress dev docs",
68
- "docs:build": "(git fetch --unshallow || git fetch --all) && vitepress build docs",
68
+ "docs:build": "(git fetch --unshallow -q || git fetch --all -q) && vitepress build docs",
69
69
  "docs:preview": "vitepress preview docs",
70
70
  "docs:snippets": "jiti docs/scripts/snippets"
71
71
  }