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.
- package/.agents/skills/mailchannels-js/SKILL.md +4 -0
- package/.agents/skills/mailchannels-js/resources/attachments.md +7 -7
- package/.agents/skills/mailchannels-js/resources/clients-and-transport.md +5 -5
- package/.agents/skills/mailchannels-js/resources/custom-headers.md +4 -4
- package/.agents/skills/mailchannels-js/resources/dkim.md +10 -10
- package/.agents/skills/mailchannels-js/resources/domain-checks.md +6 -6
- package/.agents/skills/mailchannels-js/resources/error-handling.md +5 -5
- package/.agents/skills/mailchannels-js/resources/metrics-and-usage.md +9 -9
- package/.agents/skills/mailchannels-js/resources/overview.md +5 -5
- package/.agents/skills/mailchannels-js/resources/plugins/nodemailer.md +112 -0
- package/.agents/skills/mailchannels-js/resources/sending.md +10 -10
- package/.agents/skills/mailchannels-js/resources/sub-accounts.md +7 -7
- package/.agents/skills/mailchannels-js/resources/suppressions.md +4 -4
- package/.agents/skills/mailchannels-js/resources/templates.md +5 -5
- package/.agents/skills/mailchannels-js/resources/testing.md +52 -5
- package/.agents/skills/mailchannels-js/resources/unsubscribe.md +3 -3
- package/.agents/skills/mailchannels-js/resources/webhooks.md +8 -8
- package/README.md +71 -0
- package/dist/_chunks/mailchannels.d.mts +2094 -0
- package/dist/_chunks/mailchannels.mjs +2201 -0
- package/dist/_chunks/{simulate.mjs → simulator.mjs} +7 -39
- package/dist/cli/index.mjs +266 -13
- package/dist/mailchannels.d.mts +1 -2093
- package/dist/mailchannels.mjs +1 -2199
- package/dist/plugins/nodemailer/index.d.mts +33 -0
- package/dist/plugins/nodemailer/index.mjs +145 -0
- package/dist/simulator/index.d.mts +20 -0
- package/dist/simulator/index.mjs +2 -0
- package/package.json +18 -6
- 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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailchannels-sdk",
|
|
3
|
-
"version": "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.
|
|
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.
|
|
61
|
+
"oxlint": "^1.78.0",
|
|
51
62
|
"scule": "^1.3.0",
|
|
52
63
|
"typescript": "^6.0.3",
|
|
53
|
-
"vitepress": "^2.0.0-alpha.
|
|
64
|
+
"vitepress": "^2.0.0-alpha.19",
|
|
54
65
|
"vitepress-plugin-group-icons": "^1.7.6",
|
|
55
|
-
"vitepress-plugin-llms": "^1.13.
|
|
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
|
-
"
|
|
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",
|
package/dist/cli/index.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {}
|