shipmail-cli 0.3.0 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +27 -1
  2. package/dist/cli.js +124 -5
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -34,6 +34,7 @@ shipmail mailboxes resume mbx_123
34
34
  shipmail mailboxes forwarding-add mbx_123 --destination owner@example.net
35
35
  shipmail mailboxes forwarding mbx_123
36
36
  shipmail mailboxes forwarding-remove mbx_123 fwd_123
37
+ shipmail mailboxes rules mbx_123
37
38
  shipmail mailboxes app-passwords list mbx_123
38
39
  shipmail mailboxes app-passwords create mbx_123 --name "Desktop mail" --expires-at 2026-10-01T00:00:00Z
39
40
  shipmail mailboxes app-passwords revoke mbx_123 appwd_123
@@ -51,6 +52,30 @@ shipmail messages send \
51
52
  shipmail messages list --client-reference crm-123
52
53
  shipmail messages reply msg_123 --to user@example.com --text "Thanks"
53
54
 
55
+ # Stage a raw file, then pass the returned sat_... ID when sending
56
+ shipmail mailboxes stage-attachment mbx_123 ./invoice.pdf --content-type application/pdf
57
+
58
+ # Prepare a five-minute, single-use upload URL for a browser-hosted component
59
+ shipmail mailboxes prepare-attachment-upload mbx_123 ./invoice.pdf --content-type application/pdf
60
+
61
+ shipmail messages send \
62
+ --mailbox-id mbx_123 \
63
+ --to customer@example.com \
64
+ --subject "Invoice" \
65
+ --text "Attached." \
66
+ --staged-attachment-id sat_123 \
67
+ --scheduled-at 2026-08-01T08:00:00.000Z
68
+
69
+ shipmail scheduled-messages list
70
+ shipmail scheduled-messages get msg_123
71
+ shipmail scheduled-messages update msg_123 \
72
+ --to customer@example.com \
73
+ --subject "Updated invoice" \
74
+ --text "Attached." \
75
+ --staged-attachment-id sat_123 \
76
+ --scheduled-at 2026-08-02T08:00:00.000Z
77
+ shipmail scheduled-messages cancel msg_123
78
+
54
79
  # Use an sm_test_... key to simulate an outbound result and fake inbound reply
55
80
  shipmail messages send --mailbox-id mbx_123 --to user@example.com --subject "Test" --text "Not delivered" --sandbox-outcome bounced
56
81
  shipmail mailboxes sandbox-inbound mbx_123 --from customer@example.com --subject "Re: Test" --text "Fake inbound reply"
