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
@@ -0,0 +1,33 @@
1
+ import { EmailsQueueResponse, EmailsSendOptions, EmailsSendResponse, MailChannelsClientOptions } from "../../_chunks/mailchannels.mjs";
2
+ import { Transport as Transport$1 } from "nodemailer";
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 {
7
+ /**
8
+ * The MailChannels Email API key.
9
+ */
10
+ apiKey: string;
11
+ /**
12
+ * The mode in which emails are sent.
13
+ * @default "async"
14
+ */
15
+ sendMode?: T;
16
+ }
17
+ interface MailChannelsTransportInfo<T extends MailChannelsTransportSendMode> {
18
+ messageId: string | null;
19
+ accepted: string[];
20
+ rejected: string[];
21
+ envelope: MimeNode.Envelope;
22
+ response: (T extends "sync" ? EmailsSendResponse : EmailsQueueResponse) | null;
23
+ }
24
+ declare module "nodemailer" {
25
+ function createTransport<T>(transport: Transport<T> | TransportOptions, defaults?: TransportOptions): Transporter<T, TransportOptions>;
26
+ }
27
+ declare module "nodemailer/lib/mailer" {
28
+ interface Options {
29
+ mailchannels?: MailChannelsTransportSendOptions;
30
+ }
31
+ }
32
+ declare const mailchannelsTransport: <T extends MailChannelsTransportSendMode = "async">(options: MailChannelsTransportOptions<T>) => Transport$1<MailChannelsTransportInfo<T>>;
33
+ export { MailChannelsTransportInfo, MailChannelsTransportOptions, MailChannelsTransportSendMode, MailChannelsTransportSendOptions, mailchannelsTransport };
@@ -0,0 +1,145 @@
1
+ import { Emails, MailChannelsClient, version } from "../../_chunks/mailchannels.mjs";
2
+ import { createPrivateKey } from "node:crypto";
3
+ import { Buffer } from "node:buffer";
4
+ const parseAddress = (address) => {
5
+ if (!address) return "";
6
+ if (Array.isArray(address)) return parseAddress(address[0]);
7
+ if (typeof address === "string") return address;
8
+ return {
9
+ email: address.address,
10
+ name: address.name || void 0
11
+ };
12
+ };
13
+ const parseAddresses = (addresses) => {
14
+ if (!addresses) return [];
15
+ if (typeof addresses === "string") return addresses.split(",").map((s) => s.trim()).filter(Boolean);
16
+ if (Array.isArray(addresses)) return addresses.map((address) => parseAddress(address));
17
+ if (typeof addresses === "object" && addresses.address) return [{
18
+ email: addresses.address,
19
+ name: addresses.name || void 0
20
+ }];
21
+ return [];
22
+ };
23
+ const parseAttachments = (attachments) => {
24
+ if (!attachments) return;
25
+ return attachments.map((attachment) => {
26
+ if (!attachment.filename || attachment.content === void 0) throw new Error("Attachment is missing filename or content");
27
+ if (typeof attachment.content === "string") {
28
+ const encoding = attachment.encoding || "utf-8";
29
+ return {
30
+ content: encoding === "base64" ? attachment.content : Buffer.from(attachment.content, encoding).toString("base64"),
31
+ filename: decodeURIComponent(attachment.filename),
32
+ type: attachment.contentType,
33
+ contentId: attachment.cid
34
+ };
35
+ }
36
+ throw new Error("Attachment content must be a string or Buffer");
37
+ });
38
+ };
39
+ const parseDkim = (dkim) => {
40
+ if (!dkim) return;
41
+ if ("keys" in dkim) throw new Error("Multiple DKIM signatures are not supported");
42
+ let privateKey;
43
+ if (typeof dkim.privateKey === "string") privateKey = dkim.privateKey;
44
+ else if (dkim.privateKey?.key && dkim.privateKey?.passphrase) try {
45
+ privateKey = createPrivateKey({
46
+ key: dkim.privateKey.key,
47
+ passphrase: dkim.privateKey.passphrase
48
+ }).export({
49
+ format: "pem",
50
+ type: "pkcs1"
51
+ });
52
+ } catch (error) {
53
+ if (error.code === "ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS") throw new Error("Only RSA private keys are supported for DKIM signing");
54
+ throw error;
55
+ }
56
+ return {
57
+ selector: dkim.keySelector,
58
+ privateKey,
59
+ domain: dkim.domainName
60
+ };
61
+ };
62
+ const parseHeaders = (headers) => {
63
+ if (!headers) return;
64
+ if (Array.isArray(headers)) return headers.reduce((acc, { key, value }) => {
65
+ acc[key] = value;
66
+ return acc;
67
+ }, {});
68
+ 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)]));
69
+ };
70
+ const parseIcalEvent = (icalEvent) => {
71
+ if (typeof icalEvent === "string") return {
72
+ content: Buffer.from(icalEvent).toString("base64"),
73
+ filename: "invite.ics",
74
+ type: "text/calendar"
75
+ };
76
+ if (typeof icalEvent === "object" && icalEvent && "content" in icalEvent) return parseAttachments([{
77
+ filename: icalEvent.filename || "invite.ics",
78
+ content: icalEvent.content,
79
+ encoding: icalEvent.encoding,
80
+ contentType: "text/calendar"
81
+ }])[0];
82
+ throw new Error("Not supported icalEvent format");
83
+ };
84
+ const mailchannelsTransport = (options) => {
85
+ const { apiKey = "", sendMode = "async", ...clientOptions } = options;
86
+ const client = new MailChannelsClient(apiKey, clientOptions);
87
+ const emails = new Emails(client);
88
+ const sendMail = sendMode === "sync" ? emails.send.bind(emails) : emails.queue.bind(emails);
89
+ return {
90
+ name: "MailChannelsTransport",
91
+ version,
92
+ send(mail, callback) {
93
+ const sentMessageInfo = {
94
+ messageId: null,
95
+ accepted: [],
96
+ rejected: [],
97
+ envelope: mail.message.getEnvelope(),
98
+ response: null
99
+ };
100
+ mail.normalize(async (err, data) => {
101
+ if (!data || err) return callback(err || /* @__PURE__ */ new Error("Failed to normalize mail data"), sentMessageInfo);
102
+ try {
103
+ let attachments = parseAttachments(data.attachments);
104
+ if (data.icalEvent) {
105
+ const icalAttachment = parseIcalEvent(data.icalEvent);
106
+ attachments = attachments ? [...attachments, icalAttachment] : [icalAttachment];
107
+ }
108
+ const sdkResponse = await sendMail({
109
+ ...data.mailchannels,
110
+ from: parseAddress(data.from),
111
+ replyTo: parseAddress(data.replyTo),
112
+ to: parseAddresses(data.to),
113
+ cc: parseAddresses(data.cc),
114
+ bcc: parseAddresses(data.bcc),
115
+ subject: data.subject || "",
116
+ text: data.text?.toString() || "",
117
+ html: data.html?.toString() || "",
118
+ headers: parseHeaders(data.headers),
119
+ attachments,
120
+ dkim: parseDkim(data.dkim)
121
+ });
122
+ sentMessageInfo.response = sdkResponse;
123
+ if (sdkResponse.error) {
124
+ callback(new Error(sdkResponse.error.message), sentMessageInfo);
125
+ return;
126
+ }
127
+ if ("results" in sdkResponse.data && sdkResponse.data.results?.[0]) {
128
+ const result = sdkResponse.data.results[0];
129
+ sentMessageInfo.messageId = result.messageId;
130
+ switch (result.status) {
131
+ case "sent":
132
+ sentMessageInfo.accepted = sentMessageInfo.envelope.to;
133
+ break;
134
+ case "failed": sentMessageInfo.rejected = sentMessageInfo.envelope.to;
135
+ }
136
+ }
137
+ callback(null, sentMessageInfo);
138
+ } catch (error) {
139
+ callback(error, sentMessageInfo);
140
+ }
141
+ });
142
+ }
143
+ };
144
+ };
145
+ export { mailchannelsTransport };
@@ -0,0 +1,20 @@
1
+ declare const createSimulator: (options?: {
2
+ host?: string;
3
+ port?: number;
4
+ silent?: boolean;
5
+ }) => {
6
+ server: import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>;
7
+ state: {
8
+ emailApi: {
9
+ accounts: Map<any, any>;
10
+ apiKeyScopes: Map<any, any>;
11
+ };
12
+ };
13
+ readonly url: string | null;
14
+ close(): Promise<void>;
15
+ listen(listenOptions?: {
16
+ host?: string;
17
+ port?: number;
18
+ }): Promise<string>;
19
+ };
20
+ export { createSimulator };
@@ -0,0 +1,2 @@
1
+ import { createSimulator } from "../_chunks/simulator.mjs";
2
+ export { createSimulator };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mailchannels-sdk",
3
- "version": "1.3.1",
3
+ "version": "1.4.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",
@@ -25,6 +25,14 @@
25
25
  ".": {
26
26
  "types": "./dist/mailchannels.d.mts",
27
27
  "default": "./dist/mailchannels.mjs"
28
+ },
29
+ "./simulator": {
30
+ "types": "./dist/simulator/index.d.mts",
31
+ "default": "./dist/simulator/index.mjs"
32
+ },
33
+ "./nodemailer": {
34
+ "types": "./dist/plugins/nodemailer/index.d.mts",
35
+ "default": "./dist/plugins/nodemailer/index.mjs"
28
36
  }
