mailchannels-sdk 1.5.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ import { subtle } from "node:crypto";
3
3
  import { Buffer } from "node:buffer";
4
4
  import mime from "mime";
5
5
  var name = "mailchannels-sdk";
6
- var version = "1.5.2";
6
+ var version = "1.6.0";
7
7
  var MailChannelsClient = class MailChannelsClient {
8
8
  static DEFAULT_BASE_URL = "https://api.mailchannels.net";
9
9
  static DEFAULT_TIMEOUT = 12e4;
@@ -114,8 +114,14 @@ const createValidationError = (message) => {
114
114
  };
115
115
  const validatePagination = (pagination = {}) => {
116
116
  const { limit, offset, max } = pagination;
117
- 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."));
118
- if (typeof offset === "number" && offset < 0) return createValidationError("Offset must be greater than or equal to 0.");
117
+ if (limit !== void 0) {
118
+ if (!Number.isInteger(limit)) return createValidationError("The limit value must be an integer.");
119
+ if (limit < 1 || max != null && limit > max) return createValidationError("The limit value " + (max != null ? `must be between 1 and ${max}.` : "is invalid. Only positive values are allowed."));
120
+ }
121
+ if (offset !== void 0) {
122
+ if (!Number.isInteger(offset)) return createValidationError("The offset value must be an integer.");
123
+ if (offset < 0) return createValidationError("The offset value must be greater than or equal to 0.");
124
+ }
119
125
  return null;
120
126
  };
121
127
  const CUSTOM_TRACKING_NAME_PATTERN = /^[a-z0-9-]+$/;
@@ -458,8 +464,17 @@ const mapDkimKey = (key) => ({
458
464
  status: key.status,
459
465
  statusModifiedAt: key.status_modified_at
460
466
  });
461
- var DomainsDkim = class {
467
+ const quoteValues = (values) => {
468
+ return Array.from(values).map((value) => `'${value}'`).join(", ");
469
+ };
470
+ var DomainsDkim = class DomainsDkim {
462
471
  mailchannels;
472
+ static UPDATE_STATUS_VALUES = /* @__PURE__ */ new Set([
473
+ "retired",
474
+ "revoked",
475
+ "rotated"
476
+ ]);
477
+ static STATUS_VALUES = /* @__PURE__ */ new Set(["active", ...DomainsDkim.UPDATE_STATUS_VALUES]);
463
478
  constructor(mailchannels) {
464
479
  this.mailchannels = mailchannels;
465
480
  }
@@ -518,6 +533,13 @@ var DomainsDkim = class {
518
533
  error
519
534
  };
520
535
  }
536
+ if (options?.status !== void 0 && !DomainsDkim.STATUS_VALUES.has(options.status)) {
537
+ error = createValidationError(`Status must be one of ${quoteValues(DomainsDkim.STATUS_VALUES)}.`);
538
+ return {
539
+ data: null,
540
+ error
541
+ };
542
+ }
521
543
  error = validatePagination({
522
544
  ...options,
523
545
  max: 100
@@ -567,6 +589,13 @@ var DomainsDkim = class {
567
589
  error
568
590
  };
569
591
  }
592
+ if (options?.status !== void 0 && !DomainsDkim.UPDATE_STATUS_VALUES.has(options.status)) {
593
+ error = createValidationError(`Status must be one of ${quoteValues(DomainsDkim.UPDATE_STATUS_VALUES)}.`);
594
+ return {
595
+ success: false,
596
+ error
597
+ };
598
+ }
570
599
  const payload = { status: options.status };
571
600
  await this.mailchannels.patch(`/tx/v1/domains/${encodeURIComponent(domain)}/dkim-keys/${encodeURIComponent(options.selector)}`, {
572
601
  body: payload,
@@ -760,7 +789,7 @@ var DomainsCustomTracking = class DomainsCustomTracking {
760
789
  };
761
790
  }
762
791
  if (!scope || !DomainsCustomTracking.SCOPE_VALUES.has(scope)) {
763
- error = createValidationError("Scope must be one of 'click', 'open', or 'unsubscribe'.");
792
+ error = createValidationError(`Scope must be one of ${quoteValues(DomainsCustomTracking.SCOPE_VALUES)}.`);
764
793
  return {
765
794
  data: null,
766
795
  error
@@ -1036,6 +1065,14 @@ const parseDateInputs = (options) => {
1036
1065
  };
1037
1066
  var Webhooks = class Webhooks {
1038
1067
  mailchannels;
1068
+ static STATUS_VALUES = /* @__PURE__ */ new Set([
1069
+ "1xx",
1070
+ "2xx",
1071
+ "3xx",
1072
+ "4xx",
1073
+ "5xx",
1074
+ "no_response"
1075
+ ]);
1039
1076
  constructor(mailchannels) {
1040
1077
  this.mailchannels = mailchannels;
1041
1078
  }
@@ -1207,14 +1244,27 @@ var Webhooks = class Webhooks {
1207
1244
  data: null,
1208
1245
  error
1209
1246
  };
1210
- if (options?.statuses && options.statuses.length > 6) return {
1211
- data: null,
1212
- error: createValidationError("A maximum of 6 status filters can be provided.")
1213
- };
1214
- if (options?.statuses && new Set(options.statuses).size !== options.statuses.length) return {
1215
- data: null,
1216
- error: createValidationError("Status filters must be unique.")
1217
- };
1247
+ if (options?.statuses !== void 0) {
1248
+ if (!Array.isArray(options.statuses)) return {
1249
+ data: null,
1250
+ error: createValidationError("Status filters must be an array.")
1251
+ };
1252
+ if (options.statuses.length > 6) return {
1253
+ data: null,
1254
+ error: createValidationError("A maximum of 6 status filters can be provided.")
1255
+ };
1256
+ if (new Set(options.statuses).size !== options.statuses.length) return {
1257
+ data: null,
1258
+ error: createValidationError("Status filters must be unique.")
1259
+ };
1260
+ if (!options.statuses.every((status) => Webhooks.STATUS_VALUES.has(status))) {
1261
+ const validValues = quoteValues(Webhooks.STATUS_VALUES);
1262
+ return {
1263
+ data: null,
1264
+ error: createValidationError(`Invalid status filter provided. Valid values are: ${validValues}.`)
1265
+ };
1266
+ }
1267
+ }
1218
1268
  const { dates, error: dateError } = parseDateInputs({
1219
1269
  createdAfter: options?.createdAfter,
1220
1270
  createdBefore: options?.createdBefore
@@ -1240,7 +1290,7 @@ var Webhooks = class Webhooks {
1240
1290
  query: {
1241
1291
  created_after: dates.createdAfter,
1242
1292
  created_before: dates.createdBefore,
1243
- statuses: options?.statuses,
1293
+ statuses: options?.statuses?.join(","),
1244
1294
  webhook: options?.webhook,
1245
1295
  limit: options?.limit,
1246
1296
  offset: options?.offset
@@ -2030,8 +2080,16 @@ var Metrics = class {
2030
2080
  };
2031
2081
  }
2032
2082
  };
2033
- var Suppressions = class {
2083
+ var Suppressions = class Suppressions {
2034
2084
  mailchannels;
2085
+ static SOURCE_VALUES = /* @__PURE__ */ new Set([
2086
+ "api",
2087
+ "unsubscribe_link",
2088
+ "list_unsubscribe",
2089
+ "hard_bounce",
2090
+ "spam_complaint"
2091
+ ]);
2092
+ static DELETE_SOURCE_VALUES = /* @__PURE__ */ new Set([...Suppressions.SOURCE_VALUES, "all"]);
2035
2093
  constructor(mailchannels) {
2036
2094
  this.mailchannels = mailchannels;
2037
2095
  }
@@ -2072,6 +2130,13 @@ var Suppressions = class {
2072
2130
  }
2073
2131
  async delete(recipient, source) {
2074
2132
  let error = null;
2133
+ if (source !== void 0 && !Suppressions.DELETE_SOURCE_VALUES.has(source)) {
2134
+ error = createValidationError(`Source must be one of ${quoteValues(Suppressions.DELETE_SOURCE_VALUES)}.`);
2135
+ return {
2136
+ success: false,
2137
+ error
2138
+ };
2139
+ }
2075
2140
  await this.mailchannels.delete(`/tx/v1/suppression-list/recipients/${encodeURIComponent(recipient)}`, {
2076
2141
  query: { source },
2077
2142
  onResponseError: async ({ response }) => {
@@ -2094,6 +2159,13 @@ var Suppressions = class {
2094
2159
  error
2095
2160
  };
2096
2161
  }
2162
+ if (options?.source !== void 0 && !Suppressions.SOURCE_VALUES.has(options.source)) {
2163
+ error = createValidationError(`Source must be one of ${quoteValues(Suppressions.SOURCE_VALUES)}.`);
2164
+ return {
2165
+ data: null,
2166
+ error
2167
+ };
2168
+ }
2097
2169
  const { dates, error: dateError } = parseDateInputs({
2098
2170
  createdBefore: options?.createdBefore,
2099
2171
  createdAfter: options?.createdAfter
@@ -1,9 +1,9 @@
1
1
  import { EmailsQueueResponse, EmailsSendOptions, EmailsSendResponse, MailChannelsClientOptions } from "../../_chunks/mailchannels.mjs";
2
- import { Transport as Transport$1 } from "nodemailer";
2
+ import { Transport, TransportOptions, Transporter } from "nodemailer";
3
3
  import MimeNode from "nodemailer/lib/mime-node";
4
- type MailChannelsTransportSendOptions = Pick<EmailsSendOptions, "campaignId" | "tracking" | "transactional" | "unsubscribe">;
5
- type MailChannelsTransportSendMode = "sync" | "async";
6
- interface MailChannelsTransportOptions<T extends MailChannelsTransportSendMode = "async"> extends MailChannelsClientOptions {
4
+ export type MailChannelsTransportSendOptions = Pick<EmailsSendOptions, "campaignId" | "tracking" | "transactional" | "unsubscribe">;
5
+ export type MailChannelsTransportSendMode = "sync" | "async";
6
+ export interface MailChannelsTransportOptions<T extends MailChannelsTransportSendMode = "async"> extends MailChannelsClientOptions {
7
7
  /**
8
8
  * The MailChannels Email API key.
9
9
  */
@@ -14,7 +14,7 @@ interface MailChannelsTransportOptions<T extends MailChannelsTransportSendMode =
14
14
  */
15
15
  sendMode?: T;
16
16
  }
17
- interface MailChannelsTransportInfo<T extends MailChannelsTransportSendMode> {
17
+ export interface MailChannelsTransportInfo<T extends MailChannelsTransportSendMode> {
18
18
  messageId: string | null;
19
19
  accepted: string[];
20
20
  rejected: string[];
@@ -22,12 +22,14 @@ interface MailChannelsTransportInfo<T extends MailChannelsTransportSendMode> {
22
22
  response: (T extends "sync" ? EmailsSendResponse : EmailsQueueResponse) | null;
23
23
  }
24
24
  declare module "nodemailer" {
25
- function createTransport<T>(transport: Transport<T> | TransportOptions, defaults?: TransportOptions): Transporter<T, TransportOptions>;
25
+ function createTransport<T>(transport: Transport<T> | TransportOptions, defaults?: TransportOptions): Transporter<T>;
26
+ interface SendMailOptions {
27
+ mailchannels?: MailChannelsTransportSendOptions;
28
+ }
26
29
  }
27
30
  declare module "nodemailer/lib/mailer" {
28
31
  interface Options {
29
32
  mailchannels?: MailChannelsTransportSendOptions;
30
33
  }
31
34
  }
32
- declare const mailchannelsTransport: <T extends MailChannelsTransportSendMode = "async">(options: MailChannelsTransportOptions<T>) => Transport$1<MailChannelsTransportInfo<T>>;
33
- export { MailChannelsTransportInfo, MailChannelsTransportOptions, MailChannelsTransportSendMode, MailChannelsTransportSendOptions, mailchannelsTransport };
35
+ export declare const mailchannelsTransport: <T extends MailChannelsTransportSendMode = "async">(options: MailChannelsTransportOptions<T>) => Transport<MailChannelsTransportInfo<T>>;
@@ -5,9 +5,10 @@ const parseAddress = (address) => {
5
5
  if (!address) return "";
6
6
  if (Array.isArray(address)) return parseAddress(address[0]);
7
7
  if (typeof address === "string") return address;
8
+ if (!address.address) return "";
8
9
  return {
9
10
  email: address.address,
10
- name: address.name || void 0
11
+ name: address.name
11
12
  };
12
13
  };
13
14
  const parseAddresses = (addresses) => {
@@ -41,7 +42,7 @@ const parseDkim = (dkim) => {
41
42
  if ("keys" in dkim) throw new Error("Multiple DKIM signatures are not supported");
42
43
  let privateKey;
43
44
  if (typeof dkim.privateKey === "string") privateKey = dkim.privateKey;
44
- else if (dkim.privateKey?.key && dkim.privateKey?.passphrase) try {
45
+ else if (dkim.privateKey && "key" in dkim.privateKey && "passphrase" in dkim.privateKey) try {
45
46
  privateKey = createPrivateKey({
46
47
  key: dkim.privateKey.key,
47
48
  passphrase: dkim.privateKey.passphrase
@@ -62,7 +63,8 @@ const parseDkim = (dkim) => {
62
63
  const parseHeaders = (headers) => {
63
64
  if (!headers) return;
64
65
  if (Array.isArray(headers)) return headers.reduce((acc, { key, value }) => {
65
- acc[key] = value;
66
+ if (value == void 0) return acc;
67
+ acc[key] = value.toString();
66
68
  return acc;
67
69
  }, {});
68
70
  return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, Array.isArray(value) ? value.join(",") : value && typeof value === "object" && "value" in value ? String(value.value) : String(value)]));
@@ -94,7 +96,10 @@ const mailchannelsTransport = (options) => {
94
96
  messageId: null,
95
97
  accepted: [],
96
98
  rejected: [],
97
- envelope: mail.message.getEnvelope(),
99
+ envelope: mail.message?.getEnvelope() ?? {
100
+ from: false,
101
+ to: []
102
+ },
98
103
  response: null
99
104
  };
100
105
  mail.normalize(async (err, data) => {
@@ -1,4 +1,4 @@
1
- declare const createSimulator: (options?: {
1
+ export declare const createSimulator: (options?: {
2
2
  host?: string;
3
3
  port?: number;
4
4
  silent?: boolean;
@@ -16,5 +16,4 @@ declare const createSimulator: (options?: {
16
16
  host?: string;
17
17
  port?: number;
18
18
  }): Promise<string>;
19
- };
20
- export { createSimulator };
19
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mailchannels-sdk",
3
- "version": "1.5.2",
3
+ "version": "1.6.0",
4
4
  "description": "Node.js SDK to integrate MailChannels Email API into your JavaScript or TypeScript server-side applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -52,19 +52,18 @@
52
52
  "devDependencies": {
53
53
  "@stylistic/eslint-plugin": "^5.10.0",
54
54
  "@types/markdown-it": "^14.2.0",
55
- "@types/node": "^26.4.0",
56
- "@types/nodemailer": "^8.0.1",
57
- "@vitest/coverage-v8": "^4.1.11",
55
+ "@types/node": "^26.5.0",
56
+ "@vitest/coverage-v8": "^5.0.0",
58
57
  "changelogen": "^0.6.2",
59
- "nodemailer": "^9.0.6",
60
- "obuild": "^0.4.38",
61
- "oxlint": "^1.80.0",
58
+ "nodemailer": "^10.0.1",
59
+ "obuild": "^0.4.39",
60
+ "oxlint": "^1.82.0",
62
61
  "scule": "^1.3.0",
63
62
  "typescript": "^6.0.3",
64
- "vitepress": "^2.0.0-alpha.19",
63
+ "vitepress": "^2.0.0-alpha.20",
65
64
  "vitepress-plugin-group-icons": "^1.7.6",
66
65
  "vitepress-plugin-llms": "^1.13.5",
67
- "vitest": "^4.1.11"
66
+ "vitest": "^5.0.0"
68
67
  },
69
68
  "engines": {
70
69
  "node": ">=20"
@@ -72,7 +71,7 @@
72
71
  "scripts": {
73
72
  "build": "obuild",
74
73
  "parity:fixtures": "node scripts/generate-parity-fixtures.ts",
75
- "release": "pnpm lint && pnpm test && pnpm build && changelogen --bump",
74
+ "release": "pnpm lint && pnpm test && pnpm build && changelogen --bump && node scripts/update-examples-sdk-version.ts",
76
75
  "cli": "pnpx jiti src/cli/index.ts",
77
76
  "simulate": "pnpx jiti src/cli/index.ts simulate",
78
77
  "lint": "oxlint",