mailchannels-sdk 0.5.0 → 0.6.1

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