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