mailchannels-sdk 1.3.0 → 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 +8 -10
- 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 -9
- 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 +73 -2
- package/dist/_chunks/mailchannels.d.mts +2094 -0
- package/dist/_chunks/mailchannels.mjs +2201 -0
- package/dist/_chunks/simulator.mjs +16 -8
- package/dist/cli/index.mjs +268 -0
- package/dist/mailchannels.d.mts +1 -2095
- package/dist/mailchannels.mjs +1 -2201
- 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 +21 -9
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -50
|
@@ -313,8 +313,8 @@ const createEmailApiHandler = ({ logRequests = true } = {}) => {
|
|
|
313
313
|
}
|
|
314
314
|
targetKey.status = "rotated";
|
|
315
315
|
targetKey.status_modified_at = currentTimestamp();
|
|
316
|
-
targetKey.gracePeriodExpiresAt = new Date(Date.now() +
|
|
317
|
-
targetKey.retiresAt = new Date(Date.now() +
|
|
316
|
+
targetKey.gracePeriodExpiresAt = new Date(Date.now() + 6048e5).toISOString();
|
|
317
|
+
targetKey.retiresAt = new Date(Date.now() + 2592e6).toISOString();
|
|
318
318
|
const newKey = createDkimKey(domain, body?.new_key?.selector || createId("selector"));
|
|
319
319
|
account.dkimKeysByDomain.set(domain, [...keys, newKey]);
|
|
320
320
|
sendJson(response, 201, {
|
|
@@ -627,7 +627,7 @@ const createEmailApiHandler = ({ logRequests = true } = {}) => {
|
|
|
627
627
|
end_time: url.searchParams.get("end_time") || currentTimestamp(),
|
|
628
628
|
open: summary.open,
|
|
629
629
|
open_tracking_delivered: summary.openTrackingDelivered,
|
|
630
|
-
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() -
|
|
630
|
+
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() - 2592e6)).toISOString()
|
|
631
631
|
});
|
|
632
632
|
return;
|
|
633
633
|
}
|
|
@@ -646,7 +646,7 @@ const createEmailApiHandler = ({ logRequests = true } = {}) => {
|
|
|
646
646
|
delivered: summary.delivered,
|
|
647
647
|
end_time: url.searchParams.get("end_time") || currentTimestamp(),
|
|
648
648
|
processed: summary.processed,
|
|
649
|
-
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() -
|
|
649
|
+
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() - 2592e6)).toISOString()
|
|
650
650
|
});
|
|
651
651
|
return;
|
|
652
652
|
}
|
|
@@ -659,7 +659,7 @@ const createEmailApiHandler = ({ logRequests = true } = {}) => {
|
|
|
659
659
|
unsubscribed: createMetricsBuckets(summary.unsubscribed)
|
|
660
660
|
},
|
|
661
661
|
end_time: url.searchParams.get("end_time") || currentTimestamp(),
|
|
662
|
-
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() -
|
|
662
|
+
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() - 2592e6)).toISOString(),
|
|
663
663
|
unsubscribe_delivered: summary.unsubscribeDelivered,
|
|
664
664
|
unsubscribed: summary.unsubscribed
|
|
665
665
|
});
|
|
@@ -678,7 +678,7 @@ const createEmailApiHandler = ({ logRequests = true } = {}) => {
|
|
|
678
678
|
dropped: summary.dropped,
|
|
679
679
|
end_time: url.searchParams.get("end_time") || currentTimestamp(),
|
|
680
680
|
processed: summary.processed,
|
|
681
|
-
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() -
|
|
681
|
+
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() - 2592e6)).toISOString()
|
|
682
682
|
});
|
|
683
683
|
return;
|
|
684
684
|
}
|
|
@@ -718,7 +718,7 @@ const createEmailApiHandler = ({ logRequests = true } = {}) => {
|
|
|
718
718
|
limit,
|
|
719
719
|
offset,
|
|
720
720
|
senders: senders.slice(offset, offset + limit),
|
|
721
|
-
start_time: (/* @__PURE__ */ new Date(Date.now() -
|
|
721
|
+
start_time: (/* @__PURE__ */ new Date(Date.now() - 2592e6)).toISOString(),
|
|
722
722
|
total: senders.length
|
|
723
723
|
});
|
|
724
724
|
return;
|
|
@@ -796,6 +796,10 @@ const DEFAULT_HOST = "127.0.0.1";
|
|
|
796
796
|
const DEFAULT_PORT = 8787;
|
|
797
797
|
const createSimulator = (options = {}) => {
|
|
798
798
|
const { host = DEFAULT_HOST, port = DEFAULT_PORT } = options;
|
|
799
|
+
if (port !== void 0 && (isNaN(port) || port < 0 || port > 65535)) {
|
|
800
|
+
console.error(`[Simulator] Invalid port '${port}': must be an integer between 0 and 65535.`);
|
|
801
|
+
process.exit(1);
|
|
802
|
+
}
|
|
799
803
|
const logRequests = !options.silent;
|
|
800
804
|
const emailApi = createEmailApiHandler({ logRequests });
|
|
801
805
|
const server = createServer(async (request, response) => {
|
|
@@ -819,6 +823,7 @@ const createSimulator = (options = {}) => {
|
|
|
819
823
|
},
|
|
820
824
|
async close() {
|
|
821
825
|
await new Promise((resolve, reject) => {
|
|
826
|
+
console.info("[Simulator] Shutting down simulator...");
|
|
822
827
|
for (const socket of sockets) socket.destroy();
|
|
823
828
|
server.close((error) => error ? reject(error) : resolve());
|
|
824
829
|
});
|
|
@@ -835,10 +840,13 @@ const createSimulator = (options = {}) => {
|
|
|
835
840
|
server.off("error", onError);
|
|
836
841
|
resolve();
|
|
837
842
|
});
|
|
843
|
+
}).catch((error) => {
|
|
844
|
+
console.error(`[Simulator] ${error.message}`);
|
|
845
|
+
process.exit(1);
|
|
838
846
|
});
|
|
839
847
|
const address = server.address();
|
|
840
848
|
serverUrl = `http://${nextHost}:${typeof address === "object" && address ? address.port : nextPort}`;
|
|
841
|
-
if (logRequests) console.info(
|
|
849
|
+
if (logRequests) console.info(`[Simulator] listening on ${serverUrl}`);
|
|
842
850
|
return serverUrl;
|
|
843
851
|
}
|
|
844
852
|
};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Attachment, Emails, MailChannelsClient, name, version } from "../_chunks/mailchannels.mjs";
|
|
3
|
+
import { createSimulator } from "../_chunks/simulator.mjs";
|
|
4
|
+
import { defineCommand, runMain } from "citty";
|
|
5
|
+
var simulate_default = defineCommand({
|
|
6
|
+
meta: {
|
|
7
|
+
name: "simulate",
|
|
8
|
+
description: "Start a local MailChannels simulator server"
|
|
9
|
+
},
|
|
10
|
+
args: {
|
|
11
|
+
port: {
|
|
12
|
+
type: "string",
|
|
13
|
+
alias: "p"
|
|
14
|
+
},
|
|
15
|
+
host: {
|
|
16
|
+
type: "string",
|
|
17
|
+
alias: "h"
|
|
18
|
+
},
|
|
19
|
+
silent: {
|
|
20
|
+
type: "boolean",
|
|
21
|
+
alias: "s",
|
|
22
|
+
default: false
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
async run({ args }) {
|
|
26
|
+
const { MAILCHANNELS_SIMULATOR_HOST, MAILCHANNELS_SIMULATOR_PORT } = process.env;
|
|
27
|
+
const host = args.host || MAILCHANNELS_SIMULATOR_HOST;
|
|
28
|
+
const port = args.port || MAILCHANNELS_SIMULATOR_PORT;
|
|
29
|
+
const simulator = createSimulator({
|
|
30
|
+
host,
|
|
31
|
+
port: port !== void 0 ? Number.parseInt(port, 10) : void 0,
|
|
32
|
+
silent: args.silent
|
|
33
|
+
});
|
|
34
|
+
await simulator.listen();
|
|
35
|
+
const shutdown = async () => {
|
|
36
|
+
await simulator.close();
|
|
37
|
+
};
|
|
38
|
+
process.on("SIGINT", shutdown);
|
|
39
|
+
process.on("SIGTERM", shutdown);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const emailArgs = {
|
|
43
|
+
"api-key": {
|
|
44
|
+
type: "string",
|
|
45
|
+
alias: "k"
|
|
46
|
+
},
|
|
47
|
+
"campaign-id": { type: "string" },
|
|
48
|
+
"dkim-domain": { type: "string" },
|
|
49
|
+
"dkim-selector": { type: "string" },
|
|
50
|
+
"dkim-private-key": { type: "string" },
|
|
51
|
+
"envelope-from": { type: "string" },
|
|
52
|
+
"tracking-click": {
|
|
53
|
+
type: "boolean",
|
|
54
|
+
default: false
|
|
55
|
+
},
|
|
56
|
+
"tracking-click-custom-domain-name": {
|
|
57
|
+
type: "string",
|
|
58
|
+
valueHint: "name"
|
|
59
|
+
},
|
|
60
|
+
"tracking-open": {
|
|
61
|
+
type: "boolean",
|
|
62
|
+
default: false
|
|
63
|
+
},
|
|
64
|
+
"tracking-open-custom-domain-name": {
|
|
65
|
+
type: "string",
|
|
66
|
+
valueHint: "name"
|
|
67
|
+
},
|
|
68
|
+
"from": {
|
|
69
|
+
type: "string",
|
|
70
|
+
required: true
|
|
71
|
+
},
|
|
72
|
+
"headers": {
|
|
73
|
+
type: "string",
|
|
74
|
+
alias: "h",
|
|
75
|
+
valueHint: "JSON"
|
|
76
|
+
},
|
|
77
|
+
"to": {
|
|
78
|
+
type: "string",
|
|
79
|
+
required: true
|
|
80
|
+
},
|
|
81
|
+
"subject": {
|
|
82
|
+
type: "string",
|
|
83
|
+
required: true
|
|
84
|
+
},
|
|
85
|
+
"text": {
|
|
86
|
+
type: "string",
|
|
87
|
+
default: ""
|
|
88
|
+
},
|
|
89
|
+
"html": {
|
|
90
|
+
type: "string",
|
|
91
|
+
default: ""
|
|
92
|
+
},
|
|
93
|
+
"cc": { type: "string" },
|
|
94
|
+
"bcc": { type: "string" },
|
|
95
|
+
"reply-to": { type: "string" },
|
|
96
|
+
"attachments": {
|
|
97
|
+
type: "boolean",
|
|
98
|
+
alias: "a",
|
|
99
|
+
default: false
|
|
100
|
+
},
|
|
101
|
+
"transactional": {
|
|
102
|
+
type: "boolean",
|
|
103
|
+
default: true
|
|
104
|
+
},
|
|
105
|
+
"unsubscribe-custom-domain-name": {
|
|
106
|
+
type: "string",
|
|
107
|
+
valueHint: "name"
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const getApiKey = (apiKey) => {
|
|
111
|
+
const { MAILCHANNELS_API_KEY } = process.env;
|
|
112
|
+
const key = MAILCHANNELS_API_KEY || apiKey;
|
|
113
|
+
if (!key) {
|
|
114
|
+
console.error("[Emails] Missing API key. Provide it using the '--api-key' flag or set the 'MAILCHANNELS_API_KEY' environment variable.");
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
return key;
|
|
118
|
+
};
|
|
119
|
+
const parseAttachments = async (rawAttachments) => {
|
|
120
|
+
if (!rawAttachments) return;
|
|
121
|
+
if (process.stdin.isTTY) {
|
|
122
|
+
console.error("[Emails] '--attachments' requires JSON input piped to stdin.");
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
let input = "";
|
|
126
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
127
|
+
let inputAttachments = [];
|
|
128
|
+
try {
|
|
129
|
+
inputAttachments = JSON.parse(input);
|
|
130
|
+
} catch {
|
|
131
|
+
console.error("[Emails] JSON input for '--attachments' is not valid.");
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
return inputAttachments.map((attachment, index) => {
|
|
135
|
+
if (!attachment.content || !attachment.filename) {
|
|
136
|
+
console.error("[Emails] Attachment at index " + index + " is missing required fields 'content' or 'filename'.");
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
if (attachment.type) return {
|
|
140
|
+
content: attachment.content,
|
|
141
|
+
filename: attachment.filename,
|
|
142
|
+
type: attachment.type,
|
|
143
|
+
contentId: attachment.content_id
|
|
144
|
+
};
|
|
145
|
+
return Attachment.fromBytes(Buffer.from(attachment.content, "base64"), {
|
|
146
|
+
filename: attachment.filename,
|
|
147
|
+
type: attachment.type,
|
|
148
|
+
contentId: attachment.content_id
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
const parseHeaders = (rawHeaders) => {
|
|
153
|
+
if (!rawHeaders) return;
|
|
154
|
+
try {
|
|
155
|
+
return JSON.parse(rawHeaders);
|
|
156
|
+
} catch {
|
|
157
|
+
console.error("[Emails] Invalid JSON for '--headers'");
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const parseOptions = async (args) => {
|
|
162
|
+
const attachments = await parseAttachments(args["attachments"]);
|
|
163
|
+
const headers = parseHeaders(args["headers"]);
|
|
164
|
+
return {
|
|
165
|
+
attachments,
|
|
166
|
+
campaignId: args["campaign-id"],
|
|
167
|
+
dkim: {
|
|
168
|
+
domain: args["dkim-domain"],
|
|
169
|
+
selector: args["dkim-selector"],
|
|
170
|
+
privateKey: args["dkim-private-key"]
|
|
171
|
+
},
|
|
172
|
+
envelopeFrom: args["envelope-from"],
|
|
173
|
+
tracking: {
|
|
174
|
+
click: {
|
|
175
|
+
customDomainName: args["tracking-click-custom-domain-name"],
|
|
176
|
+
enable: args["tracking-click"] || Boolean(args["tracking-click-custom-domain-name"])
|
|
177
|
+
},
|
|
178
|
+
open: {
|
|
179
|
+
customDomainName: args["tracking-open-custom-domain-name"],
|
|
180
|
+
enable: args["tracking-open"] || Boolean(args["tracking-open-custom-domain-name"])
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
headers,
|
|
184
|
+
from: args["from"],
|
|
185
|
+
to: args["to"].split(",").map((r) => r.trim()),
|
|
186
|
+
subject: args["subject"],
|
|
187
|
+
cc: args["cc"]?.split(",").map((r) => r.trim()),
|
|
188
|
+
bcc: args["bcc"]?.split(",").map((r) => r.trim()),
|
|
189
|
+
replyTo: args["reply-to"],
|
|
190
|
+
html: args["html"],
|
|
191
|
+
text: args["text"],
|
|
192
|
+
transactional: args["transactional"],
|
|
193
|
+
unsubscribe: { customDomainName: args["unsubscribe-custom-domain-name"] }
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
var send_default = defineCommand({
|
|
197
|
+
meta: {
|
|
198
|
+
name: "send",
|
|
199
|
+
description: "Send an email using the MailChannels Email API"
|
|
200
|
+
},
|
|
201
|
+
args: {
|
|
202
|
+
...emailArgs,
|
|
203
|
+
"dry-run": {
|
|
204
|
+
type: "boolean",
|
|
205
|
+
default: false
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
async run({ args }) {
|
|
209
|
+
const apiKey = getApiKey(args["api-key"]);
|
|
210
|
+
const client = new MailChannelsClient(apiKey);
|
|
211
|
+
const emails = new Emails(client);
|
|
212
|
+
const sendOptions = await parseOptions(args);
|
|
213
|
+
const { error } = await emails.send(sendOptions, args["dry-run"]);
|
|
214
|
+
if (error) {
|
|
215
|
+
console.error(`[Emails] ${error.message}`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
console.info("[Emails] Email sent successfully.");
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
var queue_default = defineCommand({
|
|
222
|
+
meta: {
|
|
223
|
+
name: "queue",
|
|
224
|
+
description: "Queue an email to be sent using the MailChannels Email API"
|
|
225
|
+
},
|
|
226
|
+
args: emailArgs,
|
|
227
|
+
async run({ args }) {
|
|
228
|
+
const apiKey = getApiKey(args["api-key"]);
|
|
229
|
+
const client = new MailChannelsClient(apiKey);
|
|
230
|
+
const emails = new Emails(client);
|
|
231
|
+
const sendOptions = await parseOptions(args);
|
|
232
|
+
const { error } = await emails.queue(sendOptions);
|
|
233
|
+
if (error) {
|
|
234
|
+
console.error(`[Emails] ${error.message}`);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
console.info("[Emails] Email queued successfully.");
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
var emails_default = defineCommand({
|
|
241
|
+
meta: {
|
|
242
|
+
name: "emails",
|
|
243
|
+
description: "Commands for sending emails using the MailChannels Email API"
|
|
244
|
+
},
|
|
245
|
+
args: {},
|
|
246
|
+
subCommands: {
|
|
247
|
+
send: send_default,
|
|
248
|
+
queue: queue_default
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
const main = defineCommand({
|
|
252
|
+
meta: {
|
|
253
|
+
name,
|
|
254
|
+
description: "MailChannels CLI",
|
|
255
|
+
version
|
|
256
|
+
},
|
|
257
|
+
subCommands: {
|
|
258
|
+
simulate: simulate_default,
|
|
259
|
+
emails: emails_default
|
|
260
|
+
},
|
|
261
|
+
setup() {
|
|
262
|
+
const LOGGER_NAME = "[MailChannels-CLI]";
|
|
263
|
+
console.info = console.info.bind(console, LOGGER_NAME);
|
|
264
|
+
console.error = console.error.bind(console, LOGGER_NAME);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
runMain(main);
|
|
268
|
+
export {};
|