mailchannels-sdk 1.3.0 → 1.3.1
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/resources/attachments.md +1 -3
- package/.agents/skills/mailchannels-js/resources/sub-accounts.md +0 -2
- package/README.md +2 -2
- package/dist/_chunks/{simulator.mjs → simulate.mjs} +48 -8
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +15 -0
- package/dist/mailchannels.d.mts +5 -7
- package/dist/mailchannels.mjs +5 -7
- package/package.json +8 -8
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -50
|
@@ -45,12 +45,11 @@ const attachment = await Attachment.fromBlob(blob, { filename: 'hello.txt' })
|
|
|
45
45
|
|
|
46
46
|
### Inline Images (CID References)
|
|
47
47
|
|
|
48
|
-
Pass
|
|
48
|
+
Pass a `contentId` to embed an image inside the HTML body:
|
|
49
49
|
|
|
50
50
|
```ts
|
|
51
51
|
const logo = Attachment.fromBytes(bytes, {
|
|
52
52
|
filename: 'logo.png',
|
|
53
|
-
disposition: 'inline',
|
|
54
53
|
contentId: 'company-logo'
|
|
55
54
|
})
|
|
56
55
|
|
|
@@ -72,7 +71,6 @@ Both `fromBytes` and `fromBlob` accept an `AttachmentOptions` object:
|
|
|
72
71
|
| `filename` | `string` | Required. MIME type is inferred from it when `type` is omitted. |
|
|
73
72
|
| `type` | `string` | MIME type. Inferred from `filename` if omitted. For `fromBlob`, defaults to the Blob's own `type`. |
|
|
74
73
|
| `contentId` | `string` | For `cid:` inline image references. |
|
|
75
|
-
| `disposition` | `'attachment' \| 'inline'` | Defaults to `'attachment'`. |
|
|
76
74
|
|
|
77
75
|
### Awaiting Attachments Lazily
|
|
78
76
|
|
|
@@ -4,8 +4,6 @@ Sub-accounts are first-class on MailChannels. Use them for tenants, customers, o
|
|
|
4
4
|
senders so that one customer's reputation, limits, and bad traffic don't contaminate the
|
|
5
5
|
parent account or other tenants.
|
|
6
6
|
|
|
7
|
-
> Sub-accounts are only available on parent accounts on the 100K and higher plans.
|
|
8
|
-
|
|
9
7
|
### Handles
|
|
10
8
|
|
|
11
9
|
A handle uniquely identifies a sub-account. Rules:
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[![TypeScript][typescript-src]][typescript-href]
|
|
10
10
|
[![Node.js][node-src]][node-href]
|
|
11
11
|
|
|
12
|
-
> Built and tested against Email API `1.
|
|
12
|
+
> Built and tested against Email API `1.6.0`
|
|
13
13
|
|
|
14
14
|
Node.js SDK to integrate [MailChannels Email API](https://docs.mailchannels.com/email-api) into your JavaScript or TypeScript server-side applications.
|
|
15
15
|
|
|
@@ -273,7 +273,7 @@ pnpm test:watch
|
|
|
273
273
|
# Run typecheck
|
|
274
274
|
pnpm test:types
|
|
275
275
|
|
|
276
|
-
# Refresh API parity fixtures
|
|
276
|
+
# Refresh API parity fixtures and README version note
|
|
277
277
|
pnpm parity:fixtures
|
|
278
278
|
|
|
279
279
|
# Run the local simulator
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
2
3
|
import { createServer } from "node:http";
|
|
3
4
|
const SIMULATOR_SIGNING_KEY_ID = "simulator-default";
|
|
4
5
|
const JSON_HEADERS = { "content-type": "application/json" };
|
|
@@ -313,8 +314,8 @@ const createEmailApiHandler = ({ logRequests = true } = {}) => {
|
|
|
313
314
|
}
|
|
314
315
|
targetKey.status = "rotated";
|
|
315
316
|
targetKey.status_modified_at = currentTimestamp();
|
|
316
|
-
targetKey.gracePeriodExpiresAt = new Date(Date.now() +
|
|
317
|
-
targetKey.retiresAt = new Date(Date.now() +
|
|
317
|
+
targetKey.gracePeriodExpiresAt = new Date(Date.now() + 6048e5).toISOString();
|
|
318
|
+
targetKey.retiresAt = new Date(Date.now() + 2592e6).toISOString();
|
|
318
319
|
const newKey = createDkimKey(domain, body?.new_key?.selector || createId("selector"));
|
|
319
320
|
account.dkimKeysByDomain.set(domain, [...keys, newKey]);
|
|
320
321
|
sendJson(response, 201, {
|
|
@@ -627,7 +628,7 @@ const createEmailApiHandler = ({ logRequests = true } = {}) => {
|
|
|
627
628
|
end_time: url.searchParams.get("end_time") || currentTimestamp(),
|
|
628
629
|
open: summary.open,
|
|
629
630
|
open_tracking_delivered: summary.openTrackingDelivered,
|
|
630
|
-
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() -
|
|
631
|
+
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() - 2592e6)).toISOString()
|
|
631
632
|
});
|
|
632
633
|
return;
|
|
633
634
|
}
|
|
@@ -646,7 +647,7 @@ const createEmailApiHandler = ({ logRequests = true } = {}) => {
|
|
|
646
647
|
delivered: summary.delivered,
|
|
647
648
|
end_time: url.searchParams.get("end_time") || currentTimestamp(),
|
|
648
649
|
processed: summary.processed,
|
|
649
|
-
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() -
|
|
650
|
+
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() - 2592e6)).toISOString()
|
|
650
651
|
});
|
|
651
652
|
return;
|
|
652
653
|
}
|
|
@@ -659,7 +660,7 @@ const createEmailApiHandler = ({ logRequests = true } = {}) => {
|
|
|
659
660
|
unsubscribed: createMetricsBuckets(summary.unsubscribed)
|
|
660
661
|
},
|
|
661
662
|
end_time: url.searchParams.get("end_time") || currentTimestamp(),
|
|
662
|
-
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() -
|
|
663
|
+
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() - 2592e6)).toISOString(),
|
|
663
664
|
unsubscribe_delivered: summary.unsubscribeDelivered,
|
|
664
665
|
unsubscribed: summary.unsubscribed
|
|
665
666
|
});
|
|
@@ -678,7 +679,7 @@ const createEmailApiHandler = ({ logRequests = true } = {}) => {
|
|
|
678
679
|
dropped: summary.dropped,
|
|
679
680
|
end_time: url.searchParams.get("end_time") || currentTimestamp(),
|
|
680
681
|
processed: summary.processed,
|
|
681
|
-
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() -
|
|
682
|
+
start_time: url.searchParams.get("start_time") || (/* @__PURE__ */ new Date(Date.now() - 2592e6)).toISOString()
|
|
682
683
|
});
|
|
683
684
|
return;
|
|
684
685
|
}
|
|
@@ -718,7 +719,7 @@ const createEmailApiHandler = ({ logRequests = true } = {}) => {
|
|
|
718
719
|
limit,
|
|
719
720
|
offset,
|
|
720
721
|
senders: senders.slice(offset, offset + limit),
|
|
721
|
-
start_time: (/* @__PURE__ */ new Date(Date.now() -
|
|
722
|
+
start_time: (/* @__PURE__ */ new Date(Date.now() - 2592e6)).toISOString(),
|
|
722
723
|
total: senders.length
|
|
723
724
|
});
|
|
724
725
|
return;
|
|
@@ -796,6 +797,10 @@ const DEFAULT_HOST = "127.0.0.1";
|
|
|
796
797
|
const DEFAULT_PORT = 8787;
|
|
797
798
|
const createSimulator = (options = {}) => {
|
|
798
799
|
const { host = DEFAULT_HOST, port = DEFAULT_PORT } = options;
|
|
800
|
+
if (port !== void 0 && (isNaN(port) || port < 0 || port > 65535)) {
|
|
801
|
+
console.error("[Simulator]", `Invalid port '${port}': must be an integer between 0 and 65535.`);
|
|
802
|
+
process.exit(1);
|
|
803
|
+
}
|
|
799
804
|
const logRequests = !options.silent;
|
|
800
805
|
const emailApi = createEmailApiHandler({ logRequests });
|
|
801
806
|
const server = createServer(async (request, response) => {
|
|
@@ -843,4 +848,39 @@ const createSimulator = (options = {}) => {
|
|
|
843
848
|
}
|
|
844
849
|
};
|
|
845
850
|
};
|
|
846
|
-
|
|
851
|
+
var simulate_default = async (args) => {
|
|
852
|
+
const { MAILCHANNELS_SIMULATOR_PORT, MAILCHANNELS_SIMULATOR_HOST } = process.env;
|
|
853
|
+
const { values } = parseArgs({
|
|
854
|
+
args,
|
|
855
|
+
options: {
|
|
856
|
+
port: {
|
|
857
|
+
type: "string",
|
|
858
|
+
short: "p",
|
|
859
|
+
default: MAILCHANNELS_SIMULATOR_PORT
|
|
860
|
+
},
|
|
861
|
+
host: {
|
|
862
|
+
type: "string",
|
|
863
|
+
short: "h",
|
|
864
|
+
default: MAILCHANNELS_SIMULATOR_HOST
|
|
865
|
+
},
|
|
866
|
+
silent: {
|
|
867
|
+
type: "boolean",
|
|
868
|
+
short: "s",
|
|
869
|
+
default: false
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
const simulator = createSimulator({
|
|
874
|
+
host: values.host,
|
|
875
|
+
port: values.port !== void 0 ? Number.parseInt(values.port, 10) : void 0,
|
|
876
|
+
silent: values.silent
|
|
877
|
+
});
|
|
878
|
+
await simulator.listen();
|
|
879
|
+
const shutdown = async () => {
|
|
880
|
+
await simulator.close();
|
|
881
|
+
process.exit(0);
|
|
882
|
+
};
|
|
883
|
+
process.on("SIGINT", shutdown);
|
|
884
|
+
process.on("SIGTERM", shutdown);
|
|
885
|
+
};
|
|
886
|
+
export { simulate_default as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const LOGGER_NAME = "[MailChannels-CLI]";
|
|
3
|
+
console.info = console.info.bind(console.info, LOGGER_NAME);
|
|
4
|
+
console.error = console.error.bind(console.error, LOGGER_NAME);
|
|
5
|
+
const [command, ...args] = process.argv.slice(2);
|
|
6
|
+
switch (command) {
|
|
7
|
+
case "simulate":
|
|
8
|
+
const { default: simulate } = await import("../_chunks/simulate.mjs");
|
|
9
|
+
await simulate(args);
|
|
10
|
+
break;
|
|
11
|
+
default:
|
|
12
|
+
console.error(`Unknown command: ${command}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
export {};
|
package/dist/mailchannels.d.mts
CHANGED
|
@@ -121,13 +121,11 @@ interface EmailsSendAttachment {
|
|
|
121
121
|
*/
|
|
122
122
|
type?: string;
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
124
|
+
* A unique identifier for this attachment.
|
|
125
|
+
*
|
|
126
|
+
* When set, the attachment is embedded inline in the message body (`Content-Disposition: inline`) instead of offered as a downloadable attachment, and can be referenced from HTML content via a `cid:` URI, e.g. `<img src="cid:logo123">` refers to an attachment with content_id: `logo123`. (RFC 2392). Must be unique across all attachments in the request. Max length is 255 characters.
|
|
125
127
|
*/
|
|
126
128
|
contentId?: string;
|
|
127
|
-
/**
|
|
128
|
-
* The `Content-Disposition` header value for the attachment.
|
|
129
|
-
*/
|
|
130
|
-
disposition?: "attachment" | "inline";
|
|
131
129
|
}
|
|
132
130
|
interface EmailsSendTracking {
|
|
133
131
|
/**
|
|
@@ -193,7 +191,7 @@ interface EmailsSendContent {
|
|
|
193
191
|
*/
|
|
194
192
|
value: string;
|
|
195
193
|
}
|
|
196
|
-
type EmailsSendRecipientInput = EmailsSendRecipient
|
|
194
|
+
type EmailsSendRecipientInput = EmailsSendRecipient | string | (EmailsSendRecipient | string)[];
|
|
197
195
|
interface EmailsSendDkim {
|
|
198
196
|
/**
|
|
199
197
|
* Domain used for DKIM signing.
|
|
@@ -1893,7 +1891,7 @@ declare class SubAccounts {
|
|
|
1893
1891
|
readonly limits: SubAccountsLimits;
|
|
1894
1892
|
constructor(mailchannels: MailChannelsClient);
|
|
1895
1893
|
/**
|
|
1896
|
-
* Creates a new sub-account under the parent account. Each sub-account must have a unique handle composed solely of lowercase alphanumeric characters. If no handle is provided, a random handle will be generated.
|
|
1894
|
+
* Creates a new sub-account under the parent account. Each sub-account must have a unique handle composed solely of lowercase alphanumeric characters. If no handle is provided, a random handle will be generated.
|
|
1897
1895
|
* @param companyName - The name of the company associated with the sub-account. This name is used for display purposes only and does not affect the functionality of the sub-account. The length must be between 3 and 128 characters.
|
|
1898
1896
|
* @param handle - A unique name for the sub-account to be created. The length must be between 3 and 128 characters, and it may contain only lowercase letters and numbers. If not provided, a random handle will be generated.
|
|
1899
1897
|
* @example
|
package/dist/mailchannels.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { $fetch } from "ofetch";
|
|
|
2
2
|
import { subtle } from "node:crypto";
|
|
3
3
|
import { Buffer } from "node:buffer";
|
|
4
4
|
import mime from "mime";
|
|
5
|
-
var version = "1.3.
|
|
5
|
+
var version = "1.3.1";
|
|
6
6
|
var MailChannelsClient = class MailChannelsClient {
|
|
7
7
|
static DEFAULT_BASE_URL = "https://api.mailchannels.net";
|
|
8
8
|
static DEFAULT_TIMEOUT = 12e4;
|
|
@@ -225,8 +225,7 @@ const mapAttachment = (attachment) => ({
|
|
|
225
225
|
content: attachment.content,
|
|
226
226
|
filename: attachment.filename,
|
|
227
227
|
type: attachment.type,
|
|
228
|
-
content_id: attachment.contentId
|
|
229
|
-
disposition: attachment.disposition
|
|
228
|
+
content_id: attachment.contentId
|
|
230
229
|
});
|
|
231
230
|
const mapPersonalization = (personalization, index, rootTemplateData) => {
|
|
232
231
|
const to = parseArrayRecipients(personalization.to);
|
|
@@ -1239,7 +1238,7 @@ var Webhooks = class Webhooks {
|
|
|
1239
1238
|
if (dates.createdAfter && dates.createdBefore) {
|
|
1240
1239
|
const createdAfter = Date.parse(dates.createdAfter);
|
|
1241
1240
|
const createdBefore = Date.parse(dates.createdBefore);
|
|
1242
|
-
const maxRangeMs =
|
|
1241
|
+
const maxRangeMs = 26784e5;
|
|
1243
1242
|
if (createdBefore <= createdAfter) return {
|
|
1244
1243
|
data: null,
|
|
1245
1244
|
error: createValidationError("createdBefore must be later than createdAfter.")
|
|
@@ -2170,13 +2169,12 @@ const guessContentType = (filename) => {
|
|
|
2170
2169
|
};
|
|
2171
2170
|
var Attachment = class Attachment {
|
|
2172
2171
|
static fromBytes(data, options) {
|
|
2173
|
-
const { filename, type, contentId
|
|
2172
|
+
const { filename, type, contentId } = options;
|
|
2174
2173
|
return {
|
|
2175
2174
|
content: base64Content(data),
|
|
2176
2175
|
filename: decodeURIComponent(filename) || "attachment",
|
|
2177
2176
|
type: type || guessContentType(filename),
|
|
2178
|
-
contentId
|
|
2179
|
-
disposition
|
|
2177
|
+
contentId
|
|
2180
2178
|
};
|
|
2181
2179
|
}
|
|
2182
2180
|
static async fromBlob(blob, options) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailchannels-sdk",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
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",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"types": "./dist/mailchannels.d.mts",
|
|
31
31
|
"bin": {
|
|
32
|
-
"mailchannels-sdk": "./dist/cli.mjs",
|
|
33
|
-
"mailchannels": "./dist/cli.mjs"
|
|
32
|
+
"mailchannels-sdk": "./dist/cli/index.mjs",
|
|
33
|
+
"mailchannels": "./dist/cli/index.mjs"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
36
|
"dist",
|
|
@@ -43,16 +43,16 @@
|
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
45
45
|
"@types/markdown-it": "^14.1.2",
|
|
46
|
-
"@types/node": "^26.1.
|
|
46
|
+
"@types/node": "^26.1.2",
|
|
47
47
|
"@vitest/coverage-v8": "^4.1.10",
|
|
48
48
|
"changelogen": "^0.6.2",
|
|
49
49
|
"obuild": "^0.4.38",
|
|
50
|
-
"oxlint": "^1.
|
|
50
|
+
"oxlint": "^1.76.0",
|
|
51
51
|
"scule": "^1.3.0",
|
|
52
52
|
"typescript": "^6.0.3",
|
|
53
53
|
"vitepress": "^2.0.0-alpha.18",
|
|
54
|
-
"vitepress-plugin-group-icons": "^1.7.
|
|
55
|
-
"vitepress-plugin-llms": "^1.13.
|
|
54
|
+
"vitepress-plugin-group-icons": "^1.7.6",
|
|
55
|
+
"vitepress-plugin-llms": "^1.13.4",
|
|
56
56
|
"vitest": "^4.1.10"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"build": "obuild",
|
|
63
63
|
"parity:fixtures": "node scripts/generate-parity-fixtures.ts",
|
|
64
64
|
"release": "pnpm lint && pnpm test && pnpm build && changelogen --bump",
|
|
65
|
-
"simulate": "node src/cli.ts simulate",
|
|
65
|
+
"simulate": "node src/cli/index.ts simulate",
|
|
66
66
|
"lint": "oxlint",
|
|
67
67
|
"lint:fix": "oxlint --fix",
|
|
68
68
|
"test": "vitest run --reporter=verbose --coverage",
|
package/dist/cli.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|
package/dist/cli.mjs
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { parseArgs } from "node:util";
|
|
3
|
-
const LOGGER_NAME = "[MailChannels-CLI]";
|
|
4
|
-
console.info = console.info.bind(console.info, LOGGER_NAME);
|
|
5
|
-
console.error = console.error.bind(console.error, LOGGER_NAME);
|
|
6
|
-
const [command, ...args] = process.argv.slice(2);
|
|
7
|
-
switch (command) {
|
|
8
|
-
case "simulate":
|
|
9
|
-
const { createSimulator } = await import("./_chunks/simulator.mjs");
|
|
10
|
-
const { MAILCHANNELS_SIMULATOR_PORT, MAILCHANNELS_SIMULATOR_HOST } = process.env;
|
|
11
|
-
const { values } = parseArgs({
|
|
12
|
-
args,
|
|
13
|
-
options: {
|
|
14
|
-
port: {
|
|
15
|
-
type: "string",
|
|
16
|
-
short: "p",
|
|
17
|
-
default: MAILCHANNELS_SIMULATOR_PORT
|
|
18
|
-
},
|
|
19
|
-
host: {
|
|
20
|
-
type: "string",
|
|
21
|
-
short: "h",
|
|
22
|
-
default: MAILCHANNELS_SIMULATOR_HOST
|
|
23
|
-
},
|
|
24
|
-
silent: {
|
|
25
|
-
type: "boolean",
|
|
26
|
-
short: "s",
|
|
27
|
-
default: false
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
const port = values.port !== void 0 ? Number.parseInt(values.port, 10) : void 0;
|
|
32
|
-
if (port !== void 0 && (isNaN(port) || port < 0 || port > 65535)) {
|
|
33
|
-
console.error("[Simulator]", `Invalid port "${values.port}": must be an integer between 0 and 65535.`);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
const simulator = createSimulator({
|
|
37
|
-
host: values.host,
|
|
38
|
-
port,
|
|
39
|
-
silent: values.silent
|
|
40
|
-
});
|
|
41
|
-
await simulator.listen();
|
|
42
|
-
const shutdown = async () => {
|
|
43
|
-
await simulator.close();
|
|
44
|
-
process.exit(0);
|
|
45
|
-
};
|
|
46
|
-
process.on("SIGINT", shutdown);
|
|
47
|
-
process.on("SIGTERM", shutdown);
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
export {};
|