mailchannels-sdk 0.6.0 → 0.7.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.
@@ -50,6 +50,12 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
50
50
  ErrorCode2[ErrorCode2["UnprocessableEntity"] = 422] = "UnprocessableEntity";
51
51
  return ErrorCode2;
52
52
  })(ErrorCode || {});
53
+ const createError = (message, statusCode = null) => {
54
+ return {
55
+ message,
56
+ statusCode
57
+ };
58
+ };
53
59
  const getStatusError = (response, errors = {}) => {
54
60
  const statusText = errors[response.status] || "Unknown error.";
55
61
  const payload = response._data ?? response.data;
@@ -61,35 +67,38 @@ const getStatusError = (response, errors = {}) => {
61
67
  } else if (Array.isArray(payload?.errors) && payload.errors?.length) {
62
68
  details = payload.errors.join(", ");
63
69
  }
64
- return details ? `${statusText} ${details}` : statusText;
70
+ return createError(details ? `${statusText} ${details}` : statusText, response.status ?? null);
71
+ };
72
+ const getResultError = (e, fallback) => {
73
+ return createError(e instanceof Error ? e.message : fallback);
65
74
  };
66
75
 
76
+ const isValidEmail = (email) => {
77
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
78
+ };
67
79
  const parseRecipientString = (input) => {
68
80
  const trimmed = input.trim();
69
- const match = trimmed.match(/^([^<]*)<([^@\s]+@[^>\s]+)>$/);
81
+ const match = trimmed.match(/^([^<]*)<([^>]*)>$/);
70
82
  if (match) {
71
83
  const [, name, email] = match;
72
- return { email: email?.trim() || "", name: name?.trim() };
84
+ if (!email?.trim() || !isValidEmail(email.trim())) return void 0;
85
+ return { email: email.trim(), name: name?.trim() };
73
86
  }
87
+ if (!isValidEmail(trimmed)) return void 0;
74
88
  return { email: trimmed };
75
89
  };
76
90
  const parseRecipient = (recipient) => {
77
91
  if (typeof recipient === "string") {
78
92
  return parseRecipientString(recipient);
79
93
  }
80
- if (recipient?.email) {
81
- return { email: recipient.email, name: recipient.name };
82
- }
94
+ if (!recipient?.email || !isValidEmail(recipient.email)) return void 0;
95
+ return { email: recipient.email, name: recipient.name };
83
96
  };
84
97
  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];
98
+ if (!recipients) return void 0;
99
+ const arr = typeof recipients === "string" ? [parseRecipientString(recipients)] : Array.isArray(recipients) ? recipients.map(parseRecipient) : [recipients];
100
+ const filtered = arr.filter((recipient) => Boolean(recipient));
101
+ return filtered.length > 0 ? filtered : void 0;
93
102
  };
94
103
 
95
104
  const stripPemHeaders = (pem) => pem.replace(/-----[^-]+-----|\s|#.*$/gm, "");
@@ -119,6 +128,21 @@ const clean = (data) => {
119
128
  }
120
129
  return data;
121
130
  };
131
+ const validateLimit = (limit, max) => {
132
+ if (typeof limit === "number" && (limit < 1 || max && limit > max)) {
133
+ return createError("The limit value " + (max ? `must be between 1 and ${max}.` : "is invalid. Only positive values are allowed."));
134
+ }
135
+ return null;
136
+ };
137
+ const validateOffset = (offset) => {
138
+ if (typeof offset === "number" && offset < 0) {
139
+ return createError("Offset must be greater than or equal to 0.");
140
+ }
141
+ return null;
142
+ };
143
+ const mapBuckets = (arr) => {
144
+ return arr.map(({ count, period_start }) => ({ count, periodStart: period_start }));
145
+ };
122
146
 
