shipmail-cli 0.4.9 → 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.
Files changed (3) hide show
  1. package/README.md +11 -4
  2. package/dist/cli.js +109 -31
  3. 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 --reply-state needs_reply --after 2025-07-20T00:00:00Z --limit 50
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-reply-version 7 --text "Thanks for the note"
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-state 550e8400-e29b-41d4-a716-446655440000 thr_123 --reply-state resolved --expected-reply-version 8
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
@@ -132,7 +133,13 @@ shipmail domains list --organization-id 00000000-0000-4000-8000-000000000123
132
133
  shipmail mailboxes create --organization-id 00000000-0000-4000-8000-000000000123 --domain-id dom_123 --address support --generate-password
133
134
  ```
134
135
 
135
- `newsletter-assets list` includes current newsletter media storage usage in the JSON response.
136
+ Paragraph, quote, callout, list-item, and column bodies in `--blocks` accept bare text or
137
+ sanitized inline HTML. Use `<p>` and `<br>` for line breaks. Shipmail keeps `a`, `b`, `br`,
138
+ `code`, `em`, `i`, `p`, `s`, `span`, `strong`, and `u`, with safe attributes and URLs.
139
+
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.
136
143
 
137
144
  ## Generic API Command
138
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.9";
17
+ var VERSION = "0.4.11";
18
18
 
19
19
  // src/cli.ts
20
20
  var DEFAULT_BASE_URL = "https://shipmail.to/api/v1";
@@ -372,12 +372,12 @@ Usage:
372
372
  shipmail mailboxes prepare-attachment-upload <id> <file> [--filename name] [--content-type type]
373
373
  shipmail mailboxes stage-attachment <id> <file> [--filename name] [--content-type type]
374
374
  shipmail mailboxes sandbox-inbound <id> --from sender@example.com --subject "Test" (--text "Hello" | --html "<p>Hello</p>") [--to support@example.com]
375
- shipmail inbox list <mailbox-id> [--folder-role inbox] [--after ISO] [--before ISO] [--limit 25] [--cursor cur]
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> [--reply-state needs_reply] [--after ISO] [--before ISO] [--limit 25] [--cursor cur]
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-state <mailbox-id> <thread-id> --reply-state resolved --expected-reply-version 3
380
- shipmail inbox draft <mailbox-id> <thread-id> --expected-reply-version 3 (--text "Hello" | --html "<p>Hello</p>") [--reply-mode reply_all]
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]
@@ -466,6 +466,9 @@ Booking page conferencing:
466
466
  Newsletter styling:
467
467
  --styling-mode <styled|plain> styled applies Shipmail's email theme. plain sends your HTML without injected styles, width, or centering, so the reader's email client styles it.
468
468
 
469
+ Newsletter block prose:
470
+ Paragraph, quote, callout, list-item, and column bodies accept bare text or sanitized inline HTML. Use <p> and <br> for line breaks.
471
+
469
472
  Newsletter feeds:
470
473
  --feed-entry-url <url> Link this issue's Atom feed title to an http(s) URL instead of its Shipmail archive.
471
474
 
@@ -909,7 +912,7 @@ async function runInbox(client, parsed, out) {
909
912
  return;
910
913
  }
911
914
  if (subcommand === "threads") {
912
- const replyState = getString(parsed.options, "reply-state");
915
+ const attentionState = getString(parsed.options, "attention-state");
913
916
  const sortBy = getString(parsed.options, "sort-by");
914
917
  const order = getString(parsed.options, "order");
915
918
  writeJson(
@@ -917,8 +920,8 @@ async function runInbox(client, parsed, out) {
917
920
  await client.mailboxes.listInboxThreads(
918
921
  requirePositional(parsed.positional, 2, "mailbox id"),
919
922
  {
920
- ...replyState ? {
921
- reply_state: replyState
923
+ ...attentionState ? {
924
+ attention_state: attentionState
922
925
  } : {},
923
926
  ...sortBy ? { sort_by: sortBy } : {},
924
927
  ...order ? { order } : {},
@@ -931,29 +934,31 @@ async function runInbox(client, parsed, out) {
931
934
  );
932
935
  return;
933
936
  }
934
- if (subcommand === "thread-state") {
935
- const replyState = getString(parsed.options, "reply-state");
936
- const expectedReplyVersion = getNumber(parsed.options, "expected-reply-version");
937
- if (!replyState || expectedReplyVersion === void 0) {
938
- throw new CliUsageError("Provide --reply-state and --expected-reply-version.");
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.");
939
943
  }
940
944
  writeJson(
941
945
  out,
942
- await client.mailboxes.updateInboxThreadReplyState(
946
+ await client.mailboxes.updateInboxThreadAttention(
943
947
  requirePositional(parsed.positional, 2, "mailbox id"),
944
948
  requirePositional(parsed.positional, 3, "thread id"),
945
949
  {
946
- reply_state: replyState,
947
- expected_reply_version: expectedReplyVersion
950
+ ...done === void 0 ? {} : { done },
951
+ ...followUpAt === void 0 ? {} : { follow_up_at: followUpAt === "null" ? null : followUpAt },
952
+ expected_version: expectedVersion
948
953
  }
949
954
  )
950
955
  );
951
956
  return;
952
957
  }
953
958
  if (subcommand === "draft") {
954
- const expectedReplyVersion = getNumber(parsed.options, "expected-reply-version");
955
- if (expectedReplyVersion === void 0) {
956
- throw new CliUsageError("Provide --expected-reply-version.");
959
+ const expectedVersion = getNumber(parsed.options, "expected-version");
960
+ if (expectedVersion === void 0) {
961
+ throw new CliUsageError("Provide --expected-version.");
957
962
  }
958
963
  const text = await readTextInput(getString(parsed.options, "text"));
959
964
  const html = await readTextInput(getString(parsed.options, "html"));
@@ -964,7 +969,7 @@ async function runInbox(client, parsed, out) {
964
969
  requirePositional(parsed.positional, 2, "mailbox id"),
965
970
  requirePositional(parsed.positional, 3, "thread id"),
966
971
  {
967
- expected_reply_version: expectedReplyVersion,
972
+ expected_version: expectedVersion,
968
973
  ...text ? { text } : {},
969
974
  ...html ? { html } : {},
970
975
  ...getString(parsed.options, "reply-mode") ? { reply_mode: getString(parsed.options, "reply-mode") } : {}
@@ -1571,7 +1576,7 @@ async function runNewsletters(client, parsed, out) {
1571
1576
  }
1572
1577
  throw new CliUsageError("Unknown newsletters command.");
1573
1578
  }
1574
- async function runNewsletterAssets(client, parsed, out) {
1579
+ async function runNewsletterAssets(client, parsed, out, fetchImpl) {
1575
1580
  const subcommand = parsed.positional[1];
1576
1581
  if (subcommand === "list") {
1577
1582
  const kind = getString(parsed.options, "kind");
@@ -1592,14 +1597,37 @@ async function runNewsletterAssets(client, parsed, out) {
1592
1597
  if (subcommand === "upload") {
1593
1598
  const filePath = requirePositional(parsed.positional, 2, "file");
1594
1599
  const data = await readFile(realpathSync(filePath));
1595
- writeJson(
1596
- out,
1597
- await client.newsletters.assets.upload({
1598
- filename: getString(parsed.options, "filename") ?? filePath.split(/[\\/]/).at(-1) ?? "newsletter-asset",
1599
- data,
1600
- content_type: getString(parsed.options, "content-type")
1601
- })
1602
- );
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);
1603
1631
  return;
1604
1632
  }
1605
1633
  if (subcommand === "register-url") {
@@ -1615,6 +1643,55 @@ async function runNewsletterAssets(client, parsed, out) {
1615
1643
  }
1616
1644
  throw new CliUsageError("Unknown newsletter-assets command.");
1617
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
+ }
1618
1695
  var CALENDAR_EVENT_STATUSES = [
1619
1696
  "confirmed",
1620
1697
  "tentative",
@@ -2079,7 +2156,8 @@ async function runCli(argv, options = {}) {
2079
2156
  else if (command === "webhooks") await runWebhooks(client, parsed, out);
2080
2157
  else if (command === "suppressions") await runSuppressions(client, parsed, out);
2081
2158
  else if (command === "newsletters") await runNewsletters(client, parsed, out);
2082
- else if (command === "newsletter-assets") await runNewsletterAssets(client, parsed, out);
2159
+ else if (command === "newsletter-assets")
2160
+ await runNewsletterAssets(client, parsed, out, options.fetch ?? globalThis.fetch);
2083
2161
  else if (command === "newsletter-sender-identities")
2084
2162
  await runNewsletterSenderIdentities(client, parsed, out);
2085
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.9",
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.15"
44
+ "shipmail": "0.4.17"
45
45
  },
46
46
  "devDependencies": {
47
47
  "tsup": "^8.4.0",