29
37
  },
30
38
  "types": "./dist/mailchannels.d.mts",
@@ -37,22 +45,25 @@
37
45
  ".agents/skills/mailchannels-js"
38
46
  ],
39
47
  "dependencies": {
48
+ "citty": "^0.2.2",
40
49
  "mime": "^4.1.0",
41
50
  "ofetch": "^2.0.0-alpha.3"
42
51
  },
43
52
  "devDependencies": {
44
53
  "@stylistic/eslint-plugin": "^5.10.0",
45
54
  "@types/markdown-it": "^14.1.2",
46
- "@types/node": "^26.1.2",
55
+ "@types/node": "^26.2.0",
56
+ "@types/nodemailer": "^8.0.1",
47
57
  "@vitest/coverage-v8": "^4.1.10",
48
58
  "changelogen": "^0.6.2",
59
+ "nodemailer": "^9.0.5",
49
60
  "obuild": "^0.4.38",
50
- "oxlint": "^1.76.0",
61
+ "oxlint": "^1.78.0",
51
62
  "scule": "^1.3.0",
52
63
  "typescript": "^6.0.3",
53
- "vitepress": "^2.0.0-alpha.18",
64
+ "vitepress": "^2.0.0-alpha.19",
54
65
  "vitepress-plugin-group-icons": "^1.7.6",
55
- "vitepress-plugin-llms": "^1.13.4",
66
+ "vitepress-plugin-llms": "^1.13.5",
56
67
  "vitest": "^4.1.10"
57
68
  },
58
69
  "engines": {
@@ -62,7 +73,8 @@
62
73
  "build": "obuild",
63
74
  "parity:fixtures": "node scripts/generate-parity-fixtures.ts",
64
75
  "release": "pnpm lint && pnpm test && pnpm build && changelogen --bump",
65
- "simulate": "node src/cli/index.ts simulate",
76
+ "cli": "pnpx jiti src/cli/index.ts",
77
+ "simulate": "pnpx jiti src/cli/index.ts simulate",
66
78
  "lint": "oxlint",
67
79
  "lint:fix": "oxlint --fix",
68
80
  "test": "vitest run --reporter=verbose --coverage",
@@ -1 +0,0 @@
1
- export {}