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.
Files changed (31) hide show
  1. package/.agents/skills/mailchannels-js/SKILL.md +4 -0
  2. package/.agents/skills/mailchannels-js/resources/attachments.md +8 -10
  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 -9
  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 +73 -2
  19. package/dist/_chunks/mailchannels.d.mts +2094 -0
  20. package/dist/_chunks/mailchannels.mjs +2201 -0
  21. package/dist/_chunks/simulator.mjs +16 -8
  22. package/dist/cli/index.mjs +268 -0
  23. package/dist/mailchannels.d.mts +1 -2095
  24. package/dist/mailchannels.mjs +1 -2201
  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 +21 -9
  30. package/dist/cli.d.mts +0 -1
  31. 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() + 10080 * 60 * 1e3).toISOString();
317
- targetKey.retiresAt = new Date(Date.now() + 720 * 60 * 60 * 1e3).toISOString();
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() - 720 * 60 * 60 * 1e3)).toISOString()
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() - 720 * 60 * 60 * 1e3)).toISOString()
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() - 720 * 60 * 60 * 1e3)).toISOString(),
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() - 720 * 60 * 60 * 1e3)).toISOString()
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() - 720 * 60 * 60 * 1e3)).toISOString(),
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("[Simulator]", `listening on ${serverUrl}`);
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 {};