mailchannels-sdk 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/.agents/skills/mailchannels-js/SKILL.md +4 -0
  2. package/.agents/skills/mailchannels-js/resources/attachments.md +7 -7
  3. package/.agents/skills/mailchannels-js/resources/clients-and-transport.md +5 -5
  4. package/.agents/skills/mailchannels-js/resources/custom-headers.md +4 -4
  5. package/.agents/skills/mailchannels-js/resources/dkim.md +10 -10
  6. package/.agents/skills/mailchannels-js/resources/domain-checks.md +6 -6
  7. package/.agents/skills/mailchannels-js/resources/error-handling.md +5 -5
  8. package/.agents/skills/mailchannels-js/resources/metrics-and-usage.md +9 -9
  9. package/.agents/skills/mailchannels-js/resources/overview.md +5 -5
  10. package/.agents/skills/mailchannels-js/resources/plugins/nodemailer.md +112 -0
  11. package/.agents/skills/mailchannels-js/resources/sending.md +10 -10
  12. package/.agents/skills/mailchannels-js/resources/sub-accounts.md +7 -7
  13. package/.agents/skills/mailchannels-js/resources/suppressions.md +4 -4
  14. package/.agents/skills/mailchannels-js/resources/templates.md +5 -5
  15. package/.agents/skills/mailchannels-js/resources/testing.md +52 -5
  16. package/.agents/skills/mailchannels-js/resources/unsubscribe.md +3 -3
  17. package/.agents/skills/mailchannels-js/resources/webhooks.md +8 -8
  18. package/README.md +71 -0
  19. package/dist/_chunks/mailchannels.d.mts +2094 -0
  20. package/dist/_chunks/mailchannels.mjs +2201 -0
  21. package/dist/_chunks/{simulate.mjs → simulator.mjs} +7 -39
  22. package/dist/cli/index.mjs +266 -13
  23. package/dist/mailchannels.d.mts +1 -2093
  24. package/dist/mailchannels.mjs +1 -2199
  25. package/dist/plugins/nodemailer/index.d.mts +33 -0
  26. package/dist/plugins/nodemailer/index.mjs +145 -0
  27. package/dist/simulator/index.d.mts +20 -0
  28. package/dist/simulator/index.mjs +2 -0
  29. package/package.json +18 -6
  30. package/dist/cli/index.d.mts +0 -1