123
147
  class Emails {
124
148
  constructor(mailchannels) {
@@ -140,21 +164,21 @@ class Emails {
140
164
  * ```
141
165
  */
142
166
  async send(options, dryRun = false) {
167
+ let error = null;
143
168
  const { cc, bcc, from, to, html, text, mustaches, dkim } = options;
144
- const result = { success: false, data: null, error: null };
145
169
  const parsedFrom = parseRecipient(from);
146
170
  if (!parsedFrom || !parsedFrom.email) {
147
- result.error = "No sender provided. Use the `from` option to specify a sender";
148
- return result;
171
+ error = createError("No sender provided. Use the `from` option to specify a sender");
172
+ return { success: false, data: null, error };
149
173
  }
150
174
  const parsedTo = parseArrayRecipients(to);
151
175
  if (!parsedTo || !parsedTo.length) {
152
- result.error = "No recipients provided. Use the `to` option to specify at least one recipient";
153
- return result;
176
+ error = createError("No recipients provided. Use the `to` option to specify at least one recipient");
177
+ return { success: false, data: null, error };
154
178
  }
155
179
  if (!text && !html) {
156
- result.error = "No email content provided";
157
- return result;
180
+ error = createError("No email content provided");
181
+ return { success: false, data: null, error };
158
182
  }
159
183
  const content = [];
160
184
  const template_type = mustaches ? "mustache" : void 0;
@@ -186,30 +210,29 @@ class Emails {
186
210
  const response = await this.mailchannels.post("/tx/v1/send", {
187
211
  query: { "dry-run": dryRun },
188
212
  body: payload,
189
- onResponse: async ({ response: response2 }) => {
190
- if (response2.ok) {
191
- result.success = true;
192
- return;
193
- }
194
- result.error = getStatusError(response2, {
213
+ onResponseError: async ({ response: response2 }) => {
214
+ error = getStatusError(response2, {
195
215
  [ErrorCode.BadRequest]: "Bad Request.",
196
216
  [ErrorCode.Forbidden]: "User does not have access to this feature.",
197
217
  [ErrorCode.PayloadTooLarge]: "The total message size should not exceed 30MB. This includes the message itself, headers, and the combined size of any attachments."
198
218
  });
199
219
  }
200
- }).catch(() => null);
201
- if (!response) return result;
202
- result.data = clean({
220
+ }).catch((e) => {
221
+ error ||= getResultError(e, "Failed to send email.");
222
+ return null;
223
+ });
224
+ if (!response) return { success: false, data: null, error };
225
+ const data = clean({
203
226
  rendered: response.data,
204
227
  requestId: response.request_id,
205
- results: response.results?.map((result2) => ({
206
- index: result2.index,
207
- messageId: result2.message_id,
208
- reason: result2.reason,
209
- status: result2.status
228
+ results: response.results?.map((result) => ({
229
+ index: result.index,
230
+ messageId: result.message_id,
231
+ reason: result.reason,
232
+ status: result.status
210
233
  }))
211
234
  });
212
- return result;
235
+ return { success: !!data, data, error: null };
213
236
  }
214
237
  /**
215
238
  * 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.
@@ -229,9 +252,9 @@ class Emails {
229
252
  * ```
230
253
  */
231
254
  async checkDomain(options) {
255
+ let error = null;
232
256
  const { dkim, domain, senderId } = options;
233
257
  const dkimOptions = dkim ? Array.isArray(dkim) ? dkim : [dkim] : void 0;
234
- const result = { data: null, error: null };
235
258
  const payload = {
236
259
  dkim_settings: dkimOptions?.map(({ domain: domain2, privateKey, selector }) => ({
237
260
  dkim_domain: domain2,
@@ -244,14 +267,17 @@ class Emails {
244
267
  const response = await this.mailchannels.post("/tx/v1/check-domain", {
245
268
  body: payload,
246
269
  onResponseError: async ({ response: response2 }) => {
247
- result.error = getStatusError(response2, {
270
+ error = getStatusError(response2, {
248
271
  [ErrorCode.BadRequest]: "Bad Request.",
249
272
  [ErrorCode.Forbidden]: "User does not have access to this feature."
250
273
  });
251
274
  }
252
- }).catch(() => null);
253
- if (!response) return result;
254
- result.data = clean({
275
+ }).catch((e) => {
276
+ error ||= getResultError(e, "Failed to check domain.");
277
+ return null;
278
+ });
279
+ if (!response) return { data: null, error };
280
+ const data = clean({
255
281
  dkim: response.check_results.dkim.map((dkimResults) => ({
256
282
  domain: dkimResults.dkim_domain,
257
283
  keyStatus: dkimResults.dkim_key_status,
@@ -264,7 +290,7 @@ class Emails {
264
290
  spf: response.check_results.spf,
265
291
  references: response.references
266
292
  });
267
- return result;
293
+ return { data, error: null };
268
294
  }
269
295
  /**
270
296
  * Create a DKIM key pair for a specified domain and selector using the specified algorithm and key length, for the current customer.
@@ -279,11 +305,11 @@ class Emails {
279
305
  * ```
280
306
  */
281
307
  async createDkimKey(domain, options) {
282
- const result = { data: null, error: null };
308
+ let error = null;
283
309
  if (!options.selector || options.selector.length > 63) {
284
- result.error = "Selector must be between 1 and 63 characters.";
310
+ error = createError("Selector must be between 1 and 63 characters.");
311
+ return { data: null, error };
285
312
  }
286
- if (result.error) return result;
287
313
  const payload = {
288
314
  algorithm: options.algorithm,
289
315
  key_length: options.length,
@@ -292,14 +318,17 @@ class Emails {
292
318
  const response = await this.mailchannels.post(`/tx/v1/domains/${domain}/dkim-keys`, {
293
319
  body: payload,
294
320
  onResponseError: async ({ response: response2 }) => {
295
- result.error = getStatusError(response2, {
321
+ error = getStatusError(response2, {
296
322
  [ErrorCode.BadRequest]: "Bad Request.",
297
323
  [ErrorCode.Conflict]: "Key pair already created for domain, and selector."
298
324
  });
299
325
  }
300
- }).catch(() => null);
301
- if (!response) return result;
302
- result.data = clean({
326
+ }).catch((e) => {
327
+ error ||= getResultError(e, "Failed to create DKIM key.");
328
+ return null;
329
+ });
330
+ if (!response) return { data: null, error };
331
+ const data = clean({
303
332
  algorithm: response.algorithm,
304
333
  createdAt: response.created_at,
305
334
  dnsRecords: response.dkim_dns_records,
@@ -312,7 +341,7 @@ class Emails {
312
341
  status: response.status,
313
342
  statusModifiedAt: response.status_modified_at
314
343
  });
315
- return result;
344
+ return { data, error: null };
316
345
  }
317
346
  /**
318
347
  * Search for DKIM keys by domain, with optional filters. If selector is provided, at most one key will be returned.
@@ -327,19 +356,13 @@ class Emails {
327
356
  * ```
328
357
  */
329
358
  async getDkimKeys(domain, options) {
330
- const result = { data: null, error: null };
359
+ let error = null;
331
360
  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.";
341
- return result;
361
+ error = createError("Selector must be between 1 and 63 characters.");
362
+ return { data: null, error };
342
363
  }
364
+ error = validateLimit(options?.limit, 100) || validateOffset(options?.offset);
365
+ if (error) return { data: null, error };
343
366
  const payload = {
344
367
  selector: options?.selector,
345
368
  status: options?.status,
@@ -350,13 +373,16 @@ class Emails {
350
373
  const response = await this.mailchannels.get(`/tx/v1/domains/${domain}/dkim-keys`, {
351
374
  query: payload,
352
375
  onResponseError: async ({ response: response2 }) => {
353
- result.error = getStatusError(response2, {
376
+ error = getStatusError(response2, {
354
377
  [ErrorCode.BadRequest]: "Bad Request."
355
378
  });
356
379
  }
357
- }).catch(() => null);
358
- if (!response) return result;
359
- result.data = clean(response.keys.map((key) => ({
380
+ }).catch((e) => {
381
+ error ||= getResultError(e, "Failed to fetch DKIM keys.");
382
+ return null;
383
+ });
384
+ if (!response) return { data: null, error };
385
+ const data = clean(response.keys.map((key) => ({
360
386
  algorithm: key.algorithm,
361
387
  createdAt: key.created_at,
362
388
  dnsRecords: key.dkim_dns_records,
@@ -369,7 +395,7 @@ class Emails {
369
395
  status: key.status,
370
396
  statusModifiedAt: key.status_modified_at
371
397
  })));
372
- return result;
398
+ return { data, error: null };
373
399
  }
374
400
  /**
375
401
  * Update fields of an existing DKIM key pair for the specified domain and selector, for the current customer. Currently, only the `status` field can be updated.
@@ -384,30 +410,26 @@ class Emails {
384
410
  * })
385
411
  */
386
412
  async updateDkimKey(domain, options) {
387
- const result = { success: false, error: null };
413
+ let error = null;
388
414
  if (!options.selector || options.selector.length > 63) {
389
- result.error = "Selector must be between 1 and 63 characters.";
390
- return result;
415
+ error = createError("Selector must be between 1 and 63 characters.");
416
+ return { success: false, error };
391
417
  }
392
418
  const payload = {
393
419
  status: options.status
394
420
  };
395
421
  await this.mailchannels.patch(`/tx/v1/domains/${domain}/dkim-keys/${options.selector}`, {
396
422
  body: payload,
397
- onResponse: async ({ response }) => {
398
- if (response.ok) {
399
- result.success = true;
400
- return;
401
- }
402
- result.error = getStatusError(response, {
423
+ onResponseError: async ({ response }) => {
424
+ error = getStatusError(response, {
403
425
  [ErrorCode.BadRequest]: "Bad Request.",
404
426
  [ErrorCode.NotFound]: "Specified key pair not found, or no active key for rotation. This may also occur if the DKIM domain or selector path parameter is missing."
405
427
  });
406
428
  }
407
- }).catch((error) => {
408
- result.error = error instanceof Error ? error.message : "Failed to update DKIM key.";
429
+ }).catch((e) => {
430
+ error ||= getResultError(e, "Failed to update DKIM key.");
409
431
  });
410
- return result;
432
+ return { success: !error, error };
411
433
  }
412
434
  /**
413
435
  * 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.
@@ -426,14 +448,14 @@ class Emails {
426
448
  * ```
427
449
  */
428
450
  async rotateDkimKey(domain, selector, options) {
429
- const result = { data: null, error: null };
451
+ let error = null;
430
452
  if (!selector || selector.length > 63) {
431
- result.error = "Selector must be between 1 and 63 characters.";
432
- return result;
453
+ error = createError("Selector must be between 1 and 63 characters.");
454
+ return { data: null, error };
433
455
  }
434
456
  if (!options.newKey.selector || options.newKey.selector.length > 63) {
435
- result.error = "New key selector must be between 1 and 63 characters.";
436
- return result;
457
+ error = createError("New key selector must be between 1 and 63 characters.");
458
+ return { data: null, error };
437
459
  }
438
460
  const payload = {
439
461
  new_key: {
@@ -443,20 +465,18 @@ class Emails {
443
465
  const response = await this.mailchannels.post(`/tx/v1/domains/${domain}/dkim-keys/${selector}/rotate`, {
444
466
  body: payload,
445
467
  onResponseError: async ({ response: response2 }) => {
446
- result.error = getStatusError(response2, {
468
+ error = getStatusError(response2, {
447
469
  [ErrorCode.BadRequest]: "Bad Request.",
448
470
  [ErrorCode.NotFound]: "Specified key pair not found.",
449
471
  [ErrorCode.Conflict]: "Key pair already created for domain, and provided new key selector."
450
472
  });
451
473
  }
452
- }).catch((error) => {
453
- if (!result.error) {
454
- result.error = error instanceof Error ? error.message : "Failed to rotate DKIM key.";
455
- }
474
+ }).catch((e) => {
475
+ error ||= getResultError(e, "Failed to rotate DKIM key.");
456
476
  return null;
457
477
  });
458
- if (!response) return result;
459
- result.data = clean({
478
+ if (!response) return { data: null, error };
479
+ const data = clean({
460
480
  new: {
461
481
  algorithm: response.new_key.algorithm,
462
482
  createdAt: response.new_key.created_at,
@@ -484,7 +504,7 @@ class Emails {
484
504
  statusModifiedAt: response.rotated_key.status_modified_at
485
505
  }
486
506
  });
487
- return result;
507
+ return { data, error: null };
488
508
  }
489
509
  }
490
510
 
@@ -502,31 +522,28 @@ class Webhooks {
502
522
  * ```
503
523
  */
504
524
  async enroll(endpoint) {
505
- const result = { success: false, error: null };
525
+ let error = null;
506
526
  if (!endpoint) {
507
- result.error = "No endpoint provided.";
508
- return result;
527
+ error = createError("No endpoint provided.");
528
+ return { success: false, error };
509
529
  }
510
530
  if (endpoint.length > 8e3) {
511
- result.error = "The endpoint exceeds the maximum length of 8000 characters.";
512
- return result;
531
+ error = createError("The endpoint exceeds the maximum length of 8000 characters.");
532
+ return { success: false, error };
513
533
  }
514
534
  await this.mailchannels.post("/tx/v1/webhook", {
515
535
  query: {
516
536
  endpoint
517
537
  },
518
- ignoreResponseError: true,
519
- onResponse: async ({ response }) => {
520
- if (response.ok) {
521
- result.success = true;
522
- return;
523
- }
524
- result.error = getStatusError(response, {
538
+ onResponseError: async ({ response }) => {
539
+ error = getStatusError(response, {
525
540
  [ErrorCode.Conflict]: `Endpoint '${endpoint}' is already enrolled to receive notifications.`
526
541
  });
527
542
  }
543
+ }).catch((e) => {
544
+ error ||= getResultError(e, "Failed to enroll webhook.");
528
545
  });
529
- return result;
546
+ return { success: !error, error };
530
547
  }
531
548
  /**
532
549
  * Retrieves all registered webhook endpoints associated with the customer.
@@ -537,20 +554,18 @@ class Webhooks {
537
554
  * ```
538
555
  */
539
556
  async list() {
540
- const result = { data: null, error: null };
557
+ let error = null;
541
558
  const response = await this.mailchannels.get("/tx/v1/webhook", {
542
559
  onResponseError: async ({ response: response2 }) => {
543
- result.error = getStatusError(response2);
544
- }
545
- }).catch((error) => {
546
- if (!result.error) {
547
- result.error = error instanceof Error ? error.message : "Failed to fetch webhooks.";
560
+ error = getStatusError(response2);
548
561
  }
562
+ }).catch((e) => {
563
+ error ||= getResultError(e, "Failed to fetch webhooks.");
549
564
  return null;
550
565
  });
551
- if (!response) return result;
552
- result.data = clean(response.map(({ webhook }) => webhook));
553
- return result;
566
+ if (!response) return { data: null, error };
567
+ const data = clean(response.map(({ webhook }) => webhook));
568
+ return { data, error: null };
554
569
  }
555
570
  /**
556
571
  * Deletes all registered webhook endpoints for the customer.
@@ -561,18 +576,15 @@ class Webhooks {
561
576
  * ```
562
577
  */
563
578
  async delete() {
564
- const result = { success: false, error: null };
579
+ let error = null;
565
580
  await this.mailchannels.delete("/tx/v1/webhook", {
566
- ignoreResponseError: true,
567
- onResponse: async ({ response }) => {
568
- if (!response.ok) {
569
- result.error = getStatusError(response);
570
- return;
571
- }
572
- result.success = true;
581
+ onResponseError: async ({ response }) => {
582
+ error = getStatusError(response);
573
583
  }
584
+ }).catch((e) => {
585
+ error ||= getResultError(e, "Failed to delete webhooks.");
574
586
  });
575
- return result;
587
+ return { success: !error, error };
576
588
  }
577
589
  /**
578
590
  * Retrieves the public key used to verify signatures on incoming webhook payloads.
@@ -584,21 +596,24 @@ class Webhooks {
584
596
  * ```
585
597
  */
586
598
  async getSigningKey(id) {
587
- const result = { data: null, error: null };
599
+ let error = null;
588
600
  const response = await this.mailchannels.get("/tx/v1/webhook/public-key", {
589
601
  query: {
590
602
  id
591
603
  },
592
- onResponseError: ({ response: response2 }) => {
593
- result.error = getStatusError(response2, {
604
+ onResponseError: async ({ response: response2 }) => {
605
+ error = getStatusError(response2, {
594
606
  [ErrorCode.BadRequest]: "Bad Request.",
595
607
  [ErrorCode.NotFound]: `The key '${id}' is not found.`
596
608
  });
597
609
  }
598
- }).catch(() => null);
599
- if (!response) return result;
600
- result.data = clean({ key: response.key });
601
- return result;
610
+ }).catch((e) => {
611
+ error ||= getResultError(e, "Failed to get signing key.");
612
+ return null;
613
+ });
614
+ if (!response) return { data: null, error };
615
+ const data = clean({ key: response.key });
616
+ return { data, error: null };
602
617
  }
603
618
  /**
604
619
  * Validates whether your enrolled webhook(s) respond with an HTTP `2xx` status code. Sends a test request to each webhook containing your customer handle, a hardcoded event type (`test`), a hardcoded sender email (`test@mailchannels.com`), a timestamp, a request ID (provided or generated), and an SMTP ID. The response includes the HTTP status code and body returned by each webhook.
@@ -610,28 +625,31 @@ class Webhooks {
610
625
  * ```
611
626
  */
612
627
  async validate(requestId) {
613
- const result = { data: null, error: null };
628
+ let error = null;
614
629
  if (requestId && requestId.length > 28) {
615
- result.error = "The request id should not exceed 28 characters.";
616
- return result;
630
+ error = createError("The request id should not exceed 28 characters.");
631
+ return { data: null, error };
617
632
  }
618
633
  const response = await this.mailchannels.post("/tx/v1/webhook/validate", {
619
634
  body: {
620
635
  request_id: requestId
621
636
  },
622
- onResponseError: ({ response: response2 }) => {
623
- result.error = getStatusError(response2, {
637
+ onResponseError: async ({ response: response2 }) => {
638
+ error = getStatusError(response2, {
624
639
  [ErrorCode.BadRequest]: "Bad Request.",
625
640
  [ErrorCode.NotFound]: "No webhooks found for the account."
626
641
  });
627
642
  }
628
- }).catch(() => null);
629
- if (!response) return result;
630
- result.data = clean({
643
+ }).catch((e) => {
644
+ error ||= getResultError(e, "Failed to validate webhooks.");
645
+ return null;
646
+ });
647
+ if (!response) return { data: null, error };
648
+ const data = clean({
631
649
  allPassed: response.all_passed,
632
650
  results: response.results
633
651
  });
634
- return result;
652
+ return { data, error: null };
635
653
  }
636
654
  }
637
655
 
@@ -652,17 +670,17 @@ class SubAccounts {
652
670
  * ```
653
671
  */
654
672
  async create(companyName, handle) {
655
- const result = { data: null, error: null };
673
+ let error = null;
656
674
  const isValidCompany = SubAccounts.COMPANY_PATTERN.test(companyName);
657
675
  if (!isValidCompany) {
658
- result.error = "Invalid company name. Company name must be between 3 and 128 characters.";
659
- return result;
676
+ error = createError("Invalid company name. Company name must be between 3 and 128 characters.");
677
+ return { data: null, error };
660
678
  }
661
679
  if (handle) {
662
680
  const isValidHandle = SubAccounts.HANDLE_PATTERN.test(handle);
663
681
  if (!isValidHandle) {
664
- result.error = "Invalid handle. Sub-account handle must be between 3 and 128 characters and contain only lowercase letters and numbers.";
665
- return result;
682
+ error = createError("Invalid handle. Sub-account handle must be between 3 and 128 characters and contain only lowercase letters and numbers.");
683
+ return { data: null, error };
666
684
  }
667
685
  }
668
686
  const response = await this.mailchannels.post("/tx/v1/sub-account", {
@@ -670,20 +688,23 @@ class SubAccounts {
670
688
  company_name: companyName,
671
689
  handle
672
690
  },
673
- onResponseError: ({ response: response2 }) => {
674
- result.error = getStatusError(response2, {
691
+ onResponseError: async ({ response: response2 }) => {
692
+ error = getStatusError(response2, {
675
693
  [ErrorCode.Forbidden]: "The parent account does not have permission to create sub-accounts.",
676
694
  [ErrorCode.Conflict]: `Sub-account with handle '${handle}' already exists.`
677
695
  });
678
696
  }
679
- }).catch(() => null);
680
- if (!response) return result;
681
- result.data = clean({
697
+ }).catch((e) => {
698
+ error ||= getResultError(e, "Failed to create sub-account.");
699
+ return null;
700
+ });
701
+ if (!response) return { data: null, error };
702
+ const data = clean({
682
703
  companyName: response.company_name,
683
704
  enabled: response.enabled,
684
705
  handle: response.handle
685
706
  });
686
- return result;
707
+ return { data, error: null };
687
708
  }
688
709
  /**
689
710
  * Retrieves all sub-accounts associated with the parent account. The response is paginated with a default limit of 1000 sub-accounts per page and an offset of 0.
@@ -695,33 +716,25 @@ class SubAccounts {
695
716
  * ```
696
717
  */
697
718
  async list(options) {
698
- 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
- }
719
+ let error = null;
720
+ error = validateLimit(options?.limit, 1e3) || validateOffset(options?.offset);
721
+ if (error) return { data: null, error };
707
722
  const response = await this.mailchannels.get("/tx/v1/sub-account", {
708
723
  query: options,
709
724
  onResponseError: async ({ response: response2 }) => {
710
- result.error = getStatusError(response2);
711
- }
712
- }).catch((error) => {
713
- if (!result.error) {
714
- result.error = error instanceof Error ? error.message : "Failed to fetch sub-accounts.";
725
+ error = getStatusError(response2);
715
726
  }
727
+ }).catch((e) => {
728
+ error ||= getResultError(e, "Failed to fetch sub-accounts.");
716
729
  return null;
717
730
  });
718
- if (!response) return result;
719
- result.data = clean(response.map((account) => ({
731
+ if (!response) return { data: null, error };
732
+ const data = clean(response.map((account) => ({
720
733
  companyName: account.company_name,
721
734
  enabled: account.enabled,
722
735
  handle: account.handle
723
736
  })));
724
- return result;
737
+ return { data, error: null };
725
738
  }
726
739
  /**
727
740
  * Deletes the sub-account identified by its handle.
@@ -732,22 +745,19 @@ class SubAccounts {
732
745
  * ```
733
746
  */
734
747
  async delete(handle) {
735
- const result = { success: false, error: null };
748
+ let error = null;
736
749
  if (!handle) {
737
- result.error = "No handle provided.";
738
- return result;
750
+ error = createError("No handle provided.");
751
+ return { success: false, error };
739
752
  }
740
753
  await this.mailchannels.delete(`/tx/v1/sub-account/${handle}`, {
741
- ignoreResponseError: true,
742
- onResponse: async ({ response }) => {
743
- if (!response.ok) {
744
- result.error = getStatusError(response);
745
- return;
746
- }
747
- result.success = true;
754
+ onResponseError: async ({ response }) => {
755
+ error = getStatusError(response);
748
756
  }
757
+ }).catch((e) => {
758
+ error ||= getResultError(e, "Failed to delete sub-account.");
749
759
  });
750
- return result;
760
+ return { success: !error, error };
751
761
  }
752
762
  /**
753
763
  * Suspends the sub-account identified by its handle. This action disables the account, preventing it from sending any emails until it is reactivated.
@@ -759,24 +769,21 @@ class SubAccounts {
759
769
  * ```
760
770
  */
761
771
  async suspend(handle) {
762
- const result = { success: false, error: null };
772
+ let error = null;
763
773
  if (!handle) {
764
- result.error = "No handle provided.";
765
- return result;
774
+ error = createError("No handle provided.");
775
+ return { success: false, error };
766
776
  }
767
777
  await this.mailchannels.post(`/tx/v1/sub-account/${handle}/suspend`, {
768
- ignoreResponseError: true,
769
- onResponse: async ({ response }) => {
770
- if (response.ok) {
771
- result.success = true;
772
- return;
773
- }
774
- result.error = getStatusError(response, {
778
+ onResponseError: async ({ response }) => {
779
+ error = getStatusError(response, {
775
780
  [ErrorCode.NotFound]: `The specified sub-account '${handle}' does not exist.`
776
781
  });
777
782
  }
783
+ }).catch((e) => {
784
+ error ||= getResultError(e, "Failed to suspend sub-account.");
778
785
  });
779
- return result;
786
+ return { success: !error, error };
780
787
  }
781
788
  /**
782
789
  * Activates a suspended sub-account identified by its handle, restoring its ability to send emails.
@@ -788,25 +795,22 @@ class SubAccounts {
788
795
  * ```
789
796
  */
790
797
  async activate(handle) {
791
- const result = { success: false, error: null };
798
+ let error = null;
792
799
  if (!handle) {
793
- result.error = "No handle provided.";
794
- return result;
800
+ error = createError("No handle provided.");
801
+ return { success: false, error };
795
802
  }
796
803
  await this.mailchannels.post(`/tx/v1/sub-account/${handle}/activate`, {
797
- ignoreResponseError: true,
798
- onResponse: async ({ response }) => {
799
- if (response.ok) {
800
- result.success = true;
801
- return;
802
- }
803
- result.error = getStatusError(response, {
804
+ onResponseError: async ({ response }) => {
805
+ error = getStatusError(response, {
804
806
  [ErrorCode.Forbidden]: "The parent account does not have permission to activate the sub-account.",
805
807
  [ErrorCode.NotFound]: `The specified sub-account '${handle}' does not exist.`
806
808
  });
807
809
  }
810
+ }).catch((e) => {
811
+ error ||= getResultError(e, "Failed to activate sub-account.");
808
812
  });
809
- return result;
813
+ return { success: !error, error };
810
814
  }
811
815
  /**
812
816
  * Creates a new API key for the specified sub-account.
@@ -818,26 +822,29 @@ class SubAccounts {
818
822
  * ```
819
823
  */
820
824
  async createApiKey(handle) {
821
- const result = { data: null, error: null };
825
+ let error = null;
822
826
  if (!handle) {
823
- result.error = "No handle provided.";
824
- return result;
827
+ error = createError("No handle provided.");
828
+ return { data: null, error };
825
829
  }
826
830
  const response = await this.mailchannels.post(`/tx/v1/sub-account/${handle}/api-key`, {
827
831
  onResponseError: async ({ response: response2 }) => {
828
- result.error = getStatusError(response2, {
832
+ error = getStatusError(response2, {
829
833
  [ErrorCode.Forbidden]: "You can't create API keys for this sub-account.",
830
834
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`,
831
835
  [ErrorCode.UnprocessableEntity]: "You have reached the limit of API keys you can create for this sub-account."
832
836
  });
833
837
  }
834
- }).catch(() => null);
835
- if (!response) return result;
836
- result.data = clean({
838
+ }).catch((e) => {
839
+ error ||= getResultError(e, "Failed to create sub-account API key.");
840
+ return null;
841
+ });
842
+ if (!response) return { data: null, error };
843
+ const data = clean({
837
844
  id: response.id,
838
845
  value: response.key
839
846
  });
840
- return result;
847
+ return { data, error: null };
841
848
  }
842
849
  /**
843
850
  * Retrieves details of all API keys associated with the specified sub-account. For security reasons, the full API key is not returned; only the key ID and a partially redacted version are provided.
@@ -850,32 +857,29 @@ class SubAccounts {
850
857
  * ```
851
858
  */
852
859
  async listApiKeys(handle, options) {
853
- const result = { data: null, error: null };
860
+ let error = null;
854
861
  if (!handle) {
855
- result.error = "No handle provided.";
856
- return result;
857
- }
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;
862
+ error = createError("No handle provided.");
863
+ return { data: null, error };
865
864
  }
865
+ error = validateLimit(options?.limit, 1e3) || validateOffset(options?.offset);
866
+ if (error) return { data: null, error };
866
867
  const response = await this.mailchannels.get(`/tx/v1/sub-account/${handle}/api-key`, {
867
868
  onResponseError: async ({ response: response2 }) => {
868
- result.error = getStatusError(response2, {
869
+ error = getStatusError(response2, {
869
870
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`
870
871
  });
871
872
  }
872
- }).catch(() => null);
873
- if (!response) return result;
874
- result.data = clean(response.map((key) => ({
873
+ }).catch((e) => {
874
+ error ||= getResultError(e, "Failed to fetch sub-account API keys.");
875
+ return null;
876
+ });
877
+ if (!response) return { data: null, error };
878
+ const data = clean(response.map((key) => ({
875
879
  id: key.id,
876
880
  value: key.key
877
881
  })));
878
- return result;
882
+ return { data, error: null };
879
883
  }
880
884
  /**
881
885
  * Deletes the API key identified by its ID for the specified sub-account.
@@ -888,24 +892,21 @@ class SubAccounts {
888
892
  * ```
889
893
  */
890
894
  async deleteApiKey(handle, id) {
891
- const result = { success: false, error: null };
895
+ let error = null;
892
896
  if (!handle) {
893
- result.error = "No handle provided.";
894
- return result;
897
+ error = createError("No handle provided.");
898
+ return { success: false, error };
895
899
  }
896
900
  await this.mailchannels.delete(`/tx/v1/sub-account/${handle}/api-key/${id}`, {
897
- ignoreResponseError: true,
898
- onResponse: async ({ response }) => {
899
- if (response.ok) {
900
- result.success = true;
901
- return;
902
- }
903
- result.error = getStatusError(response, {
901
+ onResponseError: async ({ response }) => {
902
+ error = getStatusError(response, {
904
903
  [ErrorCode.BadRequest]: "Missing or invalid API key ID."
905
904
  });
906
905
  }
906
+ }).catch((e) => {
907
+ error ||= getResultError(e, "Failed to delete sub-account API key.");
907
908
  });
908
- return result;
909
+ return { success: !error, error };
909
910
  }
910
911
  /**
911
912
  * Creates a new SMTP password for the specified sub-account.
@@ -917,27 +918,30 @@ class SubAccounts {
917
918
  * ```
918
919
  */
919
920
  async createSmtpPassword(handle) {
920
- const result = { data: null, error: null };
921
+ let error = null;
921
922
  if (!handle) {
922
- result.error = "No handle provided.";
923
- return result;
923
+ error = createError("No handle provided.");
924
+ return { data: null, error };
924
925
  }
925
926
  const response = await this.mailchannels.post(`/tx/v1/sub-account/${handle}/smtp-password`, {
926
927
  onResponseError: async ({ response: response2 }) => {
927
- result.error = getStatusError(response2, {
928
+ error = getStatusError(response2, {
928
929
  [ErrorCode.Forbidden]: "You can't create SMTP passwords for this sub-account.",
929
930
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`,
930
931
  [ErrorCode.UnprocessableEntity]: "You have reached the limit of SMTP passwords you can create for this sub-account."
931
932
  });
932
933
  }
933
- }).catch(() => null);
934
- if (!response) return result;
935
- result.data = clean({
934
+ }).catch((e) => {
935
+ error ||= getResultError(e, "Failed to create sub-account SMTP password.");
936
+ return null;
937
+ });
938
+ if (!response) return { data: null, error };
939
+ const data = clean({
936
940
  enabled: response.enabled,
937
941
  id: response.id,
938
942
  value: response.smtp_password
939
943
  });
940
- return result;
944
+ return { data, error: null };
941
945
  }
942
946
  /**
943
947
  * Retrieves details of all SMTP passwords associated with the specified sub-account. For security, the full SMTP password is not returned; only the password ID and a partially redacted version are provided.
@@ -949,25 +953,28 @@ class SubAccounts {
949
953
  * ```
950
954
  */
951
955
  async listSmtpPasswords(handle) {
952
- const result = { data: null, error: null };
956
+ let error = null;
953
957
  if (!handle) {
954
- result.error = "No handle provided.";
955
- return result;
958
+ error = createError("No handle provided.");
959
+ return { data: null, error };
956
960
  }
957
961
  const response = await this.mailchannels.get(`/tx/v1/sub-account/${handle}/smtp-password`, {
958
962
  onResponseError: async ({ response: response2 }) => {
959
- result.error = getStatusError(response2, {
963
+ error = getStatusError(response2, {
960
964
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`
961
965
  });
962
966
  }
963
- }).catch(() => null);
964
- if (!response) return result;
965
- result.data = clean(response.map((password) => ({
967
+ }).catch((e) => {
968
+ error ||= getResultError(e, "Failed to fetch sub-account SMTP passwords.");
969
+ return null;
970
+ });
971
+ if (!response) return { data: null, error };
972
+ const data = clean(response.map((password) => ({
966
973
  enabled: password.enabled,
967
974
  id: password.id,
968
975
  value: password.smtp_password
969
976
  })));
970
- return result;
977
+ return { data, error: null };
971
978
  }
972
979
  /**
973
980
  * Deletes the SMTP password identified by its ID for the specified sub-account.
@@ -980,24 +987,21 @@ class SubAccounts {
980
987
  * ```
981
988
  */
982
989
  async deleteSmtpPassword(handle, id) {
983
- const result = { success: false, error: null };
990
+ let error = null;
984
991
  if (!handle) {
985
- result.error = "No handle provided.";
986
- return result;
992
+ error = createError("No handle provided.");
993
+ return { success: false, error };
987
994
  }
988
995
  await this.mailchannels.delete(`/tx/v1/sub-account/${handle}/smtp-password/${id}`, {
989
- ignoreResponseError: true,
990
- onResponse: async ({ response }) => {
991
- if (response.ok) {
992
- result.success = true;
993
- return;
994
- }
995
- result.error = getStatusError(response, {
996
+ onResponseError: async ({ response }) => {
997
+ error = getStatusError(response, {
996
998
  [ErrorCode.BadRequest]: "Missing or invalid SMTP password ID."
997
999
  });
998
1000
  }
1001
+ }).catch((e) => {
1002
+ error ||= getResultError(e, "Failed to delete sub-account SMTP password.");
999
1003
  });
1000
- return result;
1004
+ return { success: !error, error };
1001
1005
  }
1002
1006
  /**
1003
1007
  * Retrieves the limit of a specified sub-account. A value of `-1` indicates that the sub-account inherits the parent account's limit, allowing the sub-account to utilize any remaining capacity within the parent account's allocation.
@@ -1009,21 +1013,24 @@ class SubAccounts {
1009
1013
  * ```
1010
1014
  */
1011
1015
  async getLimit(handle) {
1012
- const result = { data: null, error: null };
1016
+ let error = null;
1013
1017
  if (!handle) {
1014
- result.error = "No handle provided.";
1015
- return result;
1018
+ error = createError("No handle provided.");
1019
+ return { data: null, error };
1016
1020
  }
1017
1021
  const response = await this.mailchannels.get(`/tx/v1/sub-account/${handle}/limit`, {
1018
1022
  onResponseError: async ({ response: response2 }) => {
1019
- result.error = getStatusError(response2, {
1023
+ error = getStatusError(response2, {
1020
1024
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`
1021
1025
  });
1022
1026
  }
1023
- }).catch(() => null);
1024
- if (!response) return result;
1025
- result.data = clean(response);
1026
- return result;
1027
+ }).catch((e) => {
1028
+ error ||= getResultError(e, "Failed to fetch sub-account limit.");
1029
+ return null;
1030
+ });
1031
+ if (!response) return { data: null, error };
1032
+ const data = clean(response);
1033
+ return { data, error: null };
1027
1034
  }
1028
1035
  /**
1029
1036
  * Sets the limit for the specified sub-account.
@@ -1036,26 +1043,23 @@ class SubAccounts {
1036
1043
  * ```
1037
1044
  */
1038
1045
  async setLimit(handle, limit) {
1039
- const result = { success: false, error: null };
1046
+ let error = null;
1040
1047
  if (!handle) {
1041
- result.error = "No handle provided.";
1042
- return result;
1048
+ error = createError("No handle provided.");
1049
+ return { success: false, error };
1043
1050
  }
1044
1051
  await this.mailchannels.put(`/tx/v1/sub-account/${handle}/limit`, {
1045
1052
  body: limit,
1046
- ignoreResponseError: true,
1047
- onResponse: async ({ response }) => {
1048
- if (response.ok) {
1049
- result.success = true;
1050
- return;
1051
- }
1052
- result.error = getStatusError(response, {
1053
+ onResponseError: async ({ response }) => {
1054
+ error = getStatusError(response, {
1053
1055
  [ErrorCode.BadRequest]: "Bad Request.",
1054
1056
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`
1055
1057
  });
1056
1058
  }
1059
+ }).catch((e) => {
1060
+ error ||= getResultError(e, "Failed to set sub-account limit.");
1057
1061
  });
1058
- return result;
1062
+ return { success: !error, error };
1059
1063
  }
1060
1064
  /**
1061
1065
  * Deletes the limit for the specified sub-account. After a successful deletion, the specified sub-account will be limited to the parent account's limit.
@@ -1067,24 +1071,21 @@ class SubAccounts {
1067
1071
  * ```
1068
1072
  */
1069
1073
  async deleteLimit(handle) {
1070
- const result = { success: false, error: null };
1074
+ let error = null;
1071
1075
  if (!handle) {
1072
- result.error = "No handle provided.";
1073
- return result;
1076
+ error = createError("No handle provided.");
1077
+ return { success: false, error };
1074
1078
  }
1075
1079
  await this.mailchannels.delete(`/tx/v1/sub-account/${handle}/limit`, {
1076
- ignoreResponseError: true,
1077
- onResponse: async ({ response }) => {
1078
- if (response.ok) {
1079
- result.success = true;
1080
- return;
1081
- }
1082
- result.error = getStatusError(response, {
1080
+ onResponseError: async ({ response }) => {
1081
+ error = getStatusError(response, {
1083
1082
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`
1084
1083
  });
1085
1084
  }
1085
+ }).catch((e) => {
1086
+ error ||= getResultError(e, "Failed to delete sub-account limit.");
1086
1087
  });
1087
- return result;
1088
+ return { success: !error, error };
1088
1089
  }
1089
1090
  /**
1090
1091
  * Retrieves usage statistics for the specified sub-account during the current billing period.
@@ -1096,31 +1097,31 @@ class SubAccounts {
1096
1097
  * ```
1097
1098
  */
1098
1099
  async getUsage(handle) {
1099
- const result = { data: null, error: null };
1100
+ let error = null;
1100
1101
  if (!handle) {
1101
- result.error = "No handle provided.";
1102
- return result;
1102
+ error = createError("No handle provided.");
1103
+ return { data: null, error };
1103
1104
  }
1104
1105
  const response = await this.mailchannels.get(`/tx/v1/sub-account/${handle}/usage`, {
1105
1106
  onResponseError: async ({ response: response2 }) => {
1106
- result.error = getStatusError(response2, {
1107
+ error = getStatusError(response2, {
1107
1108
  [ErrorCode.NotFound]: `Sub-account with handle '${handle}' not found.`
1108
1109
  });
1109
1110
  }
1110
- }).catch(() => null);
1111
- if (!response) return result;
1112
- result.data = clean({
1111
+ }).catch((e) => {
1112
+ error ||= getResultError(e, "Failed to fetch sub-account usage.");
1113
+ return null;
1114
+ });
1115
+ if (!response) return { data: null, error };
1116
+ const data = clean({
1113
1117
  endDate: response.period_end_date,
1114
1118
  startDate: response.period_start_date,
1115
1119
  total: response.total_usage
1116
1120
  });
1117
- return result;
1121
+ return { data, error: null };
1118
1122
  }
1119
1123
  }
1120
1124
 
1121
- const mapBuckets = (arr) => {
1122
- return arr.map(({ count, period_start }) => ({ count, periodStart: period_start }));
1123
- };
1124
1125
  class Metrics {
1125
1126
  constructor(mailchannels) {
1126
1127
  this.mailchannels = mailchannels;
@@ -1135,7 +1136,7 @@ class Metrics {
1135
1136
  * ```
1136
1137
  */
1137
1138
  async engagement(options) {
1138
- const result = { data: null, error: null };
1139
+ let error = null;
1139
1140
  const response = await this.mailchannels.get("/tx/v1/metrics/engagement", {
1140
1141
  query: {
1141
1142
  start_time: options?.startTime,
@@ -1144,18 +1145,16 @@ class Metrics {
1144
1145
  interval: options?.interval
1145
1146
  },
1146
1147
  onResponseError: async ({ response: response2 }) => {
1147
- result.error = getStatusError(response2, {
1148
+ error = getStatusError(response2, {
1148
1149
  [ErrorCode.BadRequest]: "Bad Request."
1149
1150
  });
1150
1151
  }
1151
- }).catch((error) => {
1152
- if (!result.error) {
1153
- result.error = error instanceof Error ? error.message : "Failed to fetch engagement metrics.";
1154
- }
1152
+ }).catch((e) => {
1153
+ error ||= getResultError(e, "Failed to fetch engagement metrics.");
1155
1154
  return null;
1156
1155
  });
1157
- if (!response) return result;
1158
- result.data = clean({
1156
+ if (!response) return { data: null, error };
1157
+ const data = clean({
1159
1158
  buckets: {
1160
1159
  click: mapBuckets(response.buckets.click),
1161
1160
  clickTrackingDelivered: mapBuckets(response.buckets.click_tracking_delivered),
@@ -1169,7 +1168,7 @@ class Metrics {
1169
1168
  openTrackingDelivered: response.open_tracking_delivered,
1170
1169
  startTime: response.start_time
1171
1170
  });
1172
- return result;
1171
+ return { data, error: null };
1173
1172
  }
1174
1173
  /**
1175
1174
  * Retrieve performance metrics for messages sent from your account, including counts of processed, delivered, hard-bounced events. Supports optional filters for time range, and campaign ID.
@@ -1181,7 +1180,7 @@ class Metrics {
1181
1180
  * ```
1182
1181
  */
1183
1182
  async performance(options) {
1184
- const result = { data: null, error: null };
1183
+ let error = null;
1185
1184
  const response = await this.mailchannels.get("/tx/v1/metrics/performance", {
1186
1185
  query: {
1187
1186
  start_time: options?.startTime,
@@ -1190,18 +1189,16 @@ class Metrics {
1190
1189
  interval: options?.interval
1191
1190
  },
1192
1191
  onResponseError: async ({ response: response2 }) => {
1193
- result.error = getStatusError(response2, {
1192
+ error = getStatusError(response2, {
1194
1193
  [ErrorCode.BadRequest]: "Bad Request."
1195
1194
  });
1196
1195
  }
1197
- }).catch((error) => {
1198
- if (!result.error) {
1199
- result.error = error instanceof Error ? error.message : "Failed to fetch performance metrics.";
1200
- }
1196
+ }).catch((e) => {
1197
+ error ||= getResultError(e, "Failed to fetch performance metrics.");
1201
1198
  return null;
1202
1199
  });
1203
- if (!response) return result;
1204
- result.data = clean({
1200
+ if (!response) return { data: null, error };
1201
+ const data = clean({
1205
1202
  bounced: response.bounced,
1206
1203
  buckets: {
1207
1204
  bounced: mapBuckets(response.buckets.bounced),
@@ -1213,7 +1210,7 @@ class Metrics {
1213
1210
  processed: response.processed,
1214
1211
  startTime: response.start_time
1215
1212
  });
1216
- return result;
1213
+ return { data, error: null };
1217
1214
  }
1218
1215
  /**
1219
1216
  * Retrieve recipient behaviour metrics for messages sent from your account, including counts of unsubscribed events. Supports optional filters for time range, and campaign ID.
@@ -1225,7 +1222,7 @@ class Metrics {
1225
1222
  * ```
1226
1223
  */
1227
1224
  async recipientBehaviour(options) {
1228
- const result = { data: null, error: null };
1225
+ let error = null;
1229
1226
  const response = await this.mailchannels.get("/tx/v1/metrics/recipient-behaviour", {
1230
1227
  query: {
1231
1228
  start_time: options?.startTime,
@@ -1234,18 +1231,16 @@ class Metrics {
1234
1231
  interval: options?.interval
1235
1232
  },
1236
1233
  onResponseError: async ({ response: response2 }) => {
1237
- result.error = getStatusError(response2, {
1234
+ error = getStatusError(response2, {
1238
1235
  [ErrorCode.BadRequest]: "Bad Request."
1239
1236
  });
1240
1237
  }
1241
- }).catch((error) => {
1242
- if (!result.error) {
1243
- result.error = error instanceof Error ? error.message : "Failed to fetch recipient behaviour metrics.";
1244
- }
1238
+ }).catch((e) => {
1239
+ error ||= getResultError(e, "Failed to fetch recipient behaviour metrics.");
1245
1240
  return null;
1246
1241
  });
1247
- if (!response) return result;
1248
- result.data = clean({
1242
+ if (!response) return { data: null, error };
1243
+ const data = clean({
1249
1244
  buckets: {
1250
1245
  unsubscribeDelivered: mapBuckets(response.buckets.unsubscribe_delivered),
1251
1246
  unsubscribed: mapBuckets(response.buckets.unsubscribed)
@@ -1255,7 +1250,7 @@ class Metrics {
1255
1250
  unsubscribeDelivered: response.unsubscribe_delivered,
1256
1251
  unsubscribed: response.unsubscribed
1257
1252
  });
1258
- return result;
1253
+ return { data, error: null };
1259
1254
  }
1260
1255
  /**
1261
1256
  * Retrieve volume metrics for messages sent from your account, including counts of processed, delivered and dropped events. Supports optional filters for time range and campaign ID.
@@ -1267,7 +1262,7 @@ class Metrics {
1267
1262
  * ```
1268
1263
  */
1269
1264
  async volume(options) {
1270
- const result = { data: null, error: null };
1265
+ let error = null;
1271
1266
  const response = await this.mailchannels.get("/tx/v1/metrics/volume", {
1272
1267
  query: {
1273
1268
  start_time: options?.startTime,
@@ -1276,18 +1271,16 @@ class Metrics {
1276
1271
  interval: options?.interval
1277
1272
  },
1278
1273
  onResponseError: async ({ response: response2 }) => {
1279
- result.error = getStatusError(response2, {
1274
+ error = getStatusError(response2, {
1280
1275
  [ErrorCode.BadRequest]: "Bad Request."
1281
1276
  });
1282
1277
  }
1283
- }).catch((error) => {
1284
- if (!result.error) {
1285
- result.error = error instanceof Error ? error.message : "Failed to fetch volume metrics.";
1286
- }
1278
+ }).catch((e) => {
1279
+ error ||= getResultError(e, "Failed to fetch volume metrics.");
1287
1280
  return null;
1288
1281
  });
1289
- if (!response) return result;
1290
- result.data = clean({
1282
+ if (!response) return { data: null, error };
1283
+ const data = clean({
1291
1284
  buckets: {
1292
1285
  delivered: mapBuckets(response.buckets.delivered),
1293
1286
  dropped: mapBuckets(response.buckets.dropped),
@@ -1299,7 +1292,7 @@ class Metrics {
1299
1292
  processed: response.processed,
1300
1293
  startTime: response.start_time
1301
1294
  });
1302
- return result;
1295
+ return { data, error: null };
1303
1296
  }
1304
1297
  /**
1305
1298
  * Retrieves usage statistics during the current billing period.
@@ -1310,24 +1303,22 @@ class Metrics {
1310
1303
  * ```
1311
1304
  */
1312
1305
  async usage() {
1313
- const result = { data: null, error: null };
1306
+ let error = null;
1314
1307
  const response = await this.mailchannels.get("/tx/v1/usage", {
1315
1308
  onResponseError: async ({ response: response2 }) => {
1316
- result.error = getStatusError(response2);
1317
- }
1318
- }).catch((error) => {
1319
- if (!result.error) {
1320
- result.error = error instanceof Error ? error.message : "Failed to fetch usage metrics.";
1309
+ error = getStatusError(response2);
1321
1310
  }
1311
+ }).catch((e) => {
1312
+ error ||= getResultError(e, "Failed to fetch usage metrics.");
1322
1313
  return null;
1323
1314
  });
1324
- if (!response) return result;
1325
- result.data = clean({
1315
+ if (!response) return { data: null, error };
1316
+ const data = clean({
1326
1317
  endDate: response.period_end_date,
1327
1318
  startDate: response.period_start_date,
1328
1319
  total: response.total_usage
1329
1320
  });
1330
- return result;
1321
+ return { data, error: null };
1331
1322
  }
1332
1323
  /**
1333
1324
  * Retrieves a list of senders, either sub-accounts or campaigns, with their associated message metrics. Sorted by total # of sent messages (processed + dropped). Supports optional filter for time range, and optional settings for limit, offset, and sort order. Note: senders without any messages in the given time range will not be included in the results. The default time range is from one month ago to now, and the default sort order is descending.
@@ -1340,7 +1331,9 @@ class Metrics {
1340
1331
  * ```
1341
1332
  */
1342
1333
  async senders(type, options) {
1343
- const result = { data: null, error: null };
1334
+ let error = null;
1335
+ error = validateLimit(options?.limit, 1e3) || validateOffset(options?.offset);
1336
+ if (error) return { data: null, error };
1344
1337
  const response = await this.mailchannels.get(`/tx/v1/metrics/senders/${type}`, {
1345
1338
  query: {
1346
1339
  start_time: options?.startTime,
@@ -1350,18 +1343,16 @@ class Metrics {
1350
1343
  sort_order: options?.sortOrder
1351
1344
  },
1352
1345
  onResponseError: async ({ response: response2 }) => {
1353
- result.error = getStatusError(response2, {
1346
+ error = getStatusError(response2, {
1354
1347
  [ErrorCode.BadRequest]: "Bad Request."
1355
1348
  });
1356
1349
  }
1357
- }).catch((error) => {
1358
- if (!result.error) {
1359
- result.error = error instanceof Error ? error.message : "Failed to fetch senders metrics.";
1360
- }
1350
+ }).catch((e) => {
1351
+ error ||= getResultError(e, "Failed to fetch senders metrics.");
1361
1352
  return null;
1362
1353
  });
1363
- if (!response) return result;
1364
- result.data = clean({
1354
+ if (!response) return { data: null, error };
1355
+ const data = clean({
1365
1356
  endTime: response.end_time,
1366
1357
  limit: response.limit,
1367
1358
  offset: response.offset,
@@ -1369,7 +1360,7 @@ class Metrics {
1369
1360
  startTime: response.start_time,
1370
1361
  total: response.total
1371
1362
  });
1372
- return result;
1363
+ return { data, error: null };
1373
1364
  }
1374
1365
  }
1375
1366
 
@@ -1388,7 +1379,7 @@ class Suppressions {
1388
1379
  * });
1389
1380
  */
1390
1381
  async create(options) {
1391
- const result = { success: false, error: null };
1382
+ let error = null;
1392
1383
  const { addToSubAccounts, entries } = options;
1393
1384
  const payload = {
1394
1385
  add_to_sub_accounts: addToSubAccounts,
@@ -1401,20 +1392,17 @@ class Suppressions {
1401
1392
  };
1402
1393
  await this.mailchannels.post("/tx/v1/suppression-list", {
1403
1394
  body: payload,
1404
- ignoreResponseError: true,
1405
- onResponse: async ({ response }) => {
1406
- if (response.ok) {
1407
- result.success = true;
1408
- return;
1409
- }
1410
- result.error = getStatusError(response, {
1395
+ onResponseError: async ({ response }) => {
1396
+ error = getStatusError(response, {
1411
1397
  [ErrorCode.BadRequest]: "Bad Request.",
1412
1398
  [ErrorCode.Conflict]: "Conflict. One or more suppression entries in the request already exist and cannot be created again.",
1413
1399
  [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
1400
  });
1415
1401
  }
1402
+ }).catch((e) => {
1403
+ error ||= getResultError(e, "Failed to create suppression entries.");
1416
1404
  });
1417
- return result;
1405
+ return { success: !error, error };
1418
1406
  }
1419
1407
  /**
1420
1408
  * Deletes suppression entry associated with the account based on the specified recipient and source.
@@ -1427,23 +1415,20 @@ class Suppressions {
1427
1415
  * ```
1428
1416
  */
1429
1417
  async delete(recipient, source) {
1430
- const result = { success: false, error: null };
1418
+ let error = null;
1431
1419
  await this.mailchannels.delete(`/tx/v1/suppression-list/recipients/${recipient}`, {
1432
1420
  query: {
1433
1421
  source
1434
1422
  },
1435
- ignoreResponseError: true,
1436
- onResponse: async ({ response }) => {
1437
- if (response.ok) {
1438
- result.success = true;
1439
- return;
1440
- }
1441
- result.error = getStatusError(response, {
1423
+ onResponseError: async ({ response }) => {
1424
+ error = getStatusError(response, {
1442
1425
  [ErrorCode.BadRequest]: "Bad Request."
1443
1426
  });
1444
1427
  }
1428
+ }).catch((e) => {
1429
+ error ||= getResultError(e, "Failed to delete suppression entry.");
1445
1430
  });
1446
- return result;
1431
+ return { success: !error, error };
1447
1432
  }
1448
1433
  /**
1449
1434
  * Retrieve suppression entries associated with the specified account. Supports filtering by recipient, source and creation date range. The response is paginated, with a default limit of `1000` entries per page and an offset of `0`.
@@ -1455,15 +1440,9 @@ class Suppressions {
1455
1440
  * @param options - Options to filter and customize the suppression entries retrieval.
1456
1441
  */
1457
1442
  async list(options) {
1458
- 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
- }
1443
+ let error = null;
1444
+ error = validateLimit(options?.limit, 1e3) || validateOffset(options?.offset);
1445
+ if (error) return { data: null, error };
1467
1446
  const payload = {
1468
1447
  recipient: options?.recipient,
1469
1448
  source: options?.source,
@@ -1475,13 +1454,16 @@ class Suppressions {
1475
1454
  const response = await this.mailchannels.get("/tx/v1/suppression-list", {
1476
1455
  query: payload,
1477
1456
  onResponseError: async ({ response: response2 }) => {
1478
- result.error = getStatusError(response2, {
1457
+ error = getStatusError(response2, {
1479
1458
  [ErrorCode.BadRequest]: "Bad Request."
1480
1459
  });
1481
1460
  }
1482
- }).catch(() => null);
1483
- if (!response) return result;
1484
- result.data = clean(response.suppression_list.map((entry) => ({
1461
+ }).catch((e) => {
1462
+ error ||= getResultError(e, "Failed to fetch suppression entries.");
1463
+ return null;
1464
+ });
1465
+ if (!response) return { data: null, error };
1466
+ const data = clean(response.suppression_list.map((entry) => ({
1485
1467
  createdAt: entry.created_at,
1486
1468
  notes: entry.notes,
1487
1469
  recipient: entry.recipient,
@@ -1489,7 +1471,7 @@ class Suppressions {
1489
1471
  source: entry.source,
1490
1472
  types: entry.suppression_types
1491
1473
  })));
1492
- return result;
1474
+ return { data, error: null };
1493
1475
  }
1494
1476
  }
1495
1477
 
@@ -1510,8 +1492,8 @@ class Domains {
1510
1492
  * ```
1511
1493
  */
1512
1494
  async provision(options) {
1495
+ let error = null;
1513
1496
  const { associateKey, overwrite, ...payload } = options;
1514
- const result = { data: null, error: null };
1515
1497
  const response = await this.mailchannels.post("/inbound/v1/domains", {
1516
1498
  query: {
1517
1499
  "associate-key": associateKey,
@@ -1519,20 +1501,19 @@ class Domains {
1519
1501
  },
1520
1502
  body: payload,
1521
1503
  onResponseError: async ({ response: response2 }) => {
1522
- result.error = getStatusError(response2, {
1504
+ error = getStatusError(response2, {
1523
1505
  [ErrorCode.BadRequest]: "Bad Request, returned in the case that an error occurs while converting an A-label domain to a U-label domain name.",
1524
1506
  [ErrorCode.Forbidden]: "The limit on associated domains is reached or you are attempting to associate a domain with a subscription that is not your own.",
1525
1507
  [ErrorCode.Conflict]: `The domain '${options.domain}' is already provisioned, and is associated with a different customer.`
1526
1508
  });
1527
1509
  }
1528
- }).catch((error) => {
1529
- if (!result.error) {
1530
- result.error = error instanceof Error ? error.message : "Failed to provision domain.";
1531
- }
1510
+ }).catch((e) => {
1511
+ error ||= getResultError(e, "Failed to provision domain.");
1532
1512
  return null;
1533
1513
  });
1534
- result.data = clean(response);
1535
- return result;
1514
+ if (!response) return { data: null, error };
1515
+ const data = clean(response);
1516
+ return { data, error: null };
1536
1517
  }
1537
1518
  /**
1538
1519
  * Provision up to 1000 domains to use MailChannels Inbound.
@@ -1555,15 +1536,15 @@ class Domains {
1555
1536
  * ```
1556
1537
  */
1557
1538
  async bulkProvision(options, domains) {
1539
+ let error = null;
1558
1540
  const { associateKey, overwrite, subscriptionHandle } = options;
1559
- const result = { data: null, error: null };
1560
1541
  if (!domains || !domains.length) {
1561
- result.error = "No domains provided.";
1562
- return result;
1542
+ error = createError("No domains provided.");
1543
+ return { data: null, error };
1563
1544
  }
1564
1545
  if (domains.length > 1e3) {
1565
- result.error = "The maximum number of domains to be provisioned is 1000.";
1566
- return result;
1546
+ error = createError("The maximum number of domains to be provisioned is 1000.");
1547
+ return { data: null, error };
1567
1548
  }
1568
1549
  const response = await this.mailchannels.post("/inbound/v1/domains/batch", {
1569
1550
  query: {
@@ -1573,20 +1554,18 @@ class Domains {
1573
1554
  },
1574
1555
  body: { domains },
1575
1556
  onResponseError: async ({ response: response2 }) => {
1576
- result.error = getStatusError(response2, {
1557
+ error = getStatusError(response2, {
1577
1558
  [ErrorCode.BadRequest]: "Bad Request, returned in the case that a domain name fails RFC 5891 validation.",
1578
1559
  [ErrorCode.Forbidden]: "The limit on associated domains is reached or you are attempting to associate a domain with a subscription that is not your own."
1579
1560
  });
1580
1561
  }
1581
- }).catch((error) => {
1582
- if (!result.error) {
1583
- result.error = error instanceof Error ? error.message : "Failed to provision domains.";
1584
- }
1562
+ }).catch((e) => {
1563
+ error ||= getResultError(e, "Failed to provision domains.");
1585
1564
  return null;
1586
1565
  });
1587
- if (!response) return result;
1588
- result.data = clean(response);
1589
- return result;
1566
+ if (!response) return { data: null, error };
1567
+ const data = clean(response);
1568
+ return { data, error: null };
1590
1569
  }
1591
1570
  /**
1592
1571
  * Fetch a list of all domains associated with this API key.
@@ -1598,27 +1577,24 @@ class Domains {
1598
1577
  * ```
1599
1578
  */
1600
1579
  async list(options) {
1601
- 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
- }
1580
+ let error = null;
1581
+ error = validateLimit(options?.limit, 5e3) || validateOffset(options?.offset);
1582
+ if (error) return { data: null, error };
1610
1583
  const response = await this.mailchannels.get("/inbound/v1/domains", {
1611
1584
  query: options,
1612
1585
  onResponseError: async ({ response: response2 }) => {
1613
- result.error = getStatusError(response2);
1586
+ error = getStatusError(response2);
1614
1587
  }
1615
- }).catch(() => null);
1616
- if (!response) return result;
1617
- result.data = clean({
1588
+ }).catch((e) => {
1589
+ error ||= getResultError(e, "Failed to fetch domains.");
1590
+ return null;
1591
+ });
1592
+ if (!response) return { data: null, error };
1593
+ const data = clean({
1618
1594
  domains: response.domains,
1619
1595
  total: response.total
1620
1596
  });
1621
- return result;
1597
+ return { data, error: null };
1622
1598
  }
1623
1599
  /**
1624
1600
  * De-provision a domain to cease protecting it with MailChannels Inbound.
@@ -1630,25 +1606,22 @@ class Domains {
1630
1606
  * ```
1631
1607
  */
1632
1608
  async delete(domain) {
1633
- const result = { success: false, error: null };
1609
+ let error = null;
1634
1610
  if (!domain) {
1635
- result.error = "No domain provided.";
1636
- return result;
1611
+ error = createError("No domain provided.");
1612
+ return { success: false, error };
1637
1613
  }
1638
1614
  await this.mailchannels.delete(`/inbound/v1/domains/${domain}`, {
1639
- ignoreResponseError: true,
1640
- onResponse: async ({ response }) => {
1641
- if (response.ok) {
1642
- result.success = true;
1643
- return;
1644
- }
1645
- result.error = getStatusError(response, {
1615
+ onResponseError: async ({ response }) => {
1616
+ error = getStatusError(response, {
1646
1617
  [ErrorCode.Forbidden]: "The domain is associated with an api key that is different than the one in the request, or the domain in the request is an alias domain.",
1647
1618
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1648
1619
  });
1649
1620
  }
1621
+ }).catch((e) => {
1622
+ error ||= getResultError(e, "Failed to delete domain.");
1650
1623
  });
1651
- return result;
1624
+ return { success: !error, error };
1652
1625
  }
1653
1626
  /**
1654
1627
  * Add an entry to a domain blocklist or safelist.
@@ -1665,31 +1638,34 @@ class Domains {
1665
1638
  */
1666
1639
  async addListEntry(domain, options) {
1667
1640
  const { listName, item } = options;
1668
- const result = { data: null, error: null };
1641
+ let error = null;
1669
1642
  if (!domain) {
1670
- result.error = "No domain provided.";
1671
- return result;
1643
+ error = createError("No domain provided.");
1644
+ return { data: null, error };
1672
1645
  }
1673
1646
  if (!listName) {
1674
- result.error = "No list name provided.";
1675
- return result;
1647
+ error = createError("No list name provided.");
1648
+ return { data: null, error };
1676
1649
  }
1677
1650
  const response = await this.mailchannels.post(`/inbound/v1/domains/${domain}/lists/${listName}`, {
1678
1651
  body: { item },
1679
1652
  onResponseError: async ({ response: response2 }) => {
1680
- result.error = getStatusError(response2, {
1653
+ error = getStatusError(response2, {
1681
1654
  [ErrorCode.Forbidden]: "The domain is associated with an api key that is different than the one in the request, the domain is associated with a different customer, or the domain in the request is an alias domain.",
1682
1655
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1683
1656
  });
1684
1657
  }
1685
- }).catch(() => null);
1686
- if (!response) return result;
1687
- result.data = clean({
1658
+ }).catch((e) => {
1659
+ error ||= getResultError(e, "Failed to add domain list entry.");
1660
+ return null;
1661
+ });
1662
+ if (!response) return { data: null, error };
1663
+ const data = clean({
1688
1664
  action: response.action,
1689
1665
  item: response.item,
1690
1666
  type: response.item_type
1691
1667
  });
1692
- return result;
1668
+ return { data, error: null };
1693
1669
  }
1694
1670
  /**
1695
1671
  * Get domain list entries.
@@ -1702,30 +1678,33 @@ class Domains {
1702
1678
  * ```
1703
1679
  */
1704
1680
  async listEntries(domain, listName) {
1705
- const result = { data: null, error: null };
1681
+ let error = null;
1706
1682
  if (!domain) {
1707
- result.error = "No domain provided.";
1708
- return result;
1683
+ error = createError("No domain provided.");
1684
+ return { data: null, error };
1709
1685
  }
1710
1686
  if (!listName) {
1711
- result.error = "No list name provided.";
1712
- return result;
1687
+ error = createError("No list name provided.");
1688
+ return { data: null, error };
1713
1689
  }
1714
1690
  const response = await this.mailchannels.get(`/inbound/v1/domains/${domain}/lists/${listName}`, {
1715
1691
  onResponseError: async ({ response: response2 }) => {
1716
- result.error = getStatusError(response2, {
1692
+ error = getStatusError(response2, {
1717
1693
  [ErrorCode.Forbidden]: "The domain is associated with an api key that is different than the one in the request, the domain is associated with a different customer, or the domain in the request is an alias domain.",
1718
1694
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1719
1695
  });
1720
1696
  }
1721
- }).catch(() => null);
1722
- if (!response) return result;
1723
- result.data = clean(response.map(({ action, item, item_type }) => ({
1697
+ }).catch((e) => {
1698
+ error ||= getResultError(e, "Failed to fetch domain list entries.");
1699
+ return null;
1700
+ });
1701
+ if (!response) return { data: null, error };
1702
+ const data = clean(response.map(({ action, item, item_type }) => ({
1724
1703
  action,
1725
1704
  item,
1726
1705
  type: item_type
1727
1706
  })));
1728
- return result;
1707
+ return { data, error: null };
1729
1708
  }
1730
1709
  /**
1731
1710
  * Delete item from domain list.
@@ -1742,30 +1721,27 @@ class Domains {
1742
1721
  */
1743
1722
  async deleteListEntry(domain, options) {
1744
1723
  const { listName, item } = options;
1745
- const result = { success: false, error: null };
1724
+ let error = null;
1746
1725
  if (!domain) {
1747
- result.error = "No domain provided.";
1748
- return result;
1726
+ error = createError("No domain provided.");
1727
+ return { success: false, error };
1749
1728
  }
1750
1729
  if (!listName) {
1751
- result.error = "No list name provided.";
1752
- return result;
1730
+ error = createError("No list name provided.");
1731
+ return { success: false, error };
1753
1732
  }
1754
1733
  await this.mailchannels.delete(`/inbound/v1/domains/${domain}/lists/${listName}`, {
1755
1734
  query: { item },
1756
- ignoreResponseError: true,
1757
- onResponse: async ({ response }) => {
1758
- if (response.ok) {
1759
- result.success = true;
1760
- return;
1761
- }
1762
- result.error = getStatusError(response, {
1735
+ onResponseError: async ({ response }) => {
1736
+ error = getStatusError(response, {
1763
1737
  [ErrorCode.Forbidden]: "The domain is associated with an api key that is different than the one in the request, the domain is associated with a different customer, or the domain in the request is an alias domain.",
1764
1738
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1765
1739
  });
1766
1740
  }
1741
+ }).catch((e) => {
1742
+ error ||= getResultError(e, "Failed to delete domain list entry.");
1767
1743
  });
1768
- return result;
1744
+ return { success: !error, error };
1769
1745
  }
1770
1746
  /**
1771
1747
  * Generate a link that allows a user to log in as a domain administrator.
@@ -1777,25 +1753,28 @@ class Domains {
1777
1753
  * ```
1778
1754
  */
1779
1755
  async createLoginLink(domain) {
1780
- const result = { data: null, error: null };
1756
+ let error = null;
1781
1757
  if (!domain) {
1782
- result.error = "No domain provided.";
1783
- return result;
1758
+ error = createError("No domain provided.");
1759
+ return { data: null, error };
1784
1760
  }
1785
1761
  const response = await this.mailchannels.get(`/inbound/v1/domains/${domain}/login-link`, {
1786
1762
  onResponseError: async ({ response: response2 }) => {
1787
- result.error = getStatusError(response2, {
1763
+ error = getStatusError(response2, {
1788
1764
  [ErrorCode.Unauthorized]: "The domain does not belong to this customer.",
1789
1765
  [ErrorCode.Forbidden]: "The domain is associated with an api key that is different than the one in the request, the domain is associated with a different customer, or the domain in the request is an alias domain.",
1790
1766
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1791
1767
  });
1792
1768
  }
1793
- }).catch(() => null);
1794
- if (!response) return result;
1795
- result.data = clean({
1769
+ }).catch((e) => {
1770
+ error ||= getResultError(e, "Failed to create login link.");
1771
+ return null;
1772
+ });
1773
+ if (!response) return { data: null, error };
1774
+ const data = clean({
1796
1775
  link: response.loginLink
1797
1776
  });
1798
- return result;
1777
+ return { data, error: null };
1799
1778
  }
1800
1779
  /**
1801
1780
  * Sets the list of downstream addresses for the domain. This action deletes any existing downstream address for the domain before creating new ones. If the `records` parameter is an empty array, all downstream address records will be deleted.
@@ -1815,30 +1794,27 @@ class Domains {
1815
1794
  * ```
1816
1795
  */
1817
1796
  async setDownstreamAddress(domain, records = []) {
1818
- const result = { success: false, error: null };
1797
+ let error = null;
1819
1798
  if (!domain) {
1820
- result.error = "No domain provided.";
1821
- return result;
1799
+ error = createError("No domain provided.");
1800
+ return { success: false, error };
1822
1801
  }
1823
1802
  if (records.length > 10) {
1824
- result.error = "The maximum of records to be set is 10.";
1825
- return result;
1803
+ error = createError("The maximum of records to be set is 10.");
1804
+ return { success: false, error };
1826
1805
  }
1827
1806
  await this.mailchannels.put(`/inbound/v1/domains/${domain}/downstream-address`, {
1828
1807
  body: { records },
1829
- ignoreResponseError: true,
1830
- onResponse: async ({ response }) => {
1831
- if (response.ok) {
1832
- result.success = true;
1833
- return;
1834
- }
1835
- result.error = getStatusError(response, {
1808
+ onResponseError: async ({ response }) => {
1809
+ error = getStatusError(response, {
1836
1810
  [ErrorCode.Forbidden]: "The domain is associated with an api key that is different than the one in the request, the domain is associated with a different customer, or the domain in the request is an alias domain.",
1837
1811
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1838
1812
  });
1839
1813
  }
1814
+ }).catch((e) => {
1815
+ error ||= getResultError(e, "Failed to set downstream address.");
1840
1816
  });
1841
- return result;
1817
+ return { success: !error, error };
1842
1818
  }
1843
1819
  /**
1844
1820
  * Retrieve stored downstream addresses for the domain.
@@ -1851,31 +1827,28 @@ class Domains {
1851
1827
  * ```
1852
1828
  */
1853
1829
  async listDownstreamAddresses(domain, options) {
1854
- const result = { data: null, error: null };
1830
+ let error = null;
1855
1831
  if (!domain) {
1856
- result.error = "No domain provided.";
1857
- return result;
1858
- }
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;
1832
+ error = createError("No domain provided.");
1833
+ return { data: null, error };
1866
1834
  }
1835
+ error = validateLimit(options?.limit) || validateOffset(options?.offset);
1836
+ if (error) return { data: null, error };
1867
1837
  const response = await this.mailchannels.get(`/inbound/v1/domains/${domain}/downstream-address`, {
1868
1838
  query: options,
1869
1839
  onResponseError: async ({ response: response2 }) => {
1870
- result.error = getStatusError(response2, {
1840
+ error = getStatusError(response2, {
1871
1841
  [ErrorCode.Forbidden]: "The domain is associated with an api key that is different than the one in the request, the domain is associated with a different customer, or the domain in the request is an alias domain.",
1872
1842
  [ErrorCode.NotFound]: `The domain '${domain}' was not found.`
1873
1843
  });
1874
1844
  }
1875
- }).catch(() => null);
1876
- if (!response) return result;
1877
- result.data = clean(response.records);
1878
- return result;
1845
+ }).catch((e) => {
1846
+ error ||= getResultError(e, "Failed to list downstream addresses.");
1847
+ return null;
1848
+ });
1849
+ if (!response) return { data: null, error };
1850
+ const data = clean(response.records);
1851
+ return { data, error: null };
1879
1852
  }
1880
1853
  /**
1881
1854
  * Update the API key that is associated with a domain.
@@ -1888,30 +1861,27 @@ class Domains {
1888
1861
  * ```
1889
1862
  */
1890
1863
  async updateApiKey(domain, key) {
1891
- const result = { success: false, error: null };
1864
+ let error = null;
1892
1865
  if (!domain) {
1893
- result.error = "No domain provided.";
1894
- return result;
1866
+ error = createError("No domain provided.");
1867
+ return { success: false, error };
1895
1868
  }
1896
1869
  if (!key) {
1897
- result.error = "No API key provided.";
1898
- return result;
1870
+ error = createError("No API key provided.");
1871
+ return { success: false, error };
1899
1872
  }
1900
1873
  await this.mailchannels.put(`/inbound/v1/domains/${domain}/api-key`, {
1901
1874
  body: { apiKey: key },
1902
- ignoreResponseError: true,
1903
- onResponse: async ({ response }) => {
1904
- if (response.ok) {
1905
- result.success = true;
1906
- return;
1907
- }
1908
- result.error = getStatusError(response, {
1875
+ onResponseError: async ({ response }) => {
1876
+ error = getStatusError(response, {
1909
1877
  [ErrorCode.Forbidden]: "The domain is associated with an api key that is different than the one in the request, the domain is associated with a different customer, or the domain in the request is an alias domain.",
1910
1878
  [ErrorCode.NotFound]: "The domain does not exist."
1911
1879
  });
1912
1880
  }
1881
+ }).catch((e) => {
1882
+ error ||= getResultError(e, "Failed to update domain API key.");
1913
1883
  });
1914
- return result;
1884
+ return { success: !error, error };
1915
1885
  }
1916
1886
  /**
1917
1887
  * Generate a batch of links that allow a user to log in as a domain administrator to their different domains.
@@ -1923,28 +1893,31 @@ class Domains {
1923
1893
  * ```
1924
1894
  */
1925
1895
  async bulkCreateLoginLinks(domains) {
1926
- const result = { data: null, error: null };
1896
+ let error = null;
1927
1897
  if (!domains || !domains.length) {
1928
- result.error = "No domains provided.";
1929
- return result;
1898
+ error = createError("No domains provided.");
1899
+ return { data: null, error };
1930
1900
  }
1931
1901
  if (domains.length > 1e3) {
1932
- result.error = "The maximum number of domains to create login links for is 1000.";
1933
- return result;
1902
+ error = createError("The maximum number of domains to create login links for is 1000.");
1903
+ return { data: null, error };
1934
1904
  }
1935
1905
  const response = await this.mailchannels.post("/inbound/v1/domains/batch/login-link", {
1936
1906
  body: {
1937
1907
  domains: domains.map((domain) => ({ domain }))
1938
1908
  },
1939
1909
  onResponseError: async ({ response: response2 }) => {
1940
- result.error = getStatusError(response2, {
1910
+ error = getStatusError(response2, {
1941
1911
  [ErrorCode.BadRequest]: "Bad Request."
1942
1912
  });
1943
1913
  }
1944
- }).catch(() => null);
1945
- if (!response) return result;
1946
- result.data = clean(response);
1947
- return result;
1914
+ }).catch((e) => {
1915
+ error ||= getResultError(e, "Failed to create login links.");
1916
+ return null;
1917
+ });
1918
+ if (!response) return { data: null, error };
1919
+ const data = clean(response);
1920
+ return { data, error: null };
1948
1921
  }
1949
1922
  }
1950
1923
 
@@ -1965,25 +1938,28 @@ class Lists {
1965
1938
  * ```
1966
1939
  */
1967
1940
  async addListEntry(options) {
1941
+ let error = null;
1968
1942
  const { listName, item } = options;
1969
- const result = { data: null, error: null };
1970
1943
  if (!listName) {
1971
- result.error = "No list name provided.";
1972
- return result;
1944
+ error = createError("No list name provided.");
1945
+ return { data: null, error };
1973
1946
  }
1974
1947
  const response = await this.mailchannels.post(`/inbound/v1/lists/${listName}`, {
1975
1948
  body: { item },
1976
1949
  onResponseError: async ({ response: response2 }) => {
1977
- result.error = getStatusError(response2);
1950
+ error = getStatusError(response2);
1978
1951
  }
1979
- }).catch(() => null);
1980
- if (!response) return result;
1981
- result.data = clean({
1952
+ }).catch((e) => {
1953
+ error ||= getResultError(e, "Failed to add list entry.");
1954
+ return null;
1955
+ });
1956
+ if (!response) return { data: null, error };
1957
+ const data = clean({
1982
1958
  action: response.action,
1983
1959
  item: response.item,
1984
1960
  type: response.item_type
1985
1961
  });
1986
- return result;
1962
+ return { data, error: null };
1987
1963
  }
1988
1964
  /**
1989
1965
  * Get account-level list entries.
@@ -1995,23 +1971,26 @@ class Lists {
1995
1971
  * ```
1996
1972
  */
1997
1973
  async listEntries(listName) {
1998
- const result = { data: null, error: null };
1974
+ let error = null;
1999
1975
  if (!listName) {
2000
- result.error = "No list name provided.";
2001
- return result;
1976
+ error = createError("No list name provided.");
1977
+ return { data: null, error };
2002
1978
  }
2003
1979
  const response = await this.mailchannels.get(`/inbound/v1/lists/${listName}`, {
2004
1980
  onResponseError: async ({ response: response2 }) => {
2005
- result.error = getStatusError(response2);
1981
+ error = getStatusError(response2);
2006
1982
  }
2007
- }).catch(() => null);
2008
- if (!response) return result;
2009
- result.data = clean(response.map(({ action, item, item_type }) => ({
1983
+ }).catch((e) => {
1984
+ error ||= getResultError(e, "Failed to fetch list entries.");
1985
+ return null;
1986
+ });
1987
+ if (!response) return { data: null, error };
1988
+ const data = clean(response.map(({ action, item, item_type }) => ({
2010
1989
  action,
2011
1990
  item,
2012
1991
  type: item_type
2013
1992
  })));
2014
- return result;
1993
+ return { data, error: null };
2015
1994
  }
2016
1995
  /**
2017
1996
  * Delete item from account-level list.
@@ -2027,23 +2006,20 @@ class Lists {
2027
2006
  */
2028
2007
  async deleteListEntry(options) {
2029
2008
  const { listName, item } = options;
2030
- const result = { success: false, error: null };
2009
+ let error = null;
2031
2010
  if (!listName) {
2032
- result.error = "No list name provided.";
2033
- return result;
2011
+ error = createError("No list name provided.");
2012
+ return { success: false, error };
2034
2013
  }
2035
2014
  await this.mailchannels.delete(`/inbound/v1/lists/${listName}`, {
2036
2015
  query: { item },
2037
- ignoreResponseError: true,
2038
- onResponse: async ({ response }) => {
2039
- if (response.ok) {
2040
- result.success = true;
2041
- return;
2042
- }
2043
- result.error = getStatusError(response);
2016
+ onResponseError: async ({ response }) => {
2017
+ error = getStatusError(response);
2044
2018
  }
2019
+ }).catch((e) => {
2020
+ error ||= getResultError(e, "Failed to delete list entry.");
2045
2021
  });
2046
- return result;
2022
+ return { success: !error, error };
2047
2023
  }
2048
2024
  }
2049
2025
 
@@ -2065,10 +2041,10 @@ class Users {
2065
2041
  */
2066
2042
  async create(email, options) {
2067
2043
  const { admin, filter, listEntries } = options || {};
2068
- const result = { data: null, error: null };
2044
+ let error = null;
2069
2045
  if (!email) {
2070
- result.error = "No email address provided.";
2071
- return result;
2046
+ error = createError("No email address provided.");
2047
+ return { data: null, error };
2072
2048
  }
2073
2049
  const response = await this.mailchannels.put("/inbound/v1/users", {
2074
2050
  query: {
@@ -2080,13 +2056,16 @@ class Users {
2080
2056
  list_entries: listEntries
2081
2057
  },
2082
2058
  onResponseError: async ({ response: response2 }) => {
2083
- result.error = getStatusError(response2, {
2059
+ error = getStatusError(response2, {
2084
2060
  [ErrorCode.BadRequest]: `The email address '${email}' is invalid.`
2085
2061
  });
2086
2062
  }
2087
- }).catch(() => null);
2088
- if (!response) return result;
2089
- result.data = clean({
2063
+ }).catch((e) => {
2064
+ error ||= getResultError(e, "Failed to create user.");
2065
+ return null;
2066
+ });
2067
+ if (!response) return { data: null, error };
2068
+ const data = clean({
2090
2069
  email: response.recipient.email_address,
2091
2070
  roles: response.recipient.roles,
2092
2071
  filter: response.recipient.filter,
@@ -2096,7 +2075,7 @@ class Users {
2096
2075
  action
2097
2076
  }))
2098
2077
  });
2099
- return result;
2078
+ return { data, error: null };
2100
2079
  }
2101
2080
  /**
2102
2081
  * Add item to recipient user list
@@ -2113,31 +2092,34 @@ class Users {
2113
2092
  */
2114
2093
  async addListEntry(email, options) {
2115
2094
  const { listName, item } = options;
2116
- const result = { data: null, error: null };
2095
+ let error = null;
2117
2096
  if (!email) {
2118
- result.error = "No email provided.";
2119
- return result;
2097
+ error = createError("No email provided.");
2098
+ return { data: null, error };
2120
2099
  }
2121
2100
  if (!listName) {
2122
- result.error = "No list name provided.";
2123
- return result;
2101
+ error = createError("No list name provided.");
2102
+ return { data: null, error };
2124
2103
  }
2125
2104
  const response = await this.mailchannels.post(`/inbound/v1/users/${email}/lists/${listName}`, {
2126
2105
  body: { item },
2127
2106
  onResponseError: async ({ response: response2 }) => {
2128
- result.error = getStatusError(response2, {
2107
+ error = getStatusError(response2, {
2129
2108
  [ErrorCode.Forbidden]: "The domain is associated with an api key that is different than the one in the request, the domain is associated with a different customer, or the domain in the request is an alias domain.",
2130
2109
  [ErrorCode.NotFound]: `The recipient '${email}' was not found.`
2131
2110
  });
2132
2111
  }
2133
- }).catch(() => null);
2134
- if (!response) return result;
2135
- result.data = clean({
2112
+ }).catch((e) => {
2113
+ error ||= getResultError(e, "Failed to add user list entry.");
2114
+ return null;
2115
+ });
2116
+ if (!response) return { data: null, error };
2117
+ const data = clean({
2136
2118
  action: response.action,
2137
2119
  item: response.item,
2138
2120
  type: response.item_type
2139
2121
  });
2140
- return result;
2122
+ return { data, error: null };
2141
2123
  }
2142
2124
  /**
2143
2125
  * Get recipient list entries.
@@ -2150,30 +2132,33 @@ class Users {
2150
2132
  * ```
2151
2133
  */
2152
2134
  async listEntries(email, listName) {
2153
- const result = { data: null, error: null };
2135
+ let error = null;
2154
2136
  if (!email) {
2155
- result.error = "No email provided.";
2156
- return result;
2137
+ error = createError("No email provided.");
2138
+ return { data: null, error };
2157
2139
  }
2158
2140
  if (!listName) {
2159
- result.error = "No list name provided.";
2160
- return result;
2141
+ error = createError("No list name provided.");
2142
+ return { data: null, error };
2161
2143
  }
2162
2144
  const response = await this.mailchannels.get(`/inbound/v1/users/${email}/lists/${listName}`, {
2163
2145
  onResponseError: async ({ response: response2 }) => {
2164
- result.error = getStatusError(response2, {
2146
+ error = getStatusError(response2, {
2165
2147
  [ErrorCode.Forbidden]: "The domain is associated with an api key that is different than the one in the request, the domain is associated with a different customer, or the domain in the request is an alias domain.",
2166
2148
  [ErrorCode.NotFound]: `The recipient '${email}' was not found.`
2167
2149
  });
2168
2150
  }
2169
- }).catch(() => null);
2170
- if (!response) return result;
2171
- result.data = clean(response.map(({ action, item, item_type }) => ({
2151
+ }).catch((e) => {
2152
+ error ||= getResultError(e, "Failed to fetch user list entries.");
2153
+ return null;
2154
+ });
2155
+ if (!response) return { data: null, error };
2156
+ const data = clean(response.map(({ action, item, item_type }) => ({
2172
2157
  action,
2173
2158
  item,
2174
2159
  type: item_type
2175
2160
  })));
2176
- return result;
2161
+ return { data, error: null };
2177
2162
  }
2178
2163
  /**
2179
2164
  * Delete item from recipient list.
@@ -2190,30 +2175,27 @@ class Users {
2190
2175
  */
2191
2176
  async deleteListEntry(email, options) {
2192
2177
  const { listName, item } = options;
2193
- const result = { success: false, error: null };
2178
+ let error = null;
2194
2179
  if (!email) {
2195
- result.error = "No email provided.";
2196
- return result;
2180
+ error = createError("No email provided.");
2181
+ return { success: false, error };
2197
2182
  }
2198
2183
  if (!listName) {
2199
- result.error = "No list name provided.";
2200
- return result;
2184
+ error = createError("No list name provided.");
2185
+ return { success: false, error };
2201
2186
  }
2202
2187
  await this.mailchannels.delete(`/inbound/v1/users/${email}/lists/${listName}`, {
2203
2188
  query: { item },
2204
- ignoreResponseError: true,
2205
- onResponse: async ({ response }) => {
2206
- if (response.ok) {
2207
- result.success = true;
2208
- return;
2209
- }
2210
- result.error = getStatusError(response, {
2189
+ onResponseError: async ({ response }) => {
2190
+ error = getStatusError(response, {
2211
2191
  [ErrorCode.Forbidden]: "The domain is associated with an api key that is different than the one in the request, the domain is associated with a different customer, or the domain in the request is an alias domain.",
2212
2192
  [ErrorCode.NotFound]: `The recipient '${email}' was not found.`
2213
2193
  });
2214
2194
  }
2195
+ }).catch((e) => {
2196
+ error ||= getResultError(e, "Failed to delete user list entry.");
2215
2197
  });
2216
- return result;
2198
+ return { success: !error, error };
2217
2199
  }
2218
2200
  }
2219
2201
 
@@ -2230,18 +2212,15 @@ class Service {
2230
2212
  * ```
2231
2213
  */
2232
2214
  async status() {
2233
- const result = { success: false, error: null };
2215
+ let error = null;
2234
2216
  await this.mailchannels.get("/inbound/v1/status", {
2235
- ignoreResponseError: true,
2236
- onResponse: async ({ response }) => {
2237
- if (response.ok) {
2238
- result.success = true;
2239
- return;
2240
- }
2241
- result.error = getStatusError(response);
2217
+ onResponseError: async ({ response }) => {
2218
+ error = getStatusError(response);
2242
2219
  }
2220
+ }).catch((e) => {
2221
+ error ||= getResultError(e, "Failed to fetch service status.");
2243
2222
  });
2244
- return result;
2223
+ return { success: !error, error };
2245
2224
  }
2246
2225
  /**
2247
2226
  * Get a list of your subscriptions to MailChannels Inbound
@@ -2252,22 +2231,20 @@ class Service {
2252
2231
  * ```
2253
2232
  */
2254
2233
  async subscriptions() {
2255
- const result = { data: null, error: null };
2234
+ let error = null;
2256
2235
  const response = await this.mailchannels.get("/inbound/v1/subscriptions", {
2257
2236
  onResponseError: async ({ response: response2 }) => {
2258
- result.error = getStatusError(response2, {
2237
+ error = getStatusError(response2, {
2259
2238
  [ErrorCode.NotFound]: "We could not find a customer that matched the customerHandle."
2260
2239
  });
2261
2240
  }
2262
- }).catch((error) => {
2263
- if (!result.error) {
2264
- result.error = error instanceof Error ? error.message : "Failed to fetch subscriptions.";
2265
- }
2241
+ }).catch((e) => {
2242
+ error ||= getResultError(e, "Failed to fetch subscriptions.");
2266
2243
  return null;
2267
2244
  });
2268
- if (!response) return result;
2269
- result.data = clean(response);
2270
- return result;
2245
+ if (!response) return { data: null, error };
2246
+ const data = clean(response);
2247
+ return { data, error: null };
2271
2248
  }
2272
2249
  /**
2273
2250
  * Submit a false negative or false positive report.
@@ -2281,22 +2258,20 @@ class Service {
2281
2258
  * ```
2282
2259
  */
2283
2260
  async report(options) {
2284
- const result = { success: false, error: null };
2261
+ let error = null;
2285
2262
  const { type, ...payload } = options;
2286
2263
  await this.mailchannels.post("/inbound/v1/report", {
2287
2264
  query: {
2288
2265
  report_type: type
2289
2266
  },
2290
2267
  body: payload,
2291
- onResponse: async ({ response }) => {
2292
- if (response.ok) {
2293
- result.success = true;
2294
- return;
2295
- }
2296
- result.error = getStatusError(response);
2268
+ onResponseError: async ({ response }) => {
2269
+ error = getStatusError(response);
2297
2270
  }
2271
+ }).catch((e) => {
2272
+ error ||= getResultError(e, "Failed to submit report.");
2298
2273
  });
2299
- return result;
2274
+ return { success: !error, error };
2300
2275
  }
2301
2276
  }
2302
2277