mailchannels-sdk 0.4.7 → 0.6.0

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