shipmail-cli 0.1.4 → 0.1.6
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 +22 -0
- package/dist/cli.js +244 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -40,6 +40,23 @@ shipmail messages send \
|
|
|
40
40
|
shipmail inbox list mbx_123 --folder-role inbox --limit 25
|
|
41
41
|
shipmail inbox thread mbx_123 thr_123
|
|
42
42
|
|
|
43
|
+
# Find newsletter sending domain IDs, then create a draft with blocks
|
|
44
|
+
shipmail newsletter-domains list
|
|
45
|
+
shipmail newsletters create \
|
|
46
|
+
--audience-id aud_123 \
|
|
47
|
+
--newsletter-domain-id nwsdom_456 \
|
|
48
|
+
--name "July changelog" \
|
|
49
|
+
--subject "What shipped in July" \
|
|
50
|
+
--blocks @blocks.json
|
|
51
|
+
shipmail newsletters update nws_123 --subject "Updated subject"
|
|
52
|
+
shipmail newsletters preview nws_123
|
|
53
|
+
shipmail newsletters test-send nws_123 --recipient-email owner@example.com
|
|
54
|
+
shipmail newsletters preflight nws_123
|
|
55
|
+
shipmail newsletters schedule nws_123 --scheduled-at 2026-08-01T09:00:00.000Z
|
|
56
|
+
shipmail newsletter-assets list --kind image
|
|
57
|
+
shipmail newsletter-assets upload ./hero.png --content-type image/png
|
|
58
|
+
shipmail newsletter-assets register-url https://cdn.shipmail.to/newsletter-images/org_123/hero.png
|
|
59
|
+
|
|
43
60
|
# Manage webhooks
|
|
44
61
|
shipmail webhooks create \
|
|
45
62
|
--url https://example.com/shipmail/webhook \
|
|
@@ -47,6 +64,8 @@ shipmail webhooks create \
|
|
|
47
64
|
shipmail webhooks deliveries whk_123 --status failed
|
|
48
65
|
```
|
|
49
66
|
|
|
67
|
+
`newsletter-assets list` includes current newsletter media storage usage in the JSON response.
|
|
68
|
+
|
|
50
69
|
## Generic API Command
|
|
51
70
|
|
|
52
71
|
The `api` command reaches every Shipmail REST endpoint, including endpoints that do not have a shorthand command yet.
|
|
@@ -73,5 +92,8 @@ Use repeatable `--header "Name: value"` flags for request headers. Use `--output
|
|
|
73
92
|
- `messages send|list|get`
|
|
74
93
|
- `webhooks list|create|test|deliveries`
|
|
75
94
|
- `suppressions list|remove`
|
|
95
|
+
- `newsletter-domains list`
|
|
96
|
+
- `newsletters create|update|preview|preflight|test-send|schedule|cancel|resume`
|
|
97
|
+
- `newsletter-assets list|upload|register-url`
|
|
76
98
|
|
|
77
99
|
Run `shipmail --help` for flags and examples.
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "shipmail";
|
|
13
13
|
|
|
14
14
|
// src/version.ts
|
|
15
|
-
var VERSION = "0.1.
|
|
15
|
+
var VERSION = "0.1.6";
|
|
16
16
|
|
|
17
17
|
// src/cli.ts
|
|
18
18
|
var DEFAULT_BASE_URL = "https://shipmail.to/api/v1";
|
|
@@ -149,9 +149,95 @@ async function readJsonInput(value) {
|
|
|
149
149
|
try {
|
|
150
150
|
return JSON.parse(raw);
|
|
151
151
|
} catch {
|
|
152
|
-
throw new CliUsageError("
|
|
152
|
+
throw new CliUsageError("Value must be valid JSON or @path/to/file.json.");
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
+
async function readTextInput(value) {
|
|
156
|
+
if (value === void 0) return void 0;
|
|
157
|
+
if (value.startsWith("@")) return readFile(value.slice(1), "utf8");
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
async function readNewsletterBlocks(value) {
|
|
161
|
+
const parsed = await readJsonInput(value);
|
|
162
|
+
if (!Array.isArray(parsed)) {
|
|
163
|
+
throw new CliUsageError("--blocks must be a JSON array of newsletter blocks.");
|
|
164
|
+
}
|
|
165
|
+
if (parsed.length === 0) {
|
|
166
|
+
throw new CliUsageError("--blocks must include at least one block.");
|
|
167
|
+
}
|
|
168
|
+
return parsed;
|
|
169
|
+
}
|
|
170
|
+
function parseArchiveVisibility(options) {
|
|
171
|
+
const value = getString(options, "archive-visibility");
|
|
172
|
+
if (value === void 0) return void 0;
|
|
173
|
+
if (value === "private" || value === "public") return value;
|
|
174
|
+
throw new CliUsageError("--archive-visibility must be private or public.");
|
|
175
|
+
}
|
|
176
|
+
async function newsletterCreateParams(options) {
|
|
177
|
+
const audience_id = getRequiredString(options, "audience-id");
|
|
178
|
+
const newsletter_domain_id = getRequiredString(options, "newsletter-domain-id");
|
|
179
|
+
const name = getRequiredString(options, "name");
|
|
180
|
+
const subject = getRequiredString(options, "subject");
|
|
181
|
+
const blocksInput = getString(options, "blocks");
|
|
182
|
+
const bodyHtmlInput = getString(options, "body-html");
|
|
183
|
+
const bodyTextInput = getString(options, "body-text");
|
|
184
|
+
const blocks = blocksInput ? await readNewsletterBlocks(blocksInput) : void 0;
|
|
185
|
+
const body_html = bodyHtmlInput ? await readTextInput(bodyHtmlInput) : void 0;
|
|
186
|
+
const body_text = bodyTextInput ? await readTextInput(bodyTextInput) : void 0;
|
|
187
|
+
if (!blocks?.length && !body_html?.trim() && !body_text?.trim()) {
|
|
188
|
+
throw new CliUsageError("Provide at least one of --blocks, --body-html, or --body-text.");
|
|
189
|
+
}
|
|
190
|
+
const send_window_hours = getNumber(options, "send-window-hours");
|
|
191
|
+
const archive_visibility = parseArchiveVisibility(options);
|
|
192
|
+
const preview_text = getString(options, "preview-text");
|
|
193
|
+
const from_name = getString(options, "from-name");
|
|
194
|
+
return {
|
|
195
|
+
audience_id,
|
|
196
|
+
newsletter_domain_id,
|
|
197
|
+
name,
|
|
198
|
+
subject,
|
|
199
|
+
...preview_text !== void 0 ? { preview_text } : {},
|
|
200
|
+
...from_name !== void 0 ? { from_name } : {},
|
|
201
|
+
...blocks ? { blocks } : {},
|
|
202
|
+
...body_html !== void 0 ? { body_html } : {},
|
|
203
|
+
...body_text !== void 0 ? { body_text } : {},
|
|
204
|
+
...send_window_hours !== void 0 ? { send_window_hours } : {},
|
|
205
|
+
...archive_visibility !== void 0 ? { archive_visibility } : {}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
async function newsletterUpdateParams(options) {
|
|
209
|
+
const audience_id = getString(options, "audience-id");
|
|
210
|
+
const newsletter_domain_id = getString(options, "newsletter-domain-id");
|
|
211
|
+
const name = getString(options, "name");
|
|
212
|
+
const subject = getString(options, "subject");
|
|
213
|
+
const preview_text = getString(options, "preview-text");
|
|
214
|
+
const from_name = getString(options, "from-name");
|
|
215
|
+
const send_window_hours = getNumber(options, "send-window-hours");
|
|
216
|
+
const archive_visibility = parseArchiveVisibility(options);
|
|
217
|
+
const blocksInput = getString(options, "blocks");
|
|
218
|
+
const bodyHtmlInput = getString(options, "body-html");
|
|
219
|
+
const bodyTextInput = getString(options, "body-text");
|
|
220
|
+
const blocks = blocksInput ? await readNewsletterBlocks(blocksInput) : void 0;
|
|
221
|
+
const body_html = bodyHtmlInput ? await readTextInput(bodyHtmlInput) : void 0;
|
|
222
|
+
const body_text = bodyTextInput ? await readTextInput(bodyTextInput) : void 0;
|
|
223
|
+
const params = {
|
|
224
|
+
...audience_id ? { audience_id } : {},
|
|
225
|
+
...newsletter_domain_id ? { newsletter_domain_id } : {},
|
|
226
|
+
...name ? { name } : {},
|
|
227
|
+
...subject ? { subject } : {},
|
|
228
|
+
...preview_text !== void 0 ? { preview_text } : {},
|
|
229
|
+
...from_name !== void 0 ? { from_name } : {},
|
|
230
|
+
...blocks ? { blocks } : {},
|
|
231
|
+
...body_html !== void 0 ? { body_html } : {},
|
|
232
|
+
...body_text !== void 0 ? { body_text } : {},
|
|
233
|
+
...send_window_hours !== void 0 ? { send_window_hours } : {},
|
|
234
|
+
...archive_visibility !== void 0 ? { archive_visibility } : {}
|
|
235
|
+
};
|
|
236
|
+
if (Object.keys(params).length === 0) {
|
|
237
|
+
throw new CliUsageError("Provide at least one field to update.");
|
|
238
|
+
}
|
|
239
|
+
return params;
|
|
240
|
+
}
|
|
155
241
|
function apiPath(rawPath) {
|
|
156
242
|
if (!rawPath) throw new CliUsageError("Missing API path.");
|
|
157
243
|
if (!rawPath.startsWith("/")) throw new CliUsageError("API path must start with '/'.");
|
|
@@ -242,6 +328,23 @@ Usage:
|
|
|
242
328
|
shipmail webhooks create --url https://example.com/hook --events message.received,message.bounced
|
|
243
329
|
shipmail suppressions list
|
|
244
330
|
shipmail suppressions remove <email>
|
|
331
|
+
shipmail newsletters create \\
|
|
332
|
+
--audience-id aud_123 \\
|
|
333
|
+
--newsletter-domain-id nwsdom_456 \\
|
|
334
|
+
--name "July changelog" \\
|
|
335
|
+
--subject "What shipped in July" \\
|
|
336
|
+
--blocks @blocks.json
|
|
337
|
+
shipmail newsletters update nws_789 --subject "Updated subject" --blocks @blocks-v2.json
|
|
338
|
+
shipmail newsletters preview <id>
|
|
339
|
+
shipmail newsletters preflight <id>
|
|
340
|
+
shipmail newsletters test-send <id> --recipient-email owner@example.com
|
|
341
|
+
shipmail newsletters schedule <id> --scheduled-at 2026-08-01T09:00:00.000Z
|
|
342
|
+
shipmail newsletters cancel <id>
|
|
343
|
+
shipmail newsletters resume <id>
|
|
344
|
+
shipmail newsletter-assets list [--kind image|video] [--q text] [--limit 25] [--cursor cur]
|
|
345
|
+
shipmail newsletter-assets upload <file> [--filename name] [--content-type type]
|
|
346
|
+
shipmail newsletter-assets register-url <url> [--filename name] [--thumbnail-url url]
|
|
347
|
+
shipmail newsletter-domains list [--limit 25] [--cursor cur]
|
|
245
348
|
shipmail audiences list [--limit 25] [--cursor cur]
|
|
246
349
|
shipmail audiences create <name> --consent-source "Signup form" [--description "..."]
|
|
247
350
|
shipmail audiences get <id>
|
|
@@ -253,6 +356,7 @@ Usage:
|
|
|
253
356
|
shipmail audiences subscribers by-email <audience-id> <email>
|
|
254
357
|
shipmail audiences subscribers update <audience-id> <subscriber-id> [--name "..."]
|
|
255
358
|
shipmail audiences subscribers unsubscribe <audience-id> <subscriber-id>
|
|
359
|
+
shipmail audiences subscribers resubscribe <audience-id> <subscriber-id> --consent-source "Signup form"
|
|
256
360
|
shipmail audiences subscribers remove <audience-id> <subscriber-id>
|
|
257
361
|
|
|
258
362
|
Global options:
|
|
@@ -675,6 +779,17 @@ async function runSubscribers(client, parsed, out) {
|
|
|
675
779
|
);
|
|
676
780
|
return;
|
|
677
781
|
}
|
|
782
|
+
if (action === "resubscribe") {
|
|
783
|
+
writeJson(
|
|
784
|
+
out,
|
|
785
|
+
await client.audiences.subscribers.resubscribe(
|
|
786
|
+
audienceId,
|
|
787
|
+
requirePositional(parsed.positional, 4, "subscriber id"),
|
|
788
|
+
{ consent_source: getRequiredString(parsed.options, "consent-source") }
|
|
789
|
+
)
|
|
790
|
+
);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
678
793
|
if (action === "remove") {
|
|
679
794
|
const subscriberId = requirePositional(parsed.positional, 4, "subscriber id");
|
|
680
795
|
await client.audiences.subscribers.remove(audienceId, subscriberId);
|
|
@@ -739,6 +854,126 @@ async function runAudiences(client, parsed, out) {
|
|
|
739
854
|
}
|
|
740
855
|
throw new CliUsageError("Unknown audiences command.");
|
|
741
856
|
}
|
|
857
|
+
async function runNewsletterDomains(client, parsed, out) {
|
|
858
|
+
const subcommand = parsed.positional[1];
|
|
859
|
+
if (subcommand === "list") {
|
|
860
|
+
writeJson(
|
|
861
|
+
out,
|
|
862
|
+
await client.newsletters.domains.list({
|
|
863
|
+
cursor: getString(parsed.options, "cursor"),
|
|
864
|
+
limit: getNumber(parsed.options, "limit")
|
|
865
|
+
})
|
|
866
|
+
);
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
throw new CliUsageError("Unknown newsletter-domains command.");
|
|
870
|
+
}
|
|
871
|
+
async function runNewsletters(client, parsed, out) {
|
|
872
|
+
const subcommand = parsed.positional[1];
|
|
873
|
+
if (subcommand === "create") {
|
|
874
|
+
writeJson(out, await client.newsletters.create(await newsletterCreateParams(parsed.options)));
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (subcommand === "update") {
|
|
878
|
+
const id = requirePositional(parsed.positional, 2, "newsletter id");
|
|
879
|
+
writeJson(
|
|
880
|
+
out,
|
|
881
|
+
await client.newsletters.update(id, await newsletterUpdateParams(parsed.options))
|
|
882
|
+
);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
if (subcommand === "preview") {
|
|
886
|
+
writeJson(
|
|
887
|
+
out,
|
|
888
|
+
await client.newsletters.preview(requirePositional(parsed.positional, 2, "newsletter id"))
|
|
889
|
+
);
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (subcommand === "preflight") {
|
|
893
|
+
writeJson(
|
|
894
|
+
out,
|
|
895
|
+
await client.newsletters.preflight(requirePositional(parsed.positional, 2, "newsletter id"))
|
|
896
|
+
);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (subcommand === "test-send") {
|
|
900
|
+
writeJson(
|
|
901
|
+
out,
|
|
902
|
+
await client.newsletters.sendTest(requirePositional(parsed.positional, 2, "newsletter id"), {
|
|
903
|
+
recipient_email: getRequiredString(parsed.options, "recipient-email")
|
|
904
|
+
})
|
|
905
|
+
);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
if (subcommand === "schedule") {
|
|
909
|
+
writeJson(
|
|
910
|
+
out,
|
|
911
|
+
await client.newsletters.schedule(requirePositional(parsed.positional, 2, "newsletter id"), {
|
|
912
|
+
scheduled_at: getRequiredString(parsed.options, "scheduled-at")
|
|
913
|
+
})
|
|
914
|
+
);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
if (subcommand === "cancel") {
|
|
918
|
+
writeJson(
|
|
919
|
+
out,
|
|
920
|
+
await client.newsletters.cancel(requirePositional(parsed.positional, 2, "newsletter id"))
|
|
921
|
+
);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (subcommand === "resume") {
|
|
925
|
+
writeJson(
|
|
926
|
+
out,
|
|
927
|
+
await client.newsletters.resume(requirePositional(parsed.positional, 2, "newsletter id"))
|
|
928
|
+
);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
throw new CliUsageError("Unknown newsletters command.");
|
|
932
|
+
}
|
|
933
|
+
async function runNewsletterAssets(client, parsed, out) {
|
|
934
|
+
const subcommand = parsed.positional[1];
|
|
935
|
+
if (subcommand === "list") {
|
|
936
|
+
const kind = getString(parsed.options, "kind");
|
|
937
|
+
if (kind !== void 0 && kind !== "image" && kind !== "video") {
|
|
938
|
+
throw new CliUsageError("--kind must be image or video.");
|
|
939
|
+
}
|
|
940
|
+
writeJson(
|
|
941
|
+
out,
|
|
942
|
+
await client.newsletters.assets.list({
|
|
943
|
+
cursor: getString(parsed.options, "cursor"),
|
|
944
|
+
limit: getNumber(parsed.options, "limit"),
|
|
945
|
+
kind,
|
|
946
|
+
q: getString(parsed.options, "q")
|
|
947
|
+
})
|
|
948
|
+
);
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
if (subcommand === "upload") {
|
|
952
|
+
const filePath = requirePositional(parsed.positional, 2, "file");
|
|
953
|
+
const data = await readFile(realpathSync(filePath));
|
|
954
|
+
writeJson(
|
|
955
|
+
out,
|
|
956
|
+
await client.newsletters.assets.upload({
|
|
957
|
+
filename: getString(parsed.options, "filename") ?? filePath.split(/[\\/]/).at(-1) ?? "newsletter-asset",
|
|
958
|
+
data,
|
|
959
|
+
content_type: getString(parsed.options, "content-type")
|
|
960
|
+
})
|
|
961
|
+
);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
if (subcommand === "register-url") {
|
|
965
|
+
writeJson(
|
|
966
|
+
out,
|
|
967
|
+
await client.newsletters.assets.registerFromUrl({
|
|
968
|
+
url: requirePositional(parsed.positional, 2, "url"),
|
|
969
|
+
filename: getString(parsed.options, "filename"),
|
|
970
|
+
thumbnail_url: getString(parsed.options, "thumbnail-url")
|
|
971
|
+
})
|
|
972
|
+
);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
throw new CliUsageError("Unknown newsletter-assets command.");
|
|
976
|
+
}
|
|
742
977
|
function writeError(err, errOut) {
|
|
743
978
|
if (err instanceof CliUsageError) {
|
|
744
979
|
errOut.write(`Error: ${err.message}
|
|
@@ -753,9 +988,10 @@ ${JSON.stringify(err.body, null, 2)}
|
|
|
753
988
|
return;
|
|
754
989
|
}
|
|
755
990
|
if (err instanceof ShipMailError) {
|
|
756
|
-
const
|
|
757
|
-
const
|
|
758
|
-
|
|
991
|
+
const shipmailError = err;
|
|
992
|
+
const status = shipmailError.status !== void 0 ? ` (HTTP ${shipmailError.status})` : "";
|
|
993
|
+
const requestId = shipmailError.requestId ? ` request_id=${shipmailError.requestId}` : "";
|
|
994
|
+
errOut.write(`Error: ${shipmailError.message}${status}${requestId}
|
|
759
995
|
`);
|
|
760
996
|
return;
|
|
761
997
|
}
|
|
@@ -797,6 +1033,9 @@ async function runCli(argv, options = {}) {
|
|
|
797
1033
|
else if (command === "messages") await runMessages(client, parsed, out);
|
|
798
1034
|
else if (command === "webhooks") await runWebhooks(client, parsed, out);
|
|
799
1035
|
else if (command === "suppressions") await runSuppressions(client, parsed, out);
|
|
1036
|
+
else if (command === "newsletters") await runNewsletters(client, parsed, out);
|
|
1037
|
+
else if (command === "newsletter-assets") await runNewsletterAssets(client, parsed, out);
|
|
1038
|
+
else if (command === "newsletter-domains") await runNewsletterDomains(client, parsed, out);
|
|
800
1039
|
else if (command === "audiences") await runAudiences(client, parsed, out);
|
|
801
1040
|
else throw new CliUsageError(`Unknown command: ${command}.`);
|
|
802
1041
|
return 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shipmail-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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.1.
|
|
44
|
+
"shipmail": "0.1.29"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"tsup": "^8.4.0",
|