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