shipmail-cli 0.4.10 → 0.4.11
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/README.md +7 -4
- package/dist/cli.js +105 -30
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -84,12 +84,12 @@ shipmail mailboxes sandbox-inbound mbx_123 --from customer@example.com --subject
|
|
|
84
84
|
# Read inbound mail
|
|
85
85
|
shipmail inbox list 550e8400-e29b-41d4-a716-446655440000 --folder-role inbox --after 2025-07-20T00:00:00Z --limit 25
|
|
86
86
|
shipmail inbox message 550e8400-e29b-41d4-a716-446655440000 eml_123
|
|
87
|
-
shipmail inbox threads 550e8400-e29b-41d4-a716-446655440000 --
|
|
87
|
+
shipmail inbox threads 550e8400-e29b-41d4-a716-446655440000 --attention-state needs_reply --after 2025-07-20T00:00:00Z --limit 50
|
|
88
88
|
shipmail inbox thread 550e8400-e29b-41d4-a716-446655440000 thr_123
|
|
89
|
-
shipmail inbox draft 550e8400-e29b-41d4-a716-446655440000 thr_123 --expected-
|
|
89
|
+
shipmail inbox draft 550e8400-e29b-41d4-a716-446655440000 thr_123 --expected-version 7 --text "Thanks for the note"
|
|
90
90
|
# Apply your approval policy; stale drafts fail with 409 without delivery.
|
|
91
91
|
shipmail inbox send-draft 550e8400-e29b-41d4-a716-446655440000 thr_123 drf_123
|
|
92
|
-
shipmail inbox thread-
|
|
92
|
+
shipmail inbox thread-attention 550e8400-e29b-41d4-a716-446655440000 thr_123 --done true --expected-version 8
|
|
93
93
|
shipmail inbox reply-message 550e8400-e29b-41d4-a716-446655440000 eml_123 --to user@example.com --text "Thanks"
|
|
94
94
|
shipmail inbox reply-thread 550e8400-e29b-41d4-a716-446655440000 thr_123 --to user@example.com --text "Following up"
|
|
95
95
|
|
|
@@ -114,6 +114,7 @@ shipmail newsletters preflight nws_123
|
|
|
114
114
|
shipmail newsletters schedule nws_123 --scheduled-at 2026-08-01T09:00:00.000Z
|
|
115
115
|
shipmail newsletter-assets list --kind image
|
|
116
116
|
shipmail newsletter-assets upload ./hero.png --content-type image/png
|
|
117
|
+
shipmail newsletter-assets upload ./demo.mp4 --content-type video/mp4 --thumbnail ./demo-poster.jpg
|
|
117
118
|
shipmail newsletter-assets register-url https://cdn.shipmail.to/newsletter-images/org_123/hero.png
|
|
118
119
|
|
|
119
120
|
# Manage webhooks
|
|
@@ -136,7 +137,9 @@ Paragraph, quote, callout, list-item, and column bodies in `--blocks` accept bar
|
|
|
136
137
|
sanitized inline HTML. Use `<p>` and `<br>` for line breaks. Shipmail keeps `a`, `b`, `br`,
|
|
137
138
|
`code`, `em`, `i`, `p`, `s`, `span`, `strong`, and `u`, with safe attributes and URLs.
|
|
138
139
|
|
|
139
|
-
`newsletter-assets
|
|
140
|
+
`newsletter-assets upload` uses a five-minute direct storage PUT, then calls the single-use
|
|
141
|
+
completion URL. Video uploads require a JPEG poster through `--thumbnail`. `newsletter-assets list`
|
|
142
|
+
includes current newsletter media storage usage in the JSON response.
|
|
140
143
|
|
|
141
144
|
## Generic API Command
|
|
142
145
|
|
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "shipmail";
|
|
15
15
|
|
|
16
16
|
// src/version.ts
|
|
17
|
-
var VERSION = "0.4.
|
|
17
|
+
var VERSION = "0.4.11";
|
|
18
18
|
|
|
19
19
|
// src/cli.ts
|
|
20
20
|
var DEFAULT_BASE_URL = "https://shipmail.to/api/v1";
|
|
@@ -374,10 +374,10 @@ Usage:
|
|
|
374
374
|
shipmail mailboxes sandbox-inbound <id> --from sender@example.com --subject "Test" (--text "Hello" | --html "<p>Hello</p>") [--to support@example.com]
|
|
375
375
|
shipmail inbox list <mailbox-id> [--folder-role inbox] [--search text] [--after ISO] [--before ISO] [--limit 25] [--cursor cur]
|
|
376
376
|
shipmail inbox message <mailbox-id> <message-id>
|
|
377
|
-
shipmail inbox threads <mailbox-id> [--
|
|
377
|
+
shipmail inbox threads <mailbox-id> [--attention-state needs_reply] [--after ISO] [--before ISO] [--limit 25] [--cursor cur]
|
|
378
378
|
shipmail inbox thread <mailbox-id> <thread-id>
|
|
379
|
-
shipmail inbox thread-
|
|
380
|
-
shipmail inbox draft <mailbox-id> <thread-id> --expected-
|
|
379
|
+
shipmail inbox thread-attention <mailbox-id> <thread-id> --done true --expected-version 3
|
|
380
|
+
shipmail inbox draft <mailbox-id> <thread-id> --expected-version 3 (--text "Hello" | --html "<p>Hello</p>") [--reply-mode reply_all]
|
|
381
381
|
shipmail inbox send-draft <mailbox-id> <thread-id> <draft-id>
|
|
382
382
|
shipmail inbox reply-message <mailbox-id> <message-id> --to user@example.com (--text "Hello" | --html "<p>Hello</p>")
|
|
383
383
|
shipmail inbox reply-thread <mailbox-id> <thread-id> --to user@example.com (--text "Hello" | --html "<p>Hello</p>")
|
|
@@ -415,7 +415,7 @@ Usage:
|
|
|
415
415
|
shipmail newsletters cancel <id>
|
|
416
416
|
shipmail newsletters resume <id>
|
|
417
417
|
shipmail newsletter-assets list [--kind image|video] [--q text] [--limit 25] [--cursor cur]
|
|
418
|
-
shipmail newsletter-assets upload <file> [--filename name] [--content-type type]
|
|
418
|
+
shipmail newsletter-assets upload <file> [--filename name] [--content-type type] [--thumbnail poster.jpg]
|
|
419
419
|
shipmail newsletter-assets register-url <url> [--filename name] [--thumbnail-url url]
|
|
420
420
|
shipmail newsletter-sender-identities list [--limit 25] [--cursor cur]
|
|
421
421
|
shipmail newsletter-domains list [--limit 25] [--cursor cur]
|
|
@@ -912,7 +912,7 @@ async function runInbox(client, parsed, out) {
|
|
|
912
912
|
return;
|
|
913
913
|
}
|
|
914
914
|
if (subcommand === "threads") {
|
|
915
|
-
const
|
|
915
|
+
const attentionState = getString(parsed.options, "attention-state");
|
|
916
916
|
const sortBy = getString(parsed.options, "sort-by");
|
|
917
917
|
const order = getString(parsed.options, "order");
|
|
918
918
|
writeJson(
|
|
@@ -920,8 +920,8 @@ async function runInbox(client, parsed, out) {
|
|
|
920
920
|
await client.mailboxes.listInboxThreads(
|
|
921
921
|
requirePositional(parsed.positional, 2, "mailbox id"),
|
|
922
922
|
{
|
|
923
|
-
...
|
|
924
|
-
|
|
923
|
+
...attentionState ? {
|
|
924
|
+
attention_state: attentionState
|
|
925
925
|
} : {},
|
|
926
926
|
...sortBy ? { sort_by: sortBy } : {},
|
|
927
927
|
...order ? { order } : {},
|
|
@@ -934,29 +934,31 @@ async function runInbox(client, parsed, out) {
|
|
|
934
934
|
);
|
|
935
935
|
return;
|
|
936
936
|
}
|
|
937
|
-
if (subcommand === "thread-
|
|
938
|
-
const
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
|
|
937
|
+
if (subcommand === "thread-attention") {
|
|
938
|
+
const done = getBoolean(parsed.options, "done");
|
|
939
|
+
const followUpAt = getString(parsed.options, "follow-up-at");
|
|
940
|
+
const expectedVersion = getNumber(parsed.options, "expected-version");
|
|
941
|
+
if (done === void 0 && followUpAt === void 0 || expectedVersion === void 0) {
|
|
942
|
+
throw new CliUsageError("Provide --done or --follow-up-at, and provide --expected-version.");
|
|
942
943
|
}
|
|
943
944
|
writeJson(
|
|
944
945
|
out,
|
|
945
|
-
await client.mailboxes.
|
|
946
|
+
await client.mailboxes.updateInboxThreadAttention(
|
|
946
947
|
requirePositional(parsed.positional, 2, "mailbox id"),
|
|
947
948
|
requirePositional(parsed.positional, 3, "thread id"),
|
|
948
949
|
{
|
|
949
|
-
|
|
950
|
-
|
|
950
|
+
...done === void 0 ? {} : { done },
|
|
951
|
+
...followUpAt === void 0 ? {} : { follow_up_at: followUpAt === "null" ? null : followUpAt },
|
|
952
|
+
expected_version: expectedVersion
|
|
951
953
|
}
|
|
952
954
|
)
|
|
953
955
|
);
|
|
954
956
|
return;
|
|
955
957
|
}
|
|
956
958
|
if (subcommand === "draft") {
|
|
957
|
-
const
|
|
958
|
-
if (
|
|
959
|
-
throw new CliUsageError("Provide --expected-
|
|
959
|
+
const expectedVersion = getNumber(parsed.options, "expected-version");
|
|
960
|
+
if (expectedVersion === void 0) {
|
|
961
|
+
throw new CliUsageError("Provide --expected-version.");
|
|
960
962
|
}
|
|
961
963
|
const text = await readTextInput(getString(parsed.options, "text"));
|
|
962
964
|
const html = await readTextInput(getString(parsed.options, "html"));
|
|
@@ -967,7 +969,7 @@ async function runInbox(client, parsed, out) {
|
|
|
967
969
|
requirePositional(parsed.positional, 2, "mailbox id"),
|
|
968
970
|
requirePositional(parsed.positional, 3, "thread id"),
|
|
969
971
|
{
|
|
970
|
-
|
|
972
|
+
expected_version: expectedVersion,
|
|
971
973
|
...text ? { text } : {},
|
|
972
974
|
...html ? { html } : {},
|
|
973
975
|
...getString(parsed.options, "reply-mode") ? { reply_mode: getString(parsed.options, "reply-mode") } : {}
|
|
@@ -1574,7 +1576,7 @@ async function runNewsletters(client, parsed, out) {
|
|
|
1574
1576
|
}
|
|
1575
1577
|
throw new CliUsageError("Unknown newsletters command.");
|
|
1576
1578
|
}
|
|
1577
|
-
async function runNewsletterAssets(client, parsed, out) {
|
|
1579
|
+
async function runNewsletterAssets(client, parsed, out, fetchImpl) {
|
|
1578
1580
|
const subcommand = parsed.positional[1];
|
|
1579
1581
|
if (subcommand === "list") {
|
|
1580
1582
|
const kind = getString(parsed.options, "kind");
|
|
@@ -1595,14 +1597,37 @@ async function runNewsletterAssets(client, parsed, out) {
|
|
|
1595
1597
|
if (subcommand === "upload") {
|
|
1596
1598
|
const filePath = requirePositional(parsed.positional, 2, "file");
|
|
1597
1599
|
const data = await readFile(realpathSync(filePath));
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1600
|
+
const filename = getString(parsed.options, "filename") ?? filePath.split(/[\\/]/).at(-1) ?? "newsletter-asset";
|
|
1601
|
+
const contentType = newsletterAssetContentType(
|
|
1602
|
+
filePath,
|
|
1603
|
+
getString(parsed.options, "content-type")
|
|
1604
|
+
);
|
|
1605
|
+
const thumbnail = contentType.startsWith("video/") ? await readNewsletterVideoThumbnail(parsed.options) : void 0;
|
|
1606
|
+
const prepared = await client.newsletters.assets.prepareUpload({
|
|
1607
|
+
filename,
|
|
1608
|
+
content_type: contentType,
|
|
1609
|
+
size: data.byteLength,
|
|
1610
|
+
sha256: createHash("sha256").update(data).digest("hex")
|
|
1611
|
+
});
|
|
1612
|
+
await putPreparedNewsletterBytes(fetchImpl, prepared.upload_url, prepared.upload_headers, data);
|
|
1613
|
+
if (prepared.kind === "video") {
|
|
1614
|
+
if (!thumbnail) throw new CliUsageError("Video uploads require --thumbnail <jpeg-file>.");
|
|
1615
|
+
await putPreparedNewsletterBytes(
|
|
1616
|
+
fetchImpl,
|
|
1617
|
+
prepared.thumbnail_upload_url,
|
|
1618
|
+
prepared.thumbnail_upload_headers,
|
|
1619
|
+
thumbnail
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
const completion = await fetchImpl(prepared.complete_url, {
|
|
1623
|
+
method: prepared.complete_method,
|
|
1624
|
+
redirect: "error"
|
|
1625
|
+
});
|
|
1626
|
+
if (!completion.ok) {
|
|
1627
|
+
throw new CliHttpError(completion.status, { response: await completion.text() });
|
|
1628
|
+
}
|
|
1629
|
+
const asset = await completion.json();
|
|
1630
|
+
writeJson(out, asset);
|
|
1606
1631
|
return;
|
|
1607
1632
|
}
|
|
1608
1633
|
if (subcommand === "register-url") {
|
|
@@ -1618,6 +1643,55 @@ async function runNewsletterAssets(client, parsed, out) {
|
|
|
1618
1643
|
}
|
|
1619
1644
|
throw new CliUsageError("Unknown newsletter-assets command.");
|
|
1620
1645
|
}
|
|
1646
|
+
function newsletterAssetContentType(filePath, declared) {
|
|
1647
|
+
const value = declared?.trim().toLowerCase() ?? "";
|
|
1648
|
+
switch (value) {
|
|
1649
|
+
case "image/gif":
|
|
1650
|
+
case "image/jpeg":
|
|
1651
|
+
case "image/png":
|
|
1652
|
+
case "image/webp":
|
|
1653
|
+
case "video/mp4":
|
|
1654
|
+
case "video/quicktime":
|
|
1655
|
+
case "video/webm":
|
|
1656
|
+
return value;
|
|
1657
|
+
default:
|
|
1658
|
+
if (value) throw new CliUsageError("Unsupported newsletter asset content type.");
|
|
1659
|
+
}
|
|
1660
|
+
const lowerPath = filePath.toLowerCase();
|
|
1661
|
+
if (lowerPath.endsWith(".gif")) return "image/gif";
|
|
1662
|
+
if (lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg")) return "image/jpeg";
|
|
1663
|
+
if (lowerPath.endsWith(".png")) return "image/png";
|
|
1664
|
+
if (lowerPath.endsWith(".webp")) return "image/webp";
|
|
1665
|
+
if (lowerPath.endsWith(".mp4")) return "video/mp4";
|
|
1666
|
+
if (lowerPath.endsWith(".mov")) return "video/quicktime";
|
|
1667
|
+
if (lowerPath.endsWith(".webm")) return "video/webm";
|
|
1668
|
+
throw new CliUsageError("Use --content-type for this newsletter asset file.");
|
|
1669
|
+
}
|
|
1670
|
+
async function readNewsletterVideoThumbnail(options) {
|
|
1671
|
+
const thumbnailPath = getString(options, "thumbnail");
|
|
1672
|
+
if (!thumbnailPath) throw new CliUsageError("Video uploads require --thumbnail <jpeg-file>.");
|
|
1673
|
+
const bytes = await readFile(realpathSync(thumbnailPath));
|
|
1674
|
+
if (bytes.byteLength < 3 || bytes[0] !== 255 || bytes[1] !== 216 || bytes[2] !== 255) {
|
|
1675
|
+
throw new CliUsageError("--thumbnail must be a JPEG file.");
|
|
1676
|
+
}
|
|
1677
|
+
return bytes;
|
|
1678
|
+
}
|
|
1679
|
+
function uploadBody(bytes) {
|
|
1680
|
+
const body = new ArrayBuffer(bytes.byteLength);
|
|
1681
|
+
new Uint8Array(body).set(bytes);
|
|
1682
|
+
return body;
|
|
1683
|
+
}
|
|
1684
|
+
async function putPreparedNewsletterBytes(fetchImpl, url, headers, bytes) {
|
|
1685
|
+
const response = await fetchImpl(url, {
|
|
1686
|
+
method: "PUT",
|
|
1687
|
+
headers,
|
|
1688
|
+
body: uploadBody(bytes),
|
|
1689
|
+
redirect: "error"
|
|
1690
|
+
});
|
|
1691
|
+
if (!response.ok) {
|
|
1692
|
+
throw new CliHttpError(response.status, { response: await response.text() });
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1621
1695
|
var CALENDAR_EVENT_STATUSES = [
|
|
1622
1696
|
"confirmed",
|
|
1623
1697
|
"tentative",
|
|
@@ -2082,7 +2156,8 @@ async function runCli(argv, options = {}) {
|
|
|
2082
2156
|
else if (command === "webhooks") await runWebhooks(client, parsed, out);
|
|
2083
2157
|
else if (command === "suppressions") await runSuppressions(client, parsed, out);
|
|
2084
2158
|
else if (command === "newsletters") await runNewsletters(client, parsed, out);
|
|
2085
|
-
else if (command === "newsletter-assets")
|
|
2159
|
+
else if (command === "newsletter-assets")
|
|
2160
|
+
await runNewsletterAssets(client, parsed, out, options.fetch ?? globalThis.fetch);
|
|
2086
2161
|
else if (command === "newsletter-sender-identities")
|
|
2087
2162
|
await runNewsletterSenderIdentities(client, parsed, out);
|
|
2088
2163
|
else if (command === "newsletter-domains") await runNewsletterDomains(client, parsed, out);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shipmail-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11",
|
|
4
4
|
"description": "Official CLI for the Shipmail API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"shipmail": "0.4.
|
|
44
|
+
"shipmail": "0.4.17"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"tsup": "^8.4.0",
|