@@ -1,2200 +1,2 @@
1
- import { $fetch } from "ofetch";
2
- import { subtle } from "node:crypto";
3
- import { Buffer } from "node:buffer";
4
- import mime from "mime";
5
- var version = "1.3.1";
6
- var MailChannelsClient = class MailChannelsClient {
7
- static DEFAULT_BASE_URL = "https://api.mailchannels.net";
8
- static DEFAULT_TIMEOUT = 12e4;
9
- options;
10
- #headers;
11
- constructor(key, options = {}) {
12
- if (!key) throw new Error("Missing MailChannels API key.");
13
- this.options = {
14
- baseUrl: options.baseUrl || MailChannelsClient.DEFAULT_BASE_URL,
15
- retry: options.retry ?? false,
16
- signal: options.signal,
17
- timeout: options.timeout ?? MailChannelsClient.DEFAULT_TIMEOUT
18
- };
19
- this.#headers = {
20
- "X-API-Key": key,
21
- "Accept": "application/json",
22
- "Content-Type": "application/json",
23
- "User-Agent": `mailchannels-node/${version}`
24
- };
25
- }
26
- async _fetch(path, options) {
27
- const { headers, signal = this.options.signal, ...fetchOptions } = options;
28
- return $fetch(path, {
29
- baseURL: this.options.baseUrl,
30
- retry: this.options.retry,
31
- signal,
32
- timeout: this.options.timeout === false ? void 0 : this.options.timeout,
33
- ...fetchOptions,
34
- headers: {
35
- ...this.#headers,
36
- ...headers
37
- }
38
- });
39
- }
40
- async post(path, options) {
41
- return this._fetch(path, {
42
- method: "POST",
43
- ...options
44
- });
45
- }
46
- async get(path, options) {
47
- return this._fetch(path, {
48
- method: "GET",
49
- ...options
50
- });
51
- }
52
- async delete(path, options) {
53
- return this._fetch(path, {
54
- method: "DELETE",
55
- ...options
56
- });
57
- }
58
- async put(path, options) {
59
- return this._fetch(path, {
60
- method: "PUT",
61
- ...options
62
- });
63
- }
64
- async patch(path, options) {
65
- return this._fetch(path, {
66
- method: "PATCH",
67
- ...options
68
- });
69
- }
70
- };
71
- const STATUS_ERROR_TYPE_MAP = {
72
- [400]: "invalid_request_error",
73
- [401]: "authentication_error",
74
- [403]: "permission_error",
75
- [404]: "not_found",
76
- [409]: "conflict_error",
77
- [413]: "payload_too_large_error",
78
- [422]: "unprocessable_entity_error",
79
- [429]: "rate_limit_error",
80
- [500]: "internal_server_error"
81
- };
82
- const createError = (message, statusCode = null, type, response = null) => {
83
- return {
84
- message,
85
- statusCode,
86
- type,
87
- response
88
- };
89
- };
90
- const getStatusError = (response, errors = {}) => {
91
- const statusText = errors[response.status] || "Unknown error.";
92
- const payload = response._data ?? response.data;
93
- let details;
94
- let errorResponse = null;
95
- if (typeof payload === "string") details = payload;
96
- else if (typeof payload === "object" && payload !== null) {
97
- if (typeof payload.message === "string") details = payload.message;
98
- else if (Array.isArray(payload.errors) && payload.errors.length) details = payload.errors.join(", ");
99
- errorResponse = payload;
100
- }
101
- return createError(details ? `${statusText} ${details}` : statusText, response.status ?? null, STATUS_ERROR_TYPE_MAP[response.status] || "api_error", errorResponse);
102
- };
103
- const getResultError = (e, fallback) => {
104
- return createError(e instanceof Error ? e.message : fallback, null, "application_error");
105
- };
106
- const createValidationError = (message) => {
107
- return createError(message, null, "validation_error");
108
- };
109
- const validatePagination = (pagination = {}) => {
110
- const { limit, offset, max } = pagination;
111
- if (typeof limit === "number" && (limit < 1 || max && limit > max)) return createValidationError("The limit value " + (max ? `must be between 1 and ${max}.` : "is invalid. Only positive values are allowed."));
112
- if (typeof offset === "number" && offset < 0) return createValidationError("Offset must be greater than or equal to 0.");
113
- return null;
114
- };
115
- const CUSTOM_TRACKING_NAME_PATTERN = /^[a-z0-9-]+$/;
116
- const validateCustomTrackingName = (name) => {
117
- if (!name || name.length < 1 || name.length > 64) return createValidationError("The custom tracking domain name must be between 1 and 64 characters.");
118
- if (!CUSTOM_TRACKING_NAME_PATTERN.test(name)) return createValidationError("The custom tracking domain name must match ^[a-z0-9-]+$");
119
- return null;
120
- };
121
- const clean = (data) => {
122
- if (Array.isArray(data)) {
123
- const result = [];
124
- for (let i = 0; i < data.length; i++) {
125
- const cleaned = clean(data[i]);
126
- if (cleaned !== void 0) result.push(cleaned);
127
- }
128
- return result;
129
- }
130
- if (data && typeof data === "object" && data.constructor === Object) {
131
- const result = {};
132
- const obj = data;
133
- const keys = Object.keys(obj);
134
- for (let i = 0; i < keys.length; i++) {
135
- const key = keys[i];
136
- const cleaned = clean(obj[key]);
137
- if (cleaned !== void 0) result[key] = cleaned;
138
- }
139
- return result;
140
- }
141
- return data;
142
- };
143
- const isValidEmail = (email) => {
144
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
145
- };
146
- const parseRecipientString = (input) => {
147
- const trimmed = input.trim();
148
- const match = trimmed.match(/^([^<]*)<([^>]*)>$/);
149
- if (match) {
150
- const [, name, email] = match;
151
- if (!email?.trim() || !isValidEmail(email.trim())) return void 0;
152
- return {
153
- email: email.trim(),
154
- name: name?.trim()
155
- };
156
- }
157
- if (!isValidEmail(trimmed)) return void 0;
158
- return { email: trimmed };
159
- };
160
- const parseRecipient = (recipient) => {
161
- if (typeof recipient === "string") return parseRecipientString(recipient);
162
- if (!recipient?.email || !isValidEmail(recipient.email)) return void 0;
163
- return {
164
- email: recipient.email,
165
- name: recipient.name
166
- };
167
- };
168
- const parseArrayRecipients = (recipients) => {
169
- if (!recipients) return void 0;
170
- const filtered = (typeof recipients === "string" ? [parseRecipientString(recipients)] : Array.isArray(recipients) ? recipients.map(parseRecipient) : [recipients]).filter((recipient) => Boolean(recipient));
171
- return filtered.length > 0 ? filtered : void 0;
172
- };
173
- const stripPemHeaders = (pem) => pem.replace(/-----[^-]+-----|\s|#.*$/gm, "");
174
- const RESERVED_HEADER_NAMES = /* @__PURE__ */ new Set([
175
- "authentication-results",
176
- "bcc",
177
- "cc",
178
- "content-transfer-encoding",
179
- "content-type",
180
- "dkim-signature",
181
- "from",
182
- "message-id",
183
- "received",
184
- "reply-to",
185
- "subject",
186
- "to"
187
- ]);
188
- const MAX_ATTACHMENTS = 1e3;
189
- const MAX_PERSONALIZATIONS = 1e3;
190
- const MAX_RECIPIENTS = 1e3;
191
- const MAX_CAMPAIGN_ID_LENGTH = 48;
192
- const getRecipientCount = (recipients) => recipients?.length || 0;
193
- const isValidTemplateValue = (value) => {
194
- if (typeof value === "string" || typeof value === "boolean" || typeof value === "number") return true;
195
- if (Array.isArray(value)) return value.every(isValidTemplateValue);
196
- if (value !== null && typeof value === "object") return Object.values(value).every(isValidTemplateValue);
197
- return false;
198
- };
199
- const validateTemplateData = (data, label) => {
200
- if (data === void 0) return null;
201
- if (data === null || Array.isArray(data) || typeof data !== "object") return `${label} template data must be a key/value object.`;
202
- for (const [key, value] of Object.entries(data)) if (!isValidTemplateValue(value)) return `${label} template data key '${key}' has an invalid value. Values must be strings, booleans, numbers, lists, or maps.`;
203
- return null;
204
- };
205
- const validateHeaderMap = (headers, label) => {
206
- if (!headers) return null;
207
- for (const [headerName, headerValue] of Object.entries(headers)) {
208
- if (typeof headerValue !== "string") return `${label} header '${headerName}' must have a string value.`;
209
- if (RESERVED_HEADER_NAMES.has(headerName.toLowerCase())) return `${label} headers cannot include the reserved header '${headerName}'.`;
210
- }
211
- return null;
212
- };
213
- const validateSendDkim = (dkim, label) => {
214
- if (!dkim) return null;
215
- if (dkim.domain && !dkim.selector) return `${label} DKIM domain requires a selector.`;
216
- if (dkim.privateKey && (!dkim.domain || !dkim.selector)) return `${label} DKIM privateKey requires both a domain and selector.`;
217
- return null;
218
- };
219
- const mapDkim = (dkim) => ({
220
- dkim_domain: dkim?.domain,
221
- dkim_private_key: dkim?.privateKey ? stripPemHeaders(dkim.privateKey) : void 0,
222
- dkim_selector: dkim?.selector
223
- });
224
- const mapAttachment = (attachment) => ({
225
- content: attachment.content,
226
- filename: attachment.filename,
227
- type: attachment.type,
228
- content_id: attachment.contentId
229
- });
230
- const mapPersonalization = (personalization, index, rootTemplateData) => {
231
- const to = parseArrayRecipients(personalization.to);
232
- if (!to || !to.length) return `Personalization at index ${index} must include at least one recipient in the 'to' field.`;
233
- if (to.length > MAX_RECIPIENTS) return `Personalization at index ${index} cannot include more than ${MAX_RECIPIENTS} 'to' recipients.`;
234
- const cc = parseArrayRecipients(personalization.cc);
235
- if (cc && cc.length > MAX_RECIPIENTS) return `Personalization at index ${index} cannot include more than ${MAX_RECIPIENTS} 'cc' recipients.`;
236
- const bcc = parseArrayRecipients(personalization.bcc);
237
- if (bcc && bcc.length > MAX_RECIPIENTS) return `Personalization at index ${index} cannot include more than ${MAX_RECIPIENTS} 'bcc' recipients.`;
238
- const headerError = validateHeaderMap(personalization.headers, `Personalization at index ${index}`);
239
- if (headerError) return headerError;
240
- const dkimError = validateSendDkim(personalization.dkim, `Personalization at index ${index}`);
241
- if (dkimError) return dkimError;
242
- const templateDataError = validateTemplateData(personalization.template?.data, `Personalization at index ${index}`);
243
- if (templateDataError) return templateDataError;
244
- const dynamic_template_data = rootTemplateData || personalization.template?.data ? {
245
- ...rootTemplateData,
246
- ...personalization.template?.data
247
- } : void 0;
248
- return {
249
- bcc,
250
- cc,
251
- ...mapDkim(personalization.dkim),
252
- dynamic_template_data,
253
- envelope_from: parseRecipient(personalization.envelopeFrom),
254
- from: parseRecipient(personalization.from),
255
- headers: personalization.headers,
256
- reply_to: parseRecipient(personalization.replyTo),
257
- subject: personalization.subject,
258
- to
259
- };
260
- };
261
- const resolveAttachments = async (attachments) => {
262
- if (!attachments) return;
263
- return Promise.all(attachments.map((a) => Promise.resolve(a))).catch((e) => e.message);
264
- };
265
- const buildSendPayload = async (options) => {
266
- const { from, html, text } = options;
267
- const contentTypes = options.content ? new Set(options.content.map((item) => item.type.toLowerCase())) : void 0;
268
- const parsedFrom = parseRecipient(from);
269
- if (!parsedFrom || !parsedFrom.email) return "No sender provided. Use the 'from' option to specify a sender.";
270
- if (!options.subject || typeof options.subject !== "string" || !options.subject.trim()) return "No subject provided. Use the 'subject' option to specify a subject.";
271
- if (!text && !html && (!options.content || !options.content.length)) return "No email content provided";
272
- if (html && contentTypes?.has("text/html")) return "Cannot provide both 'html' and a 'content' entry with type 'text/html'.";
273
- if (text && contentTypes?.has("text/plain")) return "Cannot provide both 'text' and a 'content' entry with type 'text/plain'.";
274
- if (options.attachments && options.attachments.length > MAX_ATTACHMENTS) return `The maximum number of attachments is ${MAX_ATTACHMENTS}.`;
275
- if (options.campaignId && (options.campaignId.length > MAX_CAMPAIGN_ID_LENGTH || /\s/.test(options.campaignId))) return `campaignId must be ${MAX_CAMPAIGN_ID_LENGTH} characters or fewer and must not contain spaces.`;
276
- const rootHeaderError = validateHeaderMap(options.headers, "Root");
277
- if (rootHeaderError) return rootHeaderError;
278
- const rootDkimError = validateSendDkim(options.dkim, "Root");
279
- if (rootDkimError) return rootDkimError;
280
- const rootTemplateDataError = validateTemplateData(options.template?.data, "Root");
281
- if (rootTemplateDataError) return rootTemplateDataError;
282
- if (options.personalizations?.some((p) => p.template?.data !== void 0) && !options.template?.type) return "A root template type is required when using per-personalization template data.";
283
- let personalizations;
284
- if (options.personalizations) {
285
- if (!options.personalizations.length) return "At least one personalization must be provided.";
286
- if (options.personalizations.length > MAX_PERSONALIZATIONS) return `The maximum number of personalizations is ${MAX_PERSONALIZATIONS}.`;
287
- personalizations = [];
288
- for (let index = 0; index < options.personalizations.length; index++) {
289
- const mappedPersonalization = mapPersonalization(options.personalizations[index], index, options.template?.data);
290
- if (typeof mappedPersonalization === "string") return mappedPersonalization;
291
- personalizations.push(mappedPersonalization);
292
- }
293
- } else {
294
- const parsedTo = parseArrayRecipients(options.to);
295
- if (!parsedTo || !parsedTo.length) return "No recipients provided. Use the 'to' option to specify at least one recipient";
296
- if (parsedTo.length > MAX_RECIPIENTS) return `The maximum number of 'to' recipients is ${MAX_RECIPIENTS}.`;
297
- const parsedCc = parseArrayRecipients(options.cc);
298
- if (parsedCc && parsedCc.length > MAX_RECIPIENTS) return `The maximum number of 'cc' recipients is ${MAX_RECIPIENTS}.`;
299
- const parsedBcc = parseArrayRecipients(options.bcc);
300
- if (parsedBcc && parsedBcc.length > MAX_RECIPIENTS) return `The maximum number of 'bcc' recipients is ${MAX_RECIPIENTS}.`;
301
- personalizations = [{
302
- bcc: parsedBcc,
303
- cc: parsedCc,
304
- dynamic_template_data: options.template?.data,
305
- to: parsedTo
306
- }];
307
- }
308
- if (options.transactional === false) {
309
- if (!personalizations.every((personalization) => {
310
- const personalizationDkim = personalization.dkim_selector;
311
- const rootDkim = options.dkim?.selector;
312
- return Boolean(personalizationDkim || rootDkim);
313
- })) return "Non-transactional messages must be DKIM signed.";
314
- if (personalizations.some((personalization) => {
315
- return getRecipientCount(personalization.to) + getRecipientCount(personalization.cc) + getRecipientCount(personalization.bcc) !== 1;
316
- })) return "Non-transactional messages must have exactly one recipient per personalization.";
317
- }
318
- if (options.tracking?.click?.customDomainName !== void 0) {
319
- const clickCustomTrackingNameError = validateCustomTrackingName(options.tracking.click.customDomainName);
320
- if (clickCustomTrackingNameError) return `Invalid click tracking: ${clickCustomTrackingNameError.message}`;
321
- }
322
- if (options.tracking?.open?.customDomainName !== void 0) {
323
- const openCustomTrackingNameError = validateCustomTrackingName(options.tracking.open.customDomainName);
324
- if (openCustomTrackingNameError) return `Invalid open tracking: ${openCustomTrackingNameError.message}`;
325
- }
326
- if (options.unsubscribe?.customDomainName !== void 0) {
327
- const unsubscribeCustomTrackingNameError = validateCustomTrackingName(options.unsubscribe.customDomainName);
328
- if (unsubscribeCustomTrackingNameError) return `Invalid unsubscribe settings: ${unsubscribeCustomTrackingNameError.message}`;
329
- }
330
- const content = [];
331
- const template_type = options.template?.type;
332
- if (text) content.push({
333
- type: "text/plain",
334
- value: text,
335
- template_type
336
- });
337
- if (html) content.push({
338
- type: "text/html",
339
- value: html,
340
- template_type
341
- });
342
- if (options.content) for (const item of options.content) content.push({
343
- type: item.type,
344
- value: item.value,
345
- template_type
346
- });
347
- const resolvedAttachments = await resolveAttachments(options.attachments);
348
- if (typeof resolvedAttachments === "string") return resolvedAttachments;
349
- return {
350
- attachments: resolvedAttachments?.map((a) => mapAttachment(a)),
351
- campaign_id: options.campaignId,
352
- ...mapDkim(options.dkim),
353
- envelope_from: parseRecipient(options.envelopeFrom),
354
- personalizations,
355
- headers: options.headers,
356
- reply_to: parseRecipient(options.replyTo),
357
- from: parsedFrom,
358
- subject: options.subject,
359
- content,
360
- tracking_settings: options.tracking && {
361
- click_tracking: options.tracking.click && {
362
- custom_domain_name: options.tracking.click.customDomainName,
363
- enable: options.tracking.click.enable
364
- },
365
- open_tracking: options.tracking.open && {
366
- custom_domain_name: options.tracking.open.customDomainName,
367
- enable: options.tracking.open.enable
368
- }
369
- },
370
- transactional: options.transactional,
371
- unsubscribe_settings: options.unsubscribe && { custom_domain_name: options.unsubscribe.customDomainName }
372
- };
373
- };
374
- var Emails = class {
375
- mailchannels;
376
- constructor(mailchannels) {
377
- this.mailchannels = mailchannels;
378
- }
379
- async _sendEmail(options, flags) {
380
- let error = null;
381
- const payload = await buildSendPayload(options);
382
- if (typeof payload === "string") {
383
- error = createValidationError(payload);
384
- return {
385
- data: null,
386
- error
387
- };
388
- }
389
- const endpoint = flags.async ? "/tx/v1/send-async" : "/tx/v1/send";
390
- const response = await this.mailchannels.post(endpoint, {
391
- query: { "dry-run": flags.dryRun },
392
- body: payload,
393
- onResponseError: async ({ response }) => {
394
- error = getStatusError(response, {
395
- [400]: "Bad Request.",
396
- [403]: "User does not have access to this feature.",
397
- [413]: "The total message size should not exceed 30MB. This includes the message itself, headers, and the combined size of any attachments."
398
- });
399
- }
400
- }).catch((e) => {
401
- error ||= getResultError(e, flags.async ? "Failed to queue email." : "Failed to send email.");
402
- return null;
403
- });
404
- if (!response) return {
405
- data: null,
406
- error
407
- };
408
- if (flags.async) {
409
- const asyncResponse = response;
410
- return {
411
- data: clean({
412
- queuedAt: asyncResponse.queued_at,
413
- requestId: asyncResponse.request_id
414
- }),
415
- error: null
416
- };
417
- }
418
- const syncResponse = response;
419
- return {
420
- data: clean({
421
- rendered: syncResponse.data,
422
- requestId: syncResponse.request_id,
423
- results: syncResponse.results?.map((result) => ({
424
- index: result.index,
425
- messageId: result.message_id,
426
- reason: result.reason,
427
- status: result.status
428
- }))
429
- }),
430
- error: null
431
- };
432
- }
433
- async send(options, dryRun = false) {
434
- return this._sendEmail(options, { dryRun });
435
- }
436
- async queue(options) {
437
- return this._sendEmail(options, { async: true });
438
- }
439
- async sendAsync(options) {
440
- return this.queue(options);
441
- }
442
- };
443
- const mapDkimKey = (key) => ({
444
- algorithm: key.algorithm,
445
- createdAt: key.created_at,
446
- dnsRecords: key.dkim_dns_records,
447
- domain: key.domain,
448
- gracePeriodExpiresAt: key.gracePeriodExpiresAt,
449
- length: key.key_length,
450
- publicKey: key.public_key,
451
- retiresAt: key.retiresAt,
452
- selector: key.selector,
453
- status: key.status,
454
- statusModifiedAt: key.status_modified_at
455
- });
456
- var DomainsDkim = class {
457
- mailchannels;
458
- constructor(mailchannels) {
459
- this.mailchannels = mailchannels;
460
- }
461
- async create(domain, options) {
462
- let error = null;
463
- if (!domain) {
464
- error = createValidationError("No domain provided.");
465
- return {
466
- data: null,
467
- error
468
- };
469
- }
470
- if (!options.selector || options.selector.length > 63) {
471
- error = createValidationError("Selector must be between 1 and 63 characters.");
472
- return {
473
- data: null,
474
- error
475
- };
476
- }
477
- const payload = {
478
- algorithm: options.algorithm,
479
- key_length: options.length,
480
- selector: options.selector
481
- };
482
- const response = await this.mailchannels.post(`/tx/v1/domains/${encodeURIComponent(domain)}/dkim-keys`, {
483
- body: payload,
484
- onResponseError: async ({ response }) => {
485
- error = getStatusError(response, {
486
- [400]: "Bad Request.",
487
- [409]: "Key pair already created for domain, and selector."
488
- });
489
- }
490
- }).catch((e) => {
491
- error ||= getResultError(e, "Failed to create DKIM key.");
492
- return null;
493
- });
494
- if (!response) return {
495
- data: null,
496
- error
497
- };
498
- return {
499
- data: clean(mapDkimKey(response)),
500
- error: null
501
- };
502
- }
503
- async list(domain, options) {
504
- let error = null;
505
- if (!domain) {
506
- error = createValidationError("No domain provided.");
507
- return {
508
- data: null,
509
- error
510
- };
511
- }
512
- if (options?.selector && options.selector.length > 63) {
513
- error = createValidationError("Selector must be between 1 and 63 characters.");
514
- return {
515
- data: null,
516
- error
517
- };
518
- }
519
- error = validatePagination({
520
- ...options,
521
- max: 100
522
- });
523
- if (error) return {
524
- data: null,
525
- error
526
- };
527
- const payload = {
528
- selector: options?.selector,
529
- status: options?.status,
530
- offset: options?.offset,
531
- limit: options?.limit,
532
- include_dns_record: options?.includeDnsRecord
533
- };
534
- const response = await this.mailchannels.get(`/tx/v1/domains/${encodeURIComponent(domain)}/dkim-keys`, {
535
- query: payload,
536
- onResponseError: async ({ response }) => {
537
- error = getStatusError(response, { [400]: "Bad Request." });
538
- }
539
- }).catch((e) => {
540
- error ||= getResultError(e, "Failed to fetch DKIM keys.");
541
- return null;
542
- });
543
- if (!response) return {
544
- data: null,
545
- error
546
- };
547
- return {
548
- data: clean(response.keys.map(mapDkimKey)),
549
- error: null
550
- };
551
- }
552
- async updateStatus(domain, options) {
553
- let error = null;
554
- if (!domain) {
555
- error = createValidationError("No domain provided.");
556
- return {
557
- success: false,
558
- error
559
- };
560
- }
561
- if (!options.selector || options.selector.length > 63) {
562
- error = createValidationError("Selector must be between 1 and 63 characters.");
563
- return {
564
- success: false,
565
- error
566
- };
567
- }
568
- const payload = { status: options.status };
569
- await this.mailchannels.patch(`/tx/v1/domains/${encodeURIComponent(domain)}/dkim-keys/${encodeURIComponent(options.selector)}`, {
570
- body: payload,
571
- onResponseError: async ({ response }) => {
572
- error = getStatusError(response, {
573
- [400]: "Bad Request.",
574
- [404]: "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."
575
- });
576
- }
577
- }).catch((e) => {
578
- error ||= getResultError(e, "Failed to update status of DKIM key.");
579
- });
580
- return {
581
- success: !error,
582
- error
583
- };
584
- }
585
- async rotate(domain, selector, options) {
586
- let error = null;
587
- if (!domain) {
588
- error = createValidationError("No domain provided.");
589
- return {
590
- data: null,
591
- error
592
- };
593
- }
594
- if (!selector || selector.length > 63) {
595
- error = createValidationError("Selector must be between 1 and 63 characters.");
596
- return {
597
- data: null,
598
- error
599
- };
600
- }
601
- if (!options.newKey.selector || options.newKey.selector.length > 63) {
602
- error = createValidationError("New key selector must be between 1 and 63 characters.");
603
- return {
604
- data: null,
605
- error
606
- };
607
- }
608
- const payload = { new_key: { selector: options.newKey.selector } };
609
- const response = await this.mailchannels.post(`/tx/v1/domains/${encodeURIComponent(domain)}/dkim-keys/${encodeURIComponent(selector)}/rotate`, {
610
- body: payload,
611
- onResponseError: async ({ response }) => {
612
- error = getStatusError(response, {
613
- [400]: "Bad Request.",
614
- [404]: "Specified key pair not found.",
615
- [409]: "Key pair already created for domain, and provided new key selector."
616
- });
617
- }
618
- }).catch((e) => {
619
- error ||= getResultError(e, "Failed to rotate DKIM key.");
620
- return null;
621
- });
622
- if (!response) return {
623
- data: null,
624
- error
625
- };
626
- return {
627
- data: clean({
628
- new: mapDkimKey(response.new_key),
629
- rotated: mapDkimKey(response.rotated_key)
630
- }),
631
- error: null
632
- };
633
- }
634
- };
635
- var DomainsCustomTracking = class DomainsCustomTracking {
636
- mailchannels;
637
- static SCOPE_VALUES = /* @__PURE__ */ new Set([
638
- "click",
639
- "open",
640
- "unsubscribe"
641
- ]);
642
- constructor(mailchannels) {
643
- this.mailchannels = mailchannels;
644
- }
645
- async list(options) {
646
- let error = null;
647
- error = validatePagination({
648
- ...options,
649
- max: 1e3
650
- });
651
- if (error) return {
652
- data: null,
653
- error
654
- };
655
- const response = await this.mailchannels.get("/tx/v1/custom-tracking-domains", {
656
- query: options,
657
- onResponseError: async ({ response }) => {
658
- error = getStatusError(response, { [400]: "Bad Request." });
659
- }
660
- }).catch((e) => {
661
- error ||= getResultError(e, "Failed to fetch custom tracking domains.");
662
- return null;
663
- });
664
- if (!response) return {
665
- data: null,
666
- error
667
- };
668
- return {
669
- data: clean({
670
- customTrackingDomains: response.custom_tracking_domains.map((d) => ({
671
- name: d.name,
672
- hostname: d.hostname,
673
- scope: d.scope,
674
- status: d.status,
675
- createdAt: d.created_at
676
- })),
677
- total: response.total
678
- }),
679
- error: null
680
- };
681
- }
682
- async create(name, hostname, scope) {
683
- let error = null;
684
- error = validateCustomTrackingName(name);
685
- if (error) return {
686
- data: null,
687
- error
688
- };
689
- if (!hostname) {
690
- error = createValidationError("Hostname is required.");
691
- return {
692
- data: null,
693
- error
694
- };
695
- }
696
- if (!scope || !DomainsCustomTracking.SCOPE_VALUES.has(scope)) {
697
- error = createValidationError("Scope must be one of 'click', 'open', or 'unsubscribe'.");
698
- return {
699
- data: null,
700
- error
701
- };
702
- }
703
- const payload = {
704
- name,
705
- hostname,
706
- scope
707
- };
708
- let statusCode = null;
709
- const response = await this.mailchannels.post("/tx/v1/custom-tracking-domains", {
710
- body: payload,
711
- onResponse: async ({ response }) => {
712
- statusCode = response.status;
713
- },
714
- onResponseError: async ({ response }) => {
715
- error = getStatusError(response, {
716
- [400]: "Invalid request body.",
717
- [403]: "No permission to register this domain.",
718
- [409]: "A domain with the same name already exists, or the hostname and scope combination is already registered.",
719
- [422]: "DNS verification incomplete. Either the TXT ownership record has not propagated yet or the hostname CNAME does not point to the required target."
720
- });
721
- }
722
- }).catch((e) => {
723
- error ||= getResultError(e, "Failed to create custom tracking domain.");
724
- return null;
725
- });
726
- if (!response) return {
727
- data: null,
728
- error
729
- };
730
- if (statusCode === 202) {
731
- const dnsSetupRequiredResponse = response;
732
- return {
733
- data: clean({
734
- dnsSetupRequired: true,
735
- token: dnsSetupRequiredResponse.token,
736
- txtRecordName: dnsSetupRequiredResponse.txt_record_name,
737
- txtRecordValue: dnsSetupRequiredResponse.txt_record_value,
738
- instructions: dnsSetupRequiredResponse.instructions
739
- }),
740
- error: null
741
- };
742
- }
743
- const createResponse = response;
744
- return {
745
- data: clean({
746
- dnsSetupRequired: false,
747
- name: createResponse.name,
748
- hostname: createResponse.hostname,
749
- scope: createResponse.scope,
750
- status: createResponse.status,
751
- createdAt: createResponse.created_at
752
- }),
753
- error: null
754
- };
755
- }
756
- async update(hostname, scope, options) {
757
- let error = null;
758
- if (!hostname) {
759
- error = createValidationError("Hostname is required.");
760
- return {
761
- data: null,
762
- error
763
- };
764
- }
765
- if (!scope || !DomainsCustomTracking.SCOPE_VALUES.has(scope)) {
766
- error = createValidationError("Scope must be one of 'click', 'open', or 'unsubscribe'.");
767
- return {
768
- data: null,
769
- error
770
- };
771
- }
772
- if (options.name !== void 0) {
773
- error = validateCustomTrackingName(options.name);
774
- if (error) return {
775
- data: null,
776
- error
777
- };
778
- }
779
- if (options.name === void 0 && options.status === void 0) {
780
- error = createValidationError("At least one of 'name' or 'status' must be provided.");
781
- return {
782
- data: null,
783
- error
784
- };
785
- }
786
- const payload = {
787
- name: options.name,
788
- status: options.status
789
- };
790
- let statusCode = null;
791
- const response = await this.mailchannels.patch(`/tx/v1/custom-tracking-domains/${encodeURIComponent(hostname)}/${encodeURIComponent(scope)}`, {
792
- body: payload,
793
- onResponse: async ({ response }) => {
794
- statusCode = response.status;
795
- },
796
- onResponseError: async ({ response }) => {
797
- error = getStatusError(response, {
798
- [400]: "Bad Request.",
799
- [403]: "No permission to update this domain.",
800
- [404]: `Custom tracking domain for hostname '${hostname}' and scope '${scope}' not found.`,
801
- [409]: "Name already used by another domain.",
802
- [422]: "DNS verification incomplete. Either the TXT ownership record has not propagated yet or the hostname CNAME does not point to the required target."
803
- });
804
- }
805
- }).catch((e) => {
806
- error ||= getResultError(e, "Failed to update custom tracking domain.");
807
- return null;
808
- });
809
- if (!response) return {
810
- data: null,
811
- error
812
- };
813
- if (statusCode === 202) {
814
- const dnsSetupRequiredResponse = response;
815
- return {
816
- data: clean({
817
- dnsSetupRequired: true,
818
- token: dnsSetupRequiredResponse.token,
819
- txtRecordName: dnsSetupRequiredResponse.txt_record_name,
820
- txtRecordValue: dnsSetupRequiredResponse.txt_record_value,
821
- instructions: dnsSetupRequiredResponse.instructions
822
- }),
823
- error: null
824
- };
825
- }
826
- const updateResponse = response;
827
- return {
828
- data: clean({
829
- dnsSetupRequired: false,
830
- name: updateResponse.name,
831
- hostname: updateResponse.hostname,
832
- scope: updateResponse.scope,
833
- status: updateResponse.status,
834
- createdAt: updateResponse.created_at
835
- }),
836
- error: null
837
- };
838
- }
839
- async delete(hostname, scope) {
840
- let error = null;
841
- if (!hostname) {
842
- error = createValidationError("Hostname is required.");
843
- return {
844
- success: false,
845
- error
846
- };
847
- }
848
- if (!scope || !DomainsCustomTracking.SCOPE_VALUES.has(scope)) {
849
- error = createValidationError("Scope must be one of 'click', 'open', or 'unsubscribe'.");
850
- return {
851
- success: false,
852
- error
853
- };
854
- }
855
- await this.mailchannels.delete(`/tx/v1/custom-tracking-domains/${encodeURIComponent(hostname)}/${encodeURIComponent(scope)}`, { onResponseError: async ({ response }) => {
856
- error = getStatusError(response, { [400]: "Invalid hostname or scope value" });
857
- } }).catch((e) => {
858
- error ||= getResultError(e, "Failed to delete custom tracking domain.");
859
- });
860
- return {
861
- success: !error,
862
- error
863
- };
864
- }
865
- };
866
- var Domains = class {
867
- mailchannels;
868
- dkim;
869
- customTracking;
870
- constructor(mailchannels) {
871
- this.mailchannels = mailchannels;
872
- this.dkim = new DomainsDkim(mailchannels);
873
- this.customTracking = new DomainsCustomTracking(mailchannels);
874
- }
875
- async check(domain, options) {
876
- let error = null;
877
- if (!domain) {
878
- error = createValidationError("No domain provided.");
879
- return {
880
- data: null,
881
- error
882
- };
883
- }
884
- const dkimOptions = options?.dkim ? Array.isArray(options.dkim) ? options.dkim : [options.dkim] : void 0;
885
- if (dkimOptions && dkimOptions.length > 10) {
886
- error = createValidationError("A maximum of 10 DKIM settings can be provided.");
887
- return {
888
- data: null,
889
- error
890
- };
891
- }
892
- if (dkimOptions?.find(({ privateKey, selector }) => privateKey && !selector)) {
893
- error = createValidationError("DKIM settings with a privateKey must also include a selector.");
894
- return {
895
- data: null,
896
- error
897
- };
898
- }
899
- const payload = {
900
- dkim_settings: dkimOptions?.map((dkim) => ({
901
- dkim_domain: dkim.domain,
902
- dkim_private_key: dkim.privateKey ? stripPemHeaders(dkim.privateKey) : void 0,
903
- dkim_selector: dkim.selector
904
- })),
905
- domain,
906
- sender_id: options?.senderId
907
- };
908
- const response = await this.mailchannels.post("/tx/v1/check-domain", {
909
- body: payload,
910
- onResponseError: async ({ response }) => {
911
- error = getStatusError(response, {
912
- [400]: "Bad Request.",
913
- [403]: "User does not have access to this feature."
914
- });
915
- }
916
- }).catch((e) => {
917
- error ||= getResultError(e, "Failed to check domain.");
918
- return null;
919
- });
920
- if (!response) return {
921
- data: null,
922
- error
923
- };
924
- return {
925
- data: clean({
926
- dkim: response.check_results.dkim.map((dkimResults) => ({
927
- domain: dkimResults.dkim_domain,
928
- keyStatus: dkimResults.dkim_key_status,
929
- selector: dkimResults.dkim_selector,
930
- reason: dkimResults.reason,
931
- verdict: dkimResults.verdict
932
- })),
933
- domainLockdown: response.check_results.domain_lockdown,
934
- senderDomain: response.check_results.sender_domain,
935
- spf: response.check_results.spf,
936
- references: response.references
937
- }),
938
- error: null
939
- };
940
- }
941
- };
942
- const HMAC_SHA256 = {
943
- name: "HMAC",
944
- hash: "SHA-256"
945
- };
946
- const ED25519 = {
947
- name: "Ed25519",
948
- namedCurve: "Ed25519"
949
- };
950
- const encoder = new TextEncoder();
951
- const HEADER_CONTENT_DIGEST = "content-digest";
952
- const HEADER_SIGNATURE = "signature";
953
- const HEADER_SIGNATURE_INPUT = "signature-input";
954
- const signingKeyCache = /* @__PURE__ */ new Map();
955
- const validateContentDigest = async (header, body) => {
956
- const match = header.match(/^(.*?)=:(.*?):$/);
957
- if (!match) return false;
958
- const [, algorithm, hash] = match;
959
- if (!algorithm || !hash) return false;
960
- const normalizedAlgorithm = algorithm.replace("-", "").toLowerCase();
961
- if (!["sha256"].includes(normalizedAlgorithm)) return false;
962
- const signatureBuffer = await subtle.digest(HMAC_SHA256.hash, encoder.encode(body));
963
- return Buffer.from(signatureBuffer).toString("base64") === hash;
964
- };
965
- const extractSignature = (signatureHeader) => {
966
- const signatureMatch = signatureHeader.match(/sig_\d+=:([^:]+):/);
967
- return signatureMatch && signatureMatch[1] ? signatureMatch[1] : null;
968
- };
969
- const extractInputValues = (header) => {
970
- const match = header.match(/^(\w+)=\(([^)]+)\);created=(\d+);alg="([^"]+)";keyid="([^"]+)"$/);
971
- if (!match) return null;
972
- return {
973
- name: match[1],
974
- timestamp: Number.parseInt(match[3], 10),
975
- algorithm: match[4],
976
- keyId: match[5]
977
- };
978
- };
979
- async function isValidWebhook(options) {
980
- const { payload, headers, cache = true } = options;
981
- const contentDigest = headers[HEADER_CONTENT_DIGEST];
982
- const messageSignature = headers[HEADER_SIGNATURE];
983
- const signatureInput = headers[HEADER_SIGNATURE_INPUT];
984
- if (!payload || !contentDigest || !messageSignature || !signatureInput || !await validateContentDigest(contentDigest, payload)) return false;
985
- const signature = extractSignature(messageSignature);
986
- if (!signature) return false;
987
- const values = extractInputValues(signatureInput);
988
- if (!values) return false;
989
- if (Math.floor(Date.now() / 1e3) - values.timestamp > 300) return false;
990
- const signingString = `"content-digest": ${contentDigest}
991
- "@signature-params": ("content-digest");created=${values.timestamp};alg="${values.algorithm}";keyid="${values.keyId}"`;
992
- let publicKey = options.publicKey;
993
- if (!publicKey && values.keyId) {
994
- if (cache) publicKey = signingKeyCache.get(values.keyId);
995
- if (!publicKey) {
996
- const publicKeyResponse = await $fetch("/tx/v1/webhook/public-key", {
997
- baseURL: "https://api.mailchannels.net",
998
- query: { id: values.keyId }
999
- }).catch(() => null);
1000
- if (!publicKeyResponse) return false;
1001
- publicKey = publicKeyResponse.key;
1002
- if (cache && publicKey) signingKeyCache.set(values.keyId, publicKey);
1003
- }
1004
- }
1005
- if (!publicKey) return false;
1006
- publicKey = stripPemHeaders(publicKey);
1007
- const encoding = "base64";
1008
- const format = "spki";
1009
- const publicKeyBuffer = Buffer.from(publicKey, encoding);
1010
- const webhookSignatureBuffer = Buffer.from(signature, encoding);
1011
- const key = await subtle.importKey(format, publicKeyBuffer, ED25519, false, ["verify"]);
1012
- return subtle.verify(ED25519.name, key, webhookSignatureBuffer, encoder.encode(signingString));
1013
- }
1014
- const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
1015
- const ISO_DATE_ONLY_REGEX = /^\d{4}-\d{2}-\d{2}$/;
1016
- const formatDateInput = (value) => {
1017
- if (value instanceof Date) {
1018
- if (Number.isNaN(value.getTime())) return null;
1019
- return value.toISOString();
1020
- }
1021
- if (typeof value === "string") {
1022
- if (!ISO_DATE_REGEX.test(value) && !ISO_DATE_ONLY_REGEX.test(value) || Number.isNaN(Date.parse(value))) return null;
1023
- if (ISO_DATE_REGEX.test(value)) return new Date(value).toISOString();
1024
- }
1025
- return value;
1026
- };
1027
- const parseDateInputs = (options) => {
1028
- const dates = {};
1029
- const keys = Object.keys(options);
1030
- for (const key of keys) {
1031
- const formatted = formatDateInput(options?.[key]);
1032
- if (formatted === null) return {
1033
- dates: null,
1034
- error: createValidationError(`The '${key}' value is not a valid date.`)
1035
- };
1036
- dates[key] = formatted;
1037
- }
1038
- return {
1039
- dates,
1040
- error: null
1041
- };
1042
- };
1043
- var Webhooks = class Webhooks {
1044
- mailchannels;
1045
- constructor(mailchannels) {
1046
- this.mailchannels = mailchannels;
1047
- }
1048
- async create(endpoint) {
1049
- let error = null;
1050
- if (!endpoint) {
1051
- error = createValidationError("No endpoint provided.");
1052
- return {
1053
- success: false,
1054
- error
1055
- };
1056
- }
1057
- if (endpoint.length > 8e3) {
1058
- error = createValidationError("The endpoint exceeds the maximum length of 8000 characters.");
1059
- return {
1060
- success: false,
1061
- error
1062
- };
1063
- }
1064
- await this.mailchannels.post("/tx/v1/webhook", {
1065
- query: { endpoint },
1066
- onResponseError: async ({ response }) => {
1067
- error = getStatusError(response, { [409]: `Endpoint '${endpoint}' is already enrolled to receive notifications.` });
1068
- }
1069
- }).catch((e) => {
1070
- error ||= getResultError(e, "Failed to create webhook.");
1071
- });
1072
- return {
1073
- success: !error,
1074
- error
1075
- };
1076
- }
1077
- async list() {
1078
- let error = null;
1079
- const response = await this.mailchannels.get("/tx/v1/webhook", { onResponseError: async ({ response }) => {
1080
- error = getStatusError(response);
1081
- } }).catch((e) => {
1082
- error ||= getResultError(e, "Failed to fetch webhooks.");
1083
- return null;
1084
- });
1085
- if (!response) return {
1086
- data: null,
1087
- error
1088
- };
1089
- return {
1090
- data: clean(response.map((webhooks) => ({ webhook: webhooks.webhook }))),
1091
- error: null
1092
- };
1093
- }
1094
- async deleteAll() {
1095
- let error = null;
1096
- await this.mailchannels.delete("/tx/v1/webhook", { onResponseError: async ({ response }) => {
1097
- error = getStatusError(response);
1098
- } }).catch((e) => {
1099
- error ||= getResultError(e, "Failed to delete webhooks.");
1100
- });
1101
- return {
1102
- success: !error,
1103
- error
1104
- };
1105
- }
1106
- async getSigningKey(id) {
1107
- let error = null;
1108
- const response = await this.mailchannels.get("/tx/v1/webhook/public-key", {
1109
- query: { id },
1110
- onResponseError: async ({ response }) => {
1111
- error = getStatusError(response, {
1112
- [400]: "Bad Request.",
1113
- [404]: `The key '${id}' is not found.`
1114
- });
1115
- }
1116
- }).catch((e) => {
1117
- error ||= getResultError(e, "Failed to get signing key.");
1118
- return null;
1119
- });
1120
- if (!response) return {
1121
- data: null,
1122
- error
1123
- };
1124
- return {
1125
- data: clean({
1126
- id: response.id,
1127
- key: response.key
1128
- }),
1129
- error: null
1130
- };
1131
- }
1132
- async validate(requestId) {
1133
- let error = null;
1134
- if (requestId && requestId.length > 28) {
1135
- error = createValidationError("The request id should not exceed 28 characters.");
1136
- return {
1137
- data: null,
1138
- error
1139
- };
1140
- }
1141
- const response = await this.mailchannels.post("/tx/v1/webhook/validate", {
1142
- body: { request_id: requestId },
1143
- onResponseError: async ({ response }) => {
1144
- error = getStatusError(response, {
1145
- [400]: "Bad Request.",
1146
- [404]: "No webhooks found for the account."
1147
- });
1148
- }
1149
- }).catch((e) => {
1150
- error ||= getResultError(e, "Failed to validate webhooks.");
1151
- return null;
1152
- });
1153
- if (!response) return {
1154
- data: null,
1155
- error
1156
- };
1157
- return {
1158
- data: clean({
1159
- allPassed: response.all_passed,
1160
- results: response.results
1161
- }),
1162
- error: null
1163
- };
1164
- }
1165
- static async verify(options) {
1166
- let error = null;
1167
- if (!await isValidWebhook(options).catch(() => false)) {
1168
- error = createValidationError("Invalid webhook signature.");
1169
- return {
1170
- data: null,
1171
- error
1172
- };
1173
- }
1174
- try {
1175
- const payload = JSON.parse(options.payload);
1176
- if (!Array.isArray(payload)) {
1177
- error = createValidationError("Invalid webhook payload.");
1178
- return {
1179
- data: null,
1180
- error
1181
- };
1182
- }
1183
- return {
1184
- data: clean(payload.map((event) => ({
1185
- email: event.email,
1186
- customerHandle: event.customer_handle,
1187
- timestamp: event.timestamp,
1188
- smtpId: event.smtp_id,
1189
- event: event.event,
1190
- requestId: event.request_id,
1191
- campaignId: "campaign_id" in event ? event.campaign_id : void 0,
1192
- recipients: "recipients" in event ? event.recipients : void 0,
1193
- userAgent: "user_agent" in event ? event.user_agent : void 0,
1194
- ip: "ip" in event ? event.ip : void 0,
1195
- url: "url" in event ? event.url : void 0,
1196
- status: "status" in event ? event.status : void 0,
1197
- reason: "reason" in event ? event.reason : void 0
1198
- }))),
1199
- error: null
1200
- };
1201
- } catch {
1202
- error = createValidationError("Invalid webhook payload.");
1203
- return {
1204
- data: null,
1205
- error
1206
- };
1207
- }
1208
- }
1209
- async verify(options) {
1210
- return Webhooks.verify(options);
1211
- }
1212
- async batches(options) {
1213
- let error = null;
1214
- error = validatePagination({
1215
- ...options,
1216
- max: 500
1217
- });
1218
- if (error) return {
1219
- data: null,
1220
- error
1221
- };
1222
- if (options?.statuses && options.statuses.length > 6) return {
1223
- data: null,
1224
- error: createValidationError("A maximum of 6 status filters can be provided.")
1225
- };
1226
- if (options?.statuses && new Set(options.statuses).size !== options.statuses.length) return {
1227
- data: null,
1228
- error: createValidationError("Status filters must be unique.")
1229
- };
1230
- const { dates, error: dateError } = parseDateInputs({
1231
- createdAfter: options?.createdAfter,
1232
- createdBefore: options?.createdBefore
1233
- });
1234
- if (!dates || dateError) return {
1235
- data: null,
1236
- error: dateError
1237
- };
1238
- if (dates.createdAfter && dates.createdBefore) {
1239
- const createdAfter = Date.parse(dates.createdAfter);
1240
- const createdBefore = Date.parse(dates.createdBefore);
1241
- const maxRangeMs = 26784e5;
1242
- if (createdBefore <= createdAfter) return {
1243
- data: null,
1244
- error: createValidationError("createdBefore must be later than createdAfter.")
1245
- };
1246
- if (createdBefore - createdAfter > maxRangeMs) return {
1247
- data: null,
1248
- error: createValidationError("The time range between createdAfter and createdBefore must not exceed 31 days.")
1249
- };
1250
- }
1251
- const response = await this.mailchannels.get("/tx/v1/webhook-batch", {
1252
- query: {
1253
- created_after: dates.createdAfter,
1254
- created_before: dates.createdBefore,
1255
- statuses: options?.statuses,
1256
- webhook: options?.webhook,
1257
- limit: options?.limit,
1258
- offset: options?.offset
1259
- },
1260
- onResponseError: async ({ response }) => {
1261
- error = getStatusError(response, { [400]: "Bad Request." });
1262
- }
1263
- }).catch((e) => {
1264
- error ||= getResultError(e, "Failed to fetch webhook batches.");
1265
- return null;
1266
- });
1267
- if (!response) return {
1268
- data: null,
1269
- error
1270
- };
1271
- return {
1272
- data: clean(response.webhook_batches.map((batch) => ({
1273
- batchId: batch.batch_id,
1274
- createdAt: batch.created_at,
1275
- customerHandle: batch.customer_handle,
1276
- duration: batch.duration,
1277
- eventCount: batch.event_count,
1278
- status: batch.status,
1279
- statusCode: batch.status_code,
1280
- webhook: batch.webhook
1281
- }))),
1282
- error: null
1283
- };
1284
- }
1285
- async resendBatch(batchId) {
1286
- let error = null;
1287
- const response = await this.mailchannels.post(`/tx/v1/webhook-batch/${encodeURIComponent(batchId)}/resend`, { onResponseError: async ({ response }) => {
1288
- error = getStatusError(response, {
1289
- [400]: "Bad Request. The batch ID is invalid.",
1290
- [404]: `The batch '${batchId}' is not found for the customer.`
1291
- });
1292
- } }).catch((e) => {
1293
- error ||= getResultError(e, "Failed to resend webhook batch.");
1294
- return null;
1295
- });
1296
- if (!response) return {
1297
- data: null,
1298
- error
1299
- };
1300
- return {
1301
- data: clean({
1302
- batchId: response.batch_id,
1303
- customerHandle: response.customer_handle,
1304
- webhook: response.webhook,
1305
- createdAt: response.created_at,
1306
- eventCount: response.event_count,
1307
- duration: response.duration_in_ms,
1308
- statusCode: response.status_code
1309
- }),
1310
- error: null
1311
- };
1312
- }
1313
- };
1314
- var SubAccountsApiKeys = class {
1315
- mailchannels;
1316
- constructor(mailchannels) {
1317
- this.mailchannels = mailchannels;
1318
- }
1319
- async create(handle) {
1320
- let error = null;
1321
- if (!handle) {
1322
- error = createValidationError("No handle provided.");
1323
- return {
1324
- data: null,
1325
- error
1326
- };
1327
- }
1328
- const response = await this.mailchannels.post(`/tx/v1/sub-account/${encodeURIComponent(handle)}/api-key`, { onResponseError: async ({ response }) => {
1329
- error = getStatusError(response, {
1330
- [403]: "You can't create API keys for this sub-account.",
1331
- [404]: `Sub-account with handle '${handle}' not found.`,
1332
- [422]: "You have reached the limit of API keys you can create for this sub-account."
1333
- });
1334
- } }).catch((e) => {
1335
- error ||= getResultError(e, "Failed to create sub-account API key.");
1336
- return null;
1337
- });
1338
- if (!response) return {
1339
- data: null,
1340
- error
1341
- };
1342
- return {
1343
- data: clean(response),
1344
- error: null
1345
- };
1346
- }
1347
- async list(handle, options) {
1348
- let error = null;
1349
- if (!handle) {
1350
- error = createValidationError("No handle provided.");
1351
- return {
1352
- data: null,
1353
- error
1354
- };
1355
- }
1356
- error = validatePagination({
1357
- ...options,
1358
- max: 1e3
1359
- });
1360
- if (error) return {
1361
- data: null,
1362
- error
1363
- };
1364
- const response = await this.mailchannels.get(`/tx/v1/sub-account/${encodeURIComponent(handle)}/api-key`, {
1365
- query: options,
1366
- onResponseError: async ({ response }) => {
1367
- error = getStatusError(response, { [404]: `Sub-account with handle '${handle}' not found.` });
1368
- }
1369
- }).catch((e) => {
1370
- error ||= getResultError(e, "Failed to fetch sub-account API keys.");
1371
- return null;
1372
- });
1373
- if (!response) return {
1374
- data: null,
1375
- error
1376
- };
1377
- return {
1378
- data: clean(response.map((key) => ({
1379
- id: key.id,
1380
- key: key.key
1381
- }))),
1382
- error: null
1383
- };
1384
- }
1385
- async delete(handle, id) {
1386
- let error = null;
1387
- if (!handle) {
1388
- error = createValidationError("No handle provided.");
1389
- return {
1390
- success: false,
1391
- error
1392
- };
1393
- }
1394
- await this.mailchannels.delete(`/tx/v1/sub-account/${encodeURIComponent(handle)}/api-key/${encodeURIComponent(id)}`, { onResponseError: async ({ response }) => {
1395
- error = getStatusError(response, { [400]: "Missing or invalid API key ID." });
1396
- } }).catch((e) => {
1397
- error ||= getResultError(e, "Failed to delete sub-account API key.");
1398
- });
1399
- return {
1400
- success: !error,
1401
- error
1402
- };
1403
- }
1404
- };
1405
- var SubAccountsSmtpPasswords = class {
1406
- mailchannels;
1407
- constructor(mailchannels) {
1408
- this.mailchannels = mailchannels;
1409
- }
1410
- async create(handle) {
1411
- let error = null;
1412
- if (!handle) {
1413
- error = createValidationError("No handle provided.");
1414
- return {
1415
- data: null,
1416
- error
1417
- };
1418
- }
1419
- const response = await this.mailchannels.post(`/tx/v1/sub-account/${encodeURIComponent(handle)}/smtp-password`, { onResponseError: async ({ response }) => {
1420
- error = getStatusError(response, {
1421
- [403]: "You can't create SMTP passwords for this sub-account.",
1422
- [404]: `Sub-account with handle '${handle}' not found.`,
1423
- [422]: "You have reached the limit of SMTP passwords you can create for this sub-account."
1424
- });
1425
- } }).catch((e) => {
1426
- error ||= getResultError(e, "Failed to create sub-account SMTP password.");
1427
- return null;
1428
- });
1429
- if (!response) return {
1430
- data: null,
1431
- error
1432
- };
1433
- return {
1434
- data: clean({
1435
- enabled: response.enabled,
1436
- id: response.id,
1437
- smtpPassword: response.smtp_password
1438
- }),
1439
- error: null
1440
- };
1441
- }
1442
- async list(handle) {
1443
- let error = null;
1444
- if (!handle) {
1445
- error = createValidationError("No handle provided.");
1446
- return {
1447
- data: null,
1448
- error
1449
- };
1450
- }
1451
- const response = await this.mailchannels.get(`/tx/v1/sub-account/${encodeURIComponent(handle)}/smtp-password`, { onResponseError: async ({ response }) => {
1452
- error = getStatusError(response, { [404]: `Sub-account with handle '${handle}' not found.` });
1453
- } }).catch((e) => {
1454
- error ||= getResultError(e, "Failed to fetch sub-account SMTP passwords.");
1455
- return null;
1456
- });
1457
- if (!response) return {
1458
- data: null,
1459
- error
1460
- };
1461
- return {
1462
- data: clean(response.map((password) => ({
1463
- enabled: password.enabled,
1464
- id: password.id,
1465
- smtpPassword: password.smtp_password
1466
- }))),
1467
- error: null
1468
- };
1469
- }
1470
- async delete(handle, id) {
1471
- let error = null;
1472
- if (!handle) {
1473
- error = createValidationError("No handle provided.");
1474
- return {
1475
- success: false,
1476
- error
1477
- };
1478
- }
1479
- await this.mailchannels.delete(`/tx/v1/sub-account/${encodeURIComponent(handle)}/smtp-password/${encodeURIComponent(id)}`, { onResponseError: async ({ response }) => {
1480
- error = getStatusError(response, { [400]: "Missing or invalid SMTP password ID." });
1481
- } }).catch((e) => {
1482
- error ||= getResultError(e, "Failed to delete sub-account SMTP password.");
1483
- });
1484
- return {
1485
- success: !error,
1486
- error
1487
- };
1488
- }
1489
- };
1490
- var SubAccountsLimits = class {
1491
- mailchannels;
1492
- constructor(mailchannels) {
1493
- this.mailchannels = mailchannels;
1494
- }
1495
- async get(handle) {
1496
- let error = null;
1497
- if (!handle) {
1498
- error = createValidationError("No handle provided.");
1499
- return {
1500
- data: null,
1501
- error
1502
- };
1503
- }
1504
- const response = await this.mailchannels.get(`/tx/v1/sub-account/${encodeURIComponent(handle)}/limit`, { onResponseError: async ({ response }) => {
1505
- error = getStatusError(response, { [404]: `Sub-account with handle '${handle}' not found.` });
1506
- } }).catch((e) => {
1507
- error ||= getResultError(e, "Failed to fetch sub-account limit.");
1508
- return null;
1509
- });
1510
- if (!response) return {
1511
- data: null,
1512
- error
1513
- };
1514
- return {
1515
- data: clean(response),
1516
- error: null
1517
- };
1518
- }
1519
- async set(handle, options) {
1520
- let error = null;
1521
- if (!handle) {
1522
- error = createValidationError("No handle provided.");
1523
- return {
1524
- success: false,
1525
- error
1526
- };
1527
- }
1528
- if (options.sends < 0) {
1529
- error = createValidationError("The sends value must be at least 0.");
1530
- return {
1531
- success: false,
1532
- error
1533
- };
1534
- }
1535
- await this.mailchannels.put(`/tx/v1/sub-account/${encodeURIComponent(handle)}/limit`, {
1536
- body: options,
1537
- onResponseError: async ({ response }) => {
1538
- error = getStatusError(response, {
1539
- [400]: "Bad Request.",
1540
- [404]: `Sub-account with handle '${handle}' not found.`
1541
- });
1542
- }
1543
- }).catch((e) => {
1544
- error ||= getResultError(e, "Failed to set sub-account limit.");
1545
- });
1546
- return {
1547
- success: !error,
1548
- error
1549
- };
1550
- }
1551
- async delete(handle) {
1552
- let error = null;
1553
- if (!handle) {
1554
- error = createValidationError("No handle provided.");
1555
- return {
1556
- success: false,
1557
- error
1558
- };
1559
- }
1560
- await this.mailchannels.delete(`/tx/v1/sub-account/${encodeURIComponent(handle)}/limit`, { onResponseError: async ({ response }) => {
1561
- error = getStatusError(response, { [404]: `Sub-account with handle '${handle}' not found.` });
1562
- } }).catch((e) => {
1563
- error ||= getResultError(e, "Failed to delete sub-account limit.");
1564
- });
1565
- return {
1566
- success: !error,
1567
- error
1568
- };
1569
- }
1570
- };
1571
- var SubAccounts = class SubAccounts {
1572
- mailchannels;
1573
- static COMPANY_PATTERN = /^.{3,128}$/;
1574
- static HANDLE_PATTERN = /^[a-z0-9]{3,128}$/;
1575
- apiKeys;
1576
- smtpPasswords;
1577
- limits;
1578
- constructor(mailchannels) {
1579
- this.mailchannels = mailchannels;
1580
- this.apiKeys = new SubAccountsApiKeys(mailchannels);
1581
- this.smtpPasswords = new SubAccountsSmtpPasswords(mailchannels);
1582
- this.limits = new SubAccountsLimits(mailchannels);
1583
- }
1584
- async create(companyName, handle) {
1585
- let error = null;
1586
- if (!SubAccounts.COMPANY_PATTERN.test(companyName)) {
1587
- error = createValidationError("Invalid company name. Company name must be between 3 and 128 characters.");
1588
- return {
1589
- data: null,
1590
- error
1591
- };
1592
- }
1593
- if (handle) {
1594
- if (!SubAccounts.HANDLE_PATTERN.test(handle)) {
1595
- error = createValidationError("Invalid handle. Sub-account handle must be between 3 and 128 characters and contain only lowercase letters and numbers.");
1596
- return {
1597
- data: null,
1598
- error
1599
- };
1600
- }
1601
- }
1602
- const response = await this.mailchannels.post("/tx/v1/sub-account", {
1603
- body: {
1604
- company_name: companyName,
1605
- handle
1606
- },
1607
- onResponseError: async ({ response }) => {
1608
- error = getStatusError(response, {
1609
- [403]: "The parent account does not have permission to create sub-accounts.",
1610
- [409]: `Sub-account with handle '${handle}' already exists.`
1611
- });
1612
- }
1613
- }).catch((e) => {
1614
- error ||= getResultError(e, "Failed to create sub-account.");
1615
- return null;
1616
- });
1617
- if (!response) return {
1618
- data: null,
1619
- error
1620
- };
1621
- return {
1622
- data: clean({
1623
- companyName: response.company_name,
1624
- enabled: response.enabled,
1625
- handle: response.handle
1626
- }),
1627
- error: null
1628
- };
1629
- }
1630
- async list(options) {
1631
- let error = null;
1632
- error = validatePagination({
1633
- ...options,
1634
- max: 1e3
1635
- });
1636
- if (error) return {
1637
- data: null,
1638
- error
1639
- };
1640
- const response = await this.mailchannels.get("/tx/v1/sub-account", {
1641
- query: options,
1642
- onResponseError: async ({ response }) => {
1643
- error = getStatusError(response);
1644
- }
1645
- }).catch((e) => {
1646
- error ||= getResultError(e, "Failed to fetch sub-accounts.");
1647
- return null;
1648
- });
1649
- if (!response) return {
1650
- data: null,
1651
- error
1652
- };
1653
- return {
1654
- data: clean(response.map((account) => ({
1655
- companyName: account.company_name,
1656
- enabled: account.enabled,
1657
- handle: account.handle
1658
- }))),
1659
- error: null
1660
- };
1661
- }
1662
- async delete(handle) {
1663
- let error = null;
1664
- if (!handle) {
1665
- error = createValidationError("No handle provided.");
1666
- return {
1667
- success: false,
1668
- error
1669
- };
1670
- }
1671
- await this.mailchannels.delete(`/tx/v1/sub-account/${encodeURIComponent(handle)}`, { onResponseError: async ({ response }) => {
1672
- error = getStatusError(response);
1673
- } }).catch((e) => {
1674
- error ||= getResultError(e, "Failed to delete sub-account.");
1675
- });
1676
- return {
1677
- success: !error,
1678
- error
1679
- };
1680
- }
1681
- async suspend(handle) {
1682
- let error = null;
1683
- if (!handle) {
1684
- error = createValidationError("No handle provided.");
1685
- return {
1686
- success: false,
1687
- error
1688
- };
1689
- }
1690
- await this.mailchannels.post(`/tx/v1/sub-account/${encodeURIComponent(handle)}/suspend`, { onResponseError: async ({ response }) => {
1691
- error = getStatusError(response, { [404]: `The specified sub-account '${handle}' does not exist.` });
1692
- } }).catch((e) => {
1693
- error ||= getResultError(e, "Failed to suspend sub-account.");
1694
- });
1695
- return {
1696
- success: !error,
1697
- error
1698
- };
1699
- }
1700
- async activate(handle) {
1701
- let error = null;
1702
- if (!handle) {
1703
- error = createValidationError("No handle provided.");
1704
- return {
1705
- success: false,
1706
- error
1707
- };
1708
- }
1709
- await this.mailchannels.post(`/tx/v1/sub-account/${encodeURIComponent(handle)}/activate`, { onResponseError: async ({ response }) => {
1710
- error = getStatusError(response, {
1711
- [403]: "The parent account does not have permission to activate the sub-account.",
1712
- [404]: `The specified sub-account '${handle}' does not exist.`
1713
- });
1714
- } }).catch((e) => {
1715
- error ||= getResultError(e, "Failed to activate sub-account.");
1716
- });
1717
- return {
1718
- success: !error,
1719
- error
1720
- };
1721
- }
1722
- async getUsage(handle) {
1723
- let error = null;
1724
- if (!handle) {
1725
- error = createValidationError("No handle provided.");
1726
- return {
1727
- data: null,
1728
- error
1729
- };
1730
- }
1731
- const response = await this.mailchannels.get(`/tx/v1/sub-account/${encodeURIComponent(handle)}/usage`, { onResponseError: async ({ response }) => {
1732
- error = getStatusError(response, { [404]: `Sub-account with handle '${handle}' not found.` });
1733
- } }).catch((e) => {
1734
- error ||= getResultError(e, "Failed to fetch sub-account usage.");
1735
- return null;
1736
- });
1737
- if (!response) return {
1738
- data: null,
1739
- error
1740
- };
1741
- return {
1742
- data: clean({
1743
- endDate: response.period_end_date,
1744
- startDate: response.period_start_date,
1745
- total: response.total_usage,
1746
- monthlyLimit: response.monthly_limit
1747
- }),
1748
- error: null
1749
- };
1750
- }
1751
- createApiKey(...args) {
1752
- return this.apiKeys.create(...args);
1753
- }
1754
- listApiKeys(...args) {
1755
- return this.apiKeys.list(...args);
1756
- }
1757
- deleteApiKey(...args) {
1758
- return this.apiKeys.delete(...args);
1759
- }
1760
- createSmtpPassword(...args) {
1761
- return this.smtpPasswords.create(...args);
1762
- }
1763
- listSmtpPasswords(...args) {
1764
- return this.smtpPasswords.list(...args);
1765
- }
1766
- deleteSmtpPassword(...args) {
1767
- return this.smtpPasswords.delete(...args);
1768
- }
1769
- setLimit(...args) {
1770
- return this.limits.set(...args);
1771
- }
1772
- getLimit(...args) {
1773
- return this.limits.get(...args);
1774
- }
1775
- deleteLimit(...args) {
1776
- return this.limits.delete(...args);
1777
- }
1778
- };
1779
- const mapBucket = (bucket) => {
1780
- return {
1781
- count: bucket.count,
1782
- periodStart: bucket.period_start
1783
- };
1784
- };
1785
- var Metrics = class {
1786
- mailchannels;
1787
- constructor(mailchannels) {
1788
- this.mailchannels = mailchannels;
1789
- }
1790
- async engagement(options) {
1791
- let error = null;
1792
- const { dates, error: dateError } = parseDateInputs({
1793
- startTime: options?.startTime,
1794
- endTime: options?.endTime
1795
- });
1796
- if (!dates || dateError) return {
1797
- data: null,
1798
- error: dateError
1799
- };
1800
- const response = await this.mailchannels.get("/tx/v1/metrics/engagement", {
1801
- query: {
1802
- start_time: dates.startTime,
1803
- end_time: dates.endTime,
1804
- campaign_id: options?.campaignId,
1805
- interval: options?.interval
1806
- },
1807
- onResponseError: async ({ response }) => {
1808
- error = getStatusError(response, { [400]: "Bad Request." });
1809
- }
1810
- }).catch((e) => {
1811
- error ||= getResultError(e, "Failed to fetch engagement metrics.");
1812
- return null;
1813
- });
1814
- if (!response) return {
1815
- data: null,
1816
- error
1817
- };
1818
- return {
1819
- data: clean({
1820
- buckets: {
1821
- click: response.buckets.click.map(mapBucket),
1822
- clickTrackingDelivered: response.buckets.click_tracking_delivered.map(mapBucket),
1823
- open: response.buckets.open.map(mapBucket),
1824
- openTrackingDelivered: response.buckets.open_tracking_delivered.map(mapBucket),
1825
- uniqueClick: response.buckets.unique_click?.map(mapBucket),
1826
- uniqueClickTrackingDelivered: response.buckets.unique_click_tracking_delivered?.map(mapBucket),
1827
- uniqueOpen: response.buckets.unique_open?.map(mapBucket),
1828
- uniqueOpenTrackingDelivered: response.buckets.unique_open_tracking_delivered?.map(mapBucket)
1829
- },
1830
- click: response.click,
1831
- clickTrackingDelivered: response.click_tracking_delivered,
1832
- endTime: response.end_time,
1833
- open: response.open,
1834
- openTrackingDelivered: response.open_tracking_delivered,
1835
- startTime: response.start_time,
1836
- uniqueClick: response.unique_click,
1837
- uniqueClickTrackingDelivered: response.unique_click_tracking_delivered,
1838
- uniqueOpen: response.unique_open,
1839
- uniqueOpenTrackingDelivered: response.unique_open_tracking_delivered
1840
- }),
1841
- error: null
1842
- };
1843
- }
1844
- async performance(options) {
1845
- let error = null;
1846
- const { dates, error: dateError } = parseDateInputs({
1847
- startTime: options?.startTime,
1848
- endTime: options?.endTime
1849
- });
1850
- if (!dates || dateError) return {
1851
- data: null,
1852
- error: dateError
1853
- };
1854
- const response = await this.mailchannels.get("/tx/v1/metrics/performance", {
1855
- query: {
1856
- start_time: dates.startTime,
1857
- end_time: dates.endTime,
1858
- campaign_id: options?.campaignId,
1859
- interval: options?.interval
1860
- },
1861
- onResponseError: async ({ response }) => {
1862
- error = getStatusError(response, { [400]: "Bad Request." });
1863
- }
1864
- }).catch((e) => {
1865
- error ||= getResultError(e, "Failed to fetch performance metrics.");
1866
- return null;
1867
- });
1868
- if (!response) return {
1869
- data: null,
1870
- error
1871
- };
1872
- return {
1873
- data: clean({
1874
- bounced: response.bounced,
1875
- complained: response.complained,
1876
- buckets: {
1877
- bounced: response.buckets.bounced.map(mapBucket),
1878
- complained: response.buckets.complained.map(mapBucket),
1879
- delivered: response.buckets.delivered.map(mapBucket),
1880
- processed: response.buckets.processed.map(mapBucket)
1881
- },
1882
- delivered: response.delivered,
1883
- endTime: response.end_time,
1884
- processed: response.processed,
1885
- startTime: response.start_time
1886
- }),
1887
- error: null
1888
- };
1889
- }
1890
- async recipientBehaviour(options) {
1891
- let error = null;
1892
- const { dates, error: dateError } = parseDateInputs({
1893
- startTime: options?.startTime,
1894
- endTime: options?.endTime
1895
- });
1896
- if (!dates || dateError) return {
1897
- data: null,
1898
- error: dateError
1899
- };
1900
- const response = await this.mailchannels.get("/tx/v1/metrics/recipient-behaviour", {
1901
- query: {
1902
- start_time: dates.startTime,
1903
- end_time: dates.endTime,
1904
- campaign_id: options?.campaignId,
1905
- interval: options?.interval
1906
- },
1907
- onResponseError: async ({ response }) => {
1908
- error = getStatusError(response, { [400]: "Bad Request." });
1909
- }
1910
- }).catch((e) => {
1911
- error ||= getResultError(e, "Failed to fetch recipient behaviour metrics.");
1912
- return null;
1913
- });
1914
- if (!response) return {
1915
- data: null,
1916
- error
1917
- };
1918
- return {
1919
- data: clean({
1920
- buckets: {
1921
- unsubscribeDelivered: response.buckets.unsubscribe_delivered.map(mapBucket),
1922
- unsubscribed: response.buckets.unsubscribed.map(mapBucket)
1923
- },
1924
- endTime: response.end_time,
1925
- startTime: response.start_time,
1926
- unsubscribeDelivered: response.unsubscribe_delivered,
1927
- unsubscribed: response.unsubscribed
1928
- }),
1929
- error: null
1930
- };
1931
- }
1932
- async volume(options) {
1933
- let error = null;
1934
- const { dates, error: dateError } = parseDateInputs({
1935
- startTime: options?.startTime,
1936
- endTime: options?.endTime
1937
- });
1938
- if (!dates || dateError) return {
1939
- data: null,
1940
- error: dateError
1941
- };
1942
- const response = await this.mailchannels.get("/tx/v1/metrics/volume", {
1943
- query: {
1944
- start_time: dates.startTime,
1945
- end_time: dates.endTime,
1946
- campaign_id: options?.campaignId,
1947
- interval: options?.interval
1948
- },
1949
- onResponseError: async ({ response }) => {
1950
- error = getStatusError(response, { [400]: "Bad Request." });
1951
- }
1952
- }).catch((e) => {
1953
- error ||= getResultError(e, "Failed to fetch volume metrics.");
1954
- return null;
1955
- });
1956
- if (!response) return {
1957
- data: null,
1958
- error
1959
- };
1960
- return {
1961
- data: clean({
1962
- buckets: {
1963
- delivered: response.buckets.delivered.map(mapBucket),
1964
- dropped: response.buckets.dropped.map(mapBucket),
1965
- processed: response.buckets.processed.map(mapBucket)
1966
- },
1967
- delivered: response.delivered,
1968
- dropped: response.dropped,
1969
- endTime: response.end_time,
1970
- processed: response.processed,
1971
- startTime: response.start_time
1972
- }),
1973
- error: null
1974
- };
1975
- }
1976
- async usage() {
1977
- let error = null;
1978
- const response = await this.mailchannels.get("/tx/v1/usage", { onResponseError: async ({ response }) => {
1979
- error = getStatusError(response);
1980
- } }).catch((e) => {
1981
- error ||= getResultError(e, "Failed to fetch usage metrics.");
1982
- return null;
1983
- });
1984
- if (!response) return {
1985
- data: null,
1986
- error
1987
- };
1988
- return {
1989
- data: clean({
1990
- endDate: response.period_end_date,
1991
- startDate: response.period_start_date,
1992
- total: response.total_usage,
1993
- monthlyLimit: response.monthly_limit
1994
- }),
1995
- error: null
1996
- };
1997
- }
1998
- async senders(type, options) {
1999
- let error = null;
2000
- const { dates, error: dateError } = parseDateInputs({
2001
- startTime: options?.startTime,
2002
- endTime: options?.endTime
2003
- });
2004
- if (!dates || dateError) return {
2005
- data: null,
2006
- error: dateError
2007
- };
2008
- error = validatePagination({
2009
- ...options,
2010
- max: 1e3
2011
- });
2012
- if (error) return {
2013
- data: null,
2014
- error
2015
- };
2016
- const response = await this.mailchannels.get(`/tx/v1/metrics/senders/${encodeURIComponent(type)}`, {
2017
- query: {
2018
- start_time: dates.startTime,
2019
- end_time: dates.endTime,
2020
- limit: options?.limit,
2021
- offset: options?.offset,
2022
- sort_order: options?.sortOrder
2023
- },
2024
- onResponseError: async ({ response }) => {
2025
- error = getStatusError(response, { [400]: "Bad Request." });
2026
- }
2027
- }).catch((e) => {
2028
- error ||= getResultError(e, "Failed to fetch senders metrics.");
2029
- return null;
2030
- });
2031
- if (!response) return {
2032
- data: null,
2033
- error
2034
- };
2035
- return {
2036
- data: clean({
2037
- endTime: response.end_time,
2038
- limit: response.limit,
2039
- offset: response.offset,
2040
- senders: response.senders,
2041
- startTime: response.start_time,
2042
- total: response.total
2043
- }),
2044
- error: null
2045
- };
2046
- }
2047
- };
2048
- var Suppressions = class {
2049
- mailchannels;
2050
- constructor(mailchannels) {
2051
- this.mailchannels = mailchannels;
2052
- }
2053
- async create(entriesOrOptions, options) {
2054
- let error = null;
2055
- const entries = Array.isArray(entriesOrOptions) ? entriesOrOptions : entriesOrOptions.entries;
2056
- const createOptions = Array.isArray(entriesOrOptions) ? options : entriesOrOptions;
2057
- if (entries.length > 1e3) {
2058
- error = createValidationError("The number of suppression entries must not exceed 1000.");
2059
- return {
2060
- success: false,
2061
- error
2062
- };
2063
- }
2064
- const payload = {
2065
- add_to_sub_accounts: createOptions?.addToSubAccounts,
2066
- suppression_entries: entries.map((entry) => ({
2067
- notes: entry.notes,
2068
- recipient: entry.recipient,
2069
- suppression_types: Array.from(new Set(entry.types || ["non-transactional"]))
2070
- }))
2071
- };
2072
- await this.mailchannels.post("/tx/v1/suppression-list", {
2073
- body: payload,
2074
- onResponseError: async ({ response }) => {
2075
- error = getStatusError(response, {
2076
- [400]: "Bad Request.",
2077
- [409]: "Conflict. One or more suppression entries in the request already exist and cannot be created again.",
2078
- [413]: "Payload too large. The request exceeds the maximum allowed total of 1000 suppression entries for the parent account and/or its sub-accounts."
2079
- });
2080
- }
2081
- }).catch((e) => {
2082
- error ||= getResultError(e, "Failed to create suppression entries.");
2083
- });
2084
- return {
2085
- success: !error,
2086
- error
2087
- };
2088
- }
2089
- async delete(recipient, source) {
2090
- let error = null;
2091
- await this.mailchannels.delete(`/tx/v1/suppression-list/recipients/${encodeURIComponent(recipient)}`, {
2092
- query: { source },
2093
- onResponseError: async ({ response }) => {
2094
- error = getStatusError(response, { [400]: "Bad Request." });
2095
- }
2096
- }).catch((e) => {
2097
- error ||= getResultError(e, "Failed to delete suppression entry.");
2098
- });
2099
- return {
2100
- success: !error,
2101
- error
2102
- };
2103
- }
2104
- async list(options) {
2105
- let error = null;
2106
- if (options?.recipient && options.recipient.length > 255) {
2107
- error = createValidationError("The recipient must not exceed 255 characters.");
2108
- return {
2109
- data: null,
2110
- error
2111
- };
2112
- }
2113
- const { dates, error: dateError } = parseDateInputs({
2114
- createdBefore: options?.createdBefore,
2115
- createdAfter: options?.createdAfter
2116
- });
2117
- if (!dates || dateError) return {
2118
- data: null,
2119
- error: dateError
2120
- };
2121
- error = validatePagination({
2122
- ...options,
2123
- max: 1e3
2124
- });
2125
- if (error) return {
2126
- data: null,
2127
- error
2128
- };
2129
- const payload = {
2130
- recipient: options?.recipient,
2131
- source: options?.source,
2132
- created_before: dates.createdBefore,
2133
- created_after: dates.createdAfter,
2134
- limit: options?.limit,
2135
- offset: options?.offset
2136
- };
2137
- const response = await this.mailchannels.get("/tx/v1/suppression-list", {
2138
- query: payload,
2139
- onResponseError: async ({ response }) => {
2140
- error = getStatusError(response, { [400]: "Bad Request." });
2141
- }
2142
- }).catch((e) => {
2143
- error ||= getResultError(e, "Failed to fetch suppression entries.");
2144
- return null;
2145
- });
2146
- if (!response) return {
2147
- data: null,
2148
- error
2149
- };
2150
- return {
2151
- data: clean(response.suppression_list.map((entry) => ({
2152
- createdAt: entry.created_at,
2153
- notes: entry.notes,
2154
- recipient: entry.recipient,
2155
- sender: entry.sender,
2156
- source: entry.source,
2157
- types: entry.suppression_types
2158
- }))),
2159
- error: null
2160
- };
2161
- }
2162
- };
2163
- const base64Content = (data) => {
2164
- if (data instanceof ArrayBuffer) return Buffer.from(data).toString("base64");
2165
- return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("base64");
2166
- };
2167
- const guessContentType = (filename) => {
2168
- return mime.getType(filename) || void 0;
2169
- };
2170
- var Attachment = class Attachment {
2171
- static fromBytes(data, options) {
2172
- const { filename, type, contentId } = options;
2173
- return {
2174
- content: base64Content(data),
2175
- filename: decodeURIComponent(filename) || "attachment",
2176
- type: type || guessContentType(filename),
2177
- contentId
2178
- };
2179
- }
2180
- static async fromBlob(blob, options) {
2181
- if (!(blob instanceof Blob)) throw new Error("Unable to create attachment: expected a Blob");
2182
- const bytes = await blob.arrayBuffer();
2183
- return Attachment.fromBytes(bytes, {
2184
- type: blob.type,
2185
- ...options
2186
- });
2187
- }
2188
- };
2189
- var MailChannels = class extends MailChannelsClient {
2190
- emails = new Emails(this);
2191
- domains = new Domains(this);
2192
- webhooks = new Webhooks(this);
2193
- subAccounts = new SubAccounts(this);
2194
- metrics = new Metrics(this);
2195
- suppressions = new Suppressions(this);
2196
- constructor(key, options) {
2197
- super(key, options);
2198
- }
2199
- };
1
+ import { Attachment, Domains, Emails, MailChannels, MailChannelsClient, Metrics, SubAccounts, Suppressions, Webhooks } from "./_chunks/mailchannels.mjs";
2200
2
  export { Attachment, Domains, Emails, MailChannels, MailChannelsClient, Metrics, SubAccounts, Suppressions, Webhooks };