@@ -126,10 +151,11 @@ Use repeatable `--header "Name: value"` flags for request headers. Use `--output
126
151
  - `status`
127
152
  - `api <METHOD> <PATH>`
128
153
  - `domains list|search|create|get|verify|delete`
129
- - `mailboxes list|create|get|suspend|resume|forwarding|forwarding-add|forwarding-remove|app-passwords|folders|identities`
154
+ - `mailboxes list|create|get|suspend|resume|forwarding|forwarding-add|forwarding-remove|app-passwords|folders|identities|stage-attachment|prepare-attachment-upload`
130
155
  - `inbox list|message|threads|thread|thread-state|draft|send-draft|reply-message|reply-thread|update|move`
131
156
  - `reply-scans create|status|results`
132
157
  - `messages send|list|get|reply` (test keys also support `--sandbox-outcome`)
158
+ - `scheduled-messages list|get|update|cancel`
133
159
  - `mailboxes sandbox-inbound`
134
160
  - `webhooks list|create|test|deliveries|replay-delivery`
135
161
  - `suppressions list|remove`
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
+ import { createHash } from "crypto";
4
5
  import { realpathSync } from "fs";
5
6
  import { readFile, writeFile } from "fs/promises";
6
7
  import { env as processEnv, exit, stderr, stdout } from "process";
@@ -13,7 +14,7 @@ import {
13
14
  } from "shipmail";
14
15
 
15
16
  // src/version.ts
16
- var VERSION = "0.3.0";
17
+ var VERSION = "0.4.1";
17
18
 
18
19
  // src/cli.ts
19
20
  var DEFAULT_BASE_URL = "https://shipmail.to/api/v1";
@@ -178,6 +179,12 @@ function parseArchiveVisibility(options) {
178
179
  if (value === "private" || value === "public") return value;
179
180
  throw new CliUsageError("--archive-visibility must be private or public.");
180
181
  }
182
+ function parseStylingMode(options) {
183
+ const value = getString(options, "styling-mode");
184
+ if (value === void 0) return void 0;
185
+ if (value === "styled" || value === "plain") return value;
186
+ throw new CliUsageError("--styling-mode must be styled or plain.");
187
+ }
181
188
  async function newsletterCreateParams(options) {
182
189
  const audience_id = getRequiredString(options, "audience-id");
183
190
  const newsletter_domain_id = getRequiredString(options, "newsletter-domain-id");
@@ -194,6 +201,7 @@ async function newsletterCreateParams(options) {
194
201
  }
195
202
  const send_window_hours = getNumber(options, "send-window-hours");
196
203
  const archive_visibility = parseArchiveVisibility(options);
204
+ const styling_mode = parseStylingMode(options);
197
205
  const preview_text = getString(options, "preview-text");
198
206
  const from_name = getString(options, "from-name");
199
207
  return {
@@ -207,7 +215,8 @@ async function newsletterCreateParams(options) {
207
215
  ...body_html !== void 0 ? { body_html } : {},
208
216
  ...body_text !== void 0 ? { body_text } : {},
209
217
  ...send_window_hours !== void 0 ? { send_window_hours } : {},
210
- ...archive_visibility !== void 0 ? { archive_visibility } : {}
218
+ ...archive_visibility !== void 0 ? { archive_visibility } : {},
219
+ ...styling_mode !== void 0 ? { styling_mode } : {}
211
220
  };
212
221
  }
213
222
  async function newsletterUpdateParams(options) {
@@ -219,6 +228,7 @@ async function newsletterUpdateParams(options) {
219
228
  const from_name = getString(options, "from-name");
220
229
  const send_window_hours = getNumber(options, "send-window-hours");
221
230
  const archive_visibility = parseArchiveVisibility(options);
231
+ const styling_mode = parseStylingMode(options);
222
232
  const blocksInput = getString(options, "blocks");
223
233
  const bodyHtmlInput = getString(options, "body-html");
224
234
  const bodyTextInput = getString(options, "body-text");
@@ -236,7 +246,8 @@ async function newsletterUpdateParams(options) {
236
246
  ...body_html !== void 0 ? { body_html } : {},
237
247
  ...body_text !== void 0 ? { body_text } : {},
238
248
  ...send_window_hours !== void 0 ? { send_window_hours } : {},
239
- ...archive_visibility !== void 0 ? { archive_visibility } : {}
249
+ ...archive_visibility !== void 0 ? { archive_visibility } : {},
250
+ ...styling_mode !== void 0 ? { styling_mode } : {}
240
251
  };
241
252
  if (Object.keys(params).length === 0) {
242
253
  throw new CliUsageError("Provide at least one field to update.");
@@ -315,6 +326,7 @@ function usage() {
315
326
 
316
327
  Usage:
317
328
  shipmail status
329
+ shipmail capabilities
318
330
  shipmail api <METHOD> <PATH> [--data JSON|@file] [--output file]
319
331
  shipmail domains list [--limit 25] [--cursor cur]
320
332
  shipmail domains search <keyword>
@@ -335,9 +347,12 @@ Usage:
335
347
  shipmail mailboxes app-passwords revoke <id> <app-password-id>
336
348
  shipmail mailboxes folders <id>
337
349
  shipmail mailboxes identities <id>
350
+ shipmail mailboxes rules <id>
338
351
  shipmail mailboxes forwarding <id>
339
352
  shipmail mailboxes forwarding-add <id> --destination owner@example.com
340
353
  shipmail mailboxes forwarding-remove <id> <forwarding-id>
354
+ shipmail mailboxes prepare-attachment-upload <id> <file> [--filename name] [--content-type type]
355
+ shipmail mailboxes stage-attachment <id> <file> [--filename name] [--content-type type]
341
356
  shipmail mailboxes sandbox-inbound <id> --from sender@example.com --subject "Test" (--text "Hello" | --html "<p>Hello</p>") [--to support@example.com]
342
357
  shipmail inbox list <mailbox-id> [--folder-role inbox] [--after ISO] [--before ISO] [--limit 25] [--cursor cur]
343
358
  shipmail inbox message <mailbox-id> <message-id>
@@ -354,6 +369,10 @@ Usage:
354
369
  shipmail messages list (--mailbox-id mbx | --client-reference crm-123) [--limit 25] [--cursor cur]
355
370
  shipmail messages get <message-id>
356
371
  shipmail messages reply <message-id> --to user@example.com (--text "Hello" | --html "<p>Hello</p>") [--client-reference crm-reply-123]
372
+ shipmail scheduled-messages list [--include-held] [--search text] [--limit 100]
373
+ shipmail scheduled-messages get <message-id>
374
+ shipmail scheduled-messages update <message-id> --to user@example.com --subject "Hi" --scheduled-at ISO (--text "Hello" | --html "<p>Hello</p>") [--staged-attachment-id sat_...]
375
+ shipmail scheduled-messages cancel <message-id>
357
376
  shipmail reply-scans create --mailbox-id mbx --after ISO [--before ISO]
358
377
  shipmail reply-scans status <scan-id>
359
378
  shipmail reply-scans results <scan-id> [--limit 25] [--cursor cur]
@@ -367,8 +386,9 @@ Usage:
367
386
  --newsletter-domain-id nwsdom_456 \\
368
387
  --name "July changelog" \\
369
388
  --subject "What shipped in July" \\
370
- --blocks @blocks.json
371
- shipmail newsletters update nws_789 --subject "Updated subject" --blocks @blocks-v2.json
389
+ --blocks @blocks.json \\
390
+ [--styling-mode styled|plain]
391
+ shipmail newsletters update nws_789 --subject "Updated subject" --blocks @blocks-v2.json [--styling-mode styled|plain]
372
392
  shipmail newsletters preview <id>
373
393
  shipmail newsletters preflight <id>
374
394
  shipmail newsletters test-send <id> --recipient-email owner@example.com
@@ -419,6 +439,9 @@ Booking page conferencing:
419
439
  --conferencing-provider <zoom|google_meet> Create a meeting for every booking.
420
440
  --conferencing-provider none Stop creating meeting links on update.
421
441
 
442
+ Newsletter styling:
443
+ --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.
444
+
422
445
  Global options:
423
446
  --api-key <key> API key. Defaults to SHIPMAIL_API_KEY.
424
447
  --base-url <url> API base URL. Defaults to SHIPMAIL_BASE_URL or ${DEFAULT_BASE_URL}.
@@ -526,6 +549,7 @@ async function sendMessageParams(options) {
526
549
  ...metadata ? { metadata } : {},
527
550
  ...getString(options, "source-rfc-message-id") ? { source_rfc_message_id: getString(options, "source-rfc-message-id") } : {},
528
551
  ...headers.length > 0 ? { headers } : {},
552
+ ...getStringList(options, "staged-attachment-id").length > 0 ? { staged_attachment_ids: getStringList(options, "staged-attachment-id") } : {},
529
553
  ...getString(options, "scheduled-at") ? { scheduled_at: getString(options, "scheduled-at") } : {},
530
554
  ...getString(options, "sandbox-outcome") ? {
531
555
  sandbox_outcome: getEnum(options, "sandbox-outcome", [
@@ -735,6 +759,13 @@ async function runMailboxes(client, parsed, out) {
735
759
  );
736
760
  return;
737
761
  }
762
+ if (subcommand === "rules") {
763
+ writeJson(
764
+ out,
765
+ await client.mailboxes.getRules(requirePositional(parsed.positional, 2, "mailbox id"))
766
+ );
767
+ return;
768
+ }
738
769
  if (subcommand === "forwarding") {
739
770
  writeJson(
740
771
  out,
@@ -759,6 +790,35 @@ async function runMailboxes(client, parsed, out) {
759
790
  writeJson(out, { ok: true, id: forwardingId });
760
791
  return;
761
792
  }
793
+ if (subcommand === "stage-attachment") {
794
+ const mailboxId = requirePositional(parsed.positional, 2, "mailbox id");
795
+ const filePath = requirePositional(parsed.positional, 3, "file");
796
+ const data = await readFile(realpathSync(filePath));
797
+ writeJson(
798
+ out,
799
+ await client.mailboxes.stageAttachment(mailboxId, {
800
+ filename: getString(parsed.options, "filename") ?? filePath.split(/[\\/]/).at(-1) ?? "attachment",
801
+ data,
802
+ content_type: getString(parsed.options, "content-type")
803
+ })
804
+ );
805
+ return;
806
+ }
807
+ if (subcommand === "prepare-attachment-upload") {
808
+ const mailboxId = requirePositional(parsed.positional, 2, "mailbox id");
809
+ const filePath = requirePositional(parsed.positional, 3, "file");
810
+ const data = await readFile(realpathSync(filePath));
811
+ writeJson(
812
+ out,
813
+ await client.mailboxes.prepareStagedAttachmentUpload(mailboxId, {
814
+ filename: getString(parsed.options, "filename") ?? filePath.split(/[\\/]/).at(-1) ?? "attachment",
815
+ content_type: getString(parsed.options, "content-type") ?? "application/octet-stream",
816
+ size: data.byteLength,
817
+ sha256: createHash("sha256").update(data).digest("hex")
818
+ })
819
+ );
820
+ return;
821
+ }
762
822
  if (subcommand === "sandbox-inbound") {
763
823
  const text = getString(parsed.options, "text");
764
824
  const html = getString(parsed.options, "html");
@@ -1037,6 +1097,52 @@ async function runMessages(client, parsed, out) {
1037
1097
  }
1038
1098
  throw new CliUsageError("Unknown messages command.");
1039
1099
  }
1100
+ async function runScheduledMessages(client, parsed, out) {
1101
+ const subcommand = parsed.positional[1];
1102
+ if (subcommand === "list") {
1103
+ writeJson(
1104
+ out,
1105
+ await client.scheduledMessages.list({
1106
+ include_held: getBoolean(parsed.options, "include-held"),
1107
+ search: getString(parsed.options, "search"),
1108
+ limit: getNumber(parsed.options, "limit")
1109
+ })
1110
+ );
1111
+ return;
1112
+ }
1113
+ const messageId = requirePositional(parsed.positional, 2, "message id");
1114
+ if (subcommand === "get") {
1115
+ writeJson(out, await client.scheduledMessages.get(messageId));
1116
+ return;
1117
+ }
1118
+ if (subcommand === "cancel") {
1119
+ await client.scheduledMessages.cancel(messageId);
1120
+ writeJson(out, { ok: true, id: messageId });
1121
+ return;
1122
+ }
1123
+ if (subcommand === "update") {
1124
+ const to = messageRecipients(parsed.options, "to");
1125
+ const cc = messageRecipients(parsed.options, "cc");
1126
+ const bcc = messageRecipients(parsed.options, "bcc");
1127
+ const text = getString(parsed.options, "text");
1128
+ const html = getString(parsed.options, "html");
1129
+ if (to.length === 0) throw new CliUsageError("Provide at least one --to recipient.");
1130
+ if (!text && !html) throw new CliUsageError("Provide --text or --html.");
1131
+ const update = {
1132
+ to,
1133
+ ...cc.length > 0 ? { cc } : {},
1134
+ ...bcc.length > 0 ? { bcc } : {},
1135
+ subject: getRequiredString(parsed.options, "subject"),
1136
+ ...text ? { text } : {},
1137
+ ...html ? { html } : {},
1138
+ ...getStringList(parsed.options, "staged-attachment-id").length > 0 ? { staged_attachment_ids: getStringList(parsed.options, "staged-attachment-id") } : {},
1139
+ scheduled_at: getRequiredString(parsed.options, "scheduled-at")
1140
+ };
1141
+ writeJson(out, await client.scheduledMessages.update(messageId, update));
1142
+ return;
1143
+ }
1144
+ throw new CliUsageError("Unknown scheduled-messages command.");
1145
+ }
1040
1146
  async function runWebhooks(client, parsed, out) {
1041
1147
  const subcommand = parsed.positional[1];
1042
1148
  if (subcommand === "list") {
@@ -1779,6 +1885,18 @@ async function runCli(argv, options = {}) {
1779
1885
  await writeRawResponse(parsed, { stdout: out }, response);
1780
1886
  return 0;
1781
1887
  }
1888
+ if (command === "capabilities") {
1889
+ const response = await rawApiRequest(
1890
+ parsed,
1891
+ options,
1892
+ "GET",
1893
+ "/capabilities",
1894
+ void 0,
1895
+ true
1896
+ );
1897
+ await writeRawResponse(parsed, { stdout: out }, response);
1898
+ return 0;
1899
+ }
1782
1900
  if (command === "api") {
1783
1901
  const method = requirePositional(parsed.positional, 1, "method").toUpperCase();
1784
1902
  const path = apiPath(requirePositional(parsed.positional, 2, "path"));
@@ -1792,6 +1910,7 @@ async function runCli(argv, options = {}) {
1792
1910
  else if (command === "mailboxes") await runMailboxes(client, parsed, out);
1793
1911
  else if (command === "inbox") await runInbox(client, parsed, out);
1794
1912
  else if (command === "messages") await runMessages(client, parsed, out);
1913
+ else if (command === "scheduled-messages") await runScheduledMessages(client, parsed, out);
1795
1914
  else if (command === "reply-scans") await runReplyScans(client, parsed, out);
1796
1915
  else if (command === "webhooks") await runWebhooks(client, parsed, out);
1797
1916
  else if (command === "suppressions") await runSuppressions(client, parsed, out);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shipmail-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
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.3.0"
44
+ "shipmail": "0.4.4"
45
45
  },
46
46
  "devDependencies": {
47
47
  "tsup": "^8.4.0",