shipmail-cli 0.1.4 → 0.1.5
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 +19 -0
- package/dist/cli.js +220 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -40,6 +40,22 @@ 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
|
+
|
|
43
59
|
# Manage webhooks
|
|
44
60
|
shipmail webhooks create \
|
|
45
61
|
--url https://example.com/shipmail/webhook \
|
|
@@ -73,5 +89,8 @@ Use repeatable `--header "Name: value"` flags for request headers. Use `--output
|
|
|
73
89
|
- `messages send|list|get`
|
|
74
90
|
- `webhooks list|create|test|deliveries`
|
|
75
91
|
- `suppressions list|remove`
|
|
92
|
+
- `newsletter-domains list`
|
|
93
|
+
- `newsletters create|update|preview|preflight|test-send|schedule|cancel|resume`
|
|
94
|
+
- `newsletter-assets list|upload`
|
|
76
95
|
|
|
77
96
|
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.5";
|
|
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,22 @@ 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-domains list [--limit 25] [--cursor cur]
|
|
245
347
|
shipmail audiences list [--limit 25] [--cursor cur]
|
|
246
348
|
shipmail audiences create <name> --consent-source "Signup form" [--description "..."]
|
|
247
349
|
shipmail audiences get <id>
|
|
@@ -739,6 +841,115 @@ async function runAudiences(client, parsed, out) {
|
|
|
739
841
|
}
|
|
740
842
|
throw new CliUsageError("Unknown audiences command.");
|
|
741
843
|
}
|
|
844
|
+
async function runNewsletterDomains(client, parsed, out) {
|
|
845
|
+
const subcommand = parsed.positional[1];
|
|
846
|
+
if (subcommand === "list") {
|
|
847
|
+
writeJson(
|
|
848
|
+
out,
|
|
849
|
+
await client.newsletters.domains.list({
|
|
850
|
+
cursor: getString(parsed.options, "cursor"),
|
|
851
|
+
limit: getNumber(parsed.options, "limit")
|
|
852
|
+
})
|
|
853
|
+
);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
throw new CliUsageError("Unknown newsletter-domains command.");
|
|
857
|
+
}
|
|
858
|
+
async function runNewsletters(client, parsed, out) {
|
|
859
|
+
const subcommand = parsed.positional[1];
|
|
860
|
+
if (subcommand === "create") {
|
|
861
|
+
writeJson(out, await client.newsletters.create(await newsletterCreateParams(parsed.options)));
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
if (subcommand === "update") {
|
|
865
|
+
const id = requirePositional(parsed.positional, 2, "newsletter id");
|
|
866
|
+
writeJson(
|
|
867
|
+
out,
|
|
868
|
+
await client.newsletters.update(id, await newsletterUpdateParams(parsed.options))
|
|
869
|
+
);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
if (subcommand === "preview") {
|
|
873
|
+
writeJson(
|
|
874
|
+
out,
|
|
875
|
+
await client.newsletters.preview(requirePositional(parsed.positional, 2, "newsletter id"))
|
|
876
|
+
);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
if (subcommand === "preflight") {
|
|
880
|
+
writeJson(
|
|
881
|
+
out,
|
|
882
|
+
await client.newsletters.preflight(requirePositional(parsed.positional, 2, "newsletter id"))
|
|
883
|
+
);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
if (subcommand === "test-send") {
|
|
887
|
+
writeJson(
|
|
888
|
+
out,
|
|
889
|
+
await client.newsletters.sendTest(requirePositional(parsed.positional, 2, "newsletter id"), {
|
|
890
|
+
recipient_email: getRequiredString(parsed.options, "recipient-email")
|
|
891
|
+
})
|
|
892
|
+
);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (subcommand === "schedule") {
|
|
896
|
+
writeJson(
|
|
897
|
+
out,
|
|
898
|
+
await client.newsletters.schedule(requirePositional(parsed.positional, 2, "newsletter id"), {
|
|
899
|
+
scheduled_at: getRequiredString(parsed.options, "scheduled-at")
|
|
900
|
+
})
|
|
901
|
+
);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
if (subcommand === "cancel") {
|
|
905
|
+
writeJson(
|
|
906
|
+
out,
|
|
907
|
+
await client.newsletters.cancel(requirePositional(parsed.positional, 2, "newsletter id"))
|
|
908
|
+
);
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
if (subcommand === "resume") {
|
|
912
|
+
writeJson(
|
|
913
|
+
out,
|
|
914
|
+
await client.newsletters.resume(requirePositional(parsed.positional, 2, "newsletter id"))
|
|
915
|
+
);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
throw new CliUsageError("Unknown newsletters command.");
|
|
919
|
+
}
|
|
920
|
+
async function runNewsletterAssets(client, parsed, out) {
|
|
921
|
+
const subcommand = parsed.positional[1];
|
|
922
|
+
if (subcommand === "list") {
|
|
923
|
+
const kind = getString(parsed.options, "kind");
|
|
924
|
+
if (kind !== void 0 && kind !== "image" && kind !== "video") {
|
|
925
|
+
throw new CliUsageError("--kind must be image or video.");
|
|
926
|
+
}
|
|
927
|
+
writeJson(
|
|
928
|
+
out,
|
|
929
|
+
await client.newsletters.assets.list({
|
|
930
|
+
cursor: getString(parsed.options, "cursor"),
|
|
931
|
+
limit: getNumber(parsed.options, "limit"),
|
|
932
|
+
kind,
|
|
933
|
+
q: getString(parsed.options, "q")
|
|
934
|
+
})
|
|
935
|
+
);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (subcommand === "upload") {
|
|
939
|
+
const filePath = requirePositional(parsed.positional, 2, "file");
|
|
940
|
+
const data = await readFile(realpathSync(filePath));
|
|
941
|
+
writeJson(
|
|
942
|
+
out,
|
|
943
|
+
await client.newsletters.assets.upload({
|
|
944
|
+
filename: getString(parsed.options, "filename") ?? filePath.split(/[\\/]/).at(-1) ?? "newsletter-asset",
|
|
945
|
+
data,
|
|
946
|
+
content_type: getString(parsed.options, "content-type")
|
|
947
|
+
})
|
|
948
|
+
);
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
throw new CliUsageError("Unknown newsletter-assets command.");
|
|
952
|
+
}
|
|
742
953
|
function writeError(err, errOut) {
|
|
743
954
|
if (err instanceof CliUsageError) {
|
|
744
955
|
errOut.write(`Error: ${err.message}
|
|
@@ -753,9 +964,10 @@ ${JSON.stringify(err.body, null, 2)}
|
|
|
753
964
|
return;
|
|
754
965
|
}
|
|
755
966
|
if (err instanceof ShipMailError) {
|
|
756
|
-
const
|
|
757
|
-
const
|
|
758
|
-
|
|
967
|
+
const shipmailError = err;
|
|
968
|
+
const status = shipmailError.status !== void 0 ? ` (HTTP ${shipmailError.status})` : "";
|
|
969
|
+
const requestId = shipmailError.requestId ? ` request_id=${shipmailError.requestId}` : "";
|
|
970
|
+
errOut.write(`Error: ${shipmailError.message}${status}${requestId}
|
|
759
971
|
`);
|
|
760
972
|
return;
|
|
761
973
|
}
|
|
@@ -797,6 +1009,9 @@ async function runCli(argv, options = {}) {
|
|
|
797
1009
|
else if (command === "messages") await runMessages(client, parsed, out);
|
|
798
1010
|
else if (command === "webhooks") await runWebhooks(client, parsed, out);
|
|
799
1011
|
else if (command === "suppressions") await runSuppressions(client, parsed, out);
|
|
1012
|
+
else if (command === "newsletters") await runNewsletters(client, parsed, out);
|
|
1013
|
+
else if (command === "newsletter-assets") await runNewsletterAssets(client, parsed, out);
|
|
1014
|
+
else if (command === "newsletter-domains") await runNewsletterDomains(client, parsed, out);
|
|
800
1015
|
else if (command === "audiences") await runAudiences(client, parsed, out);
|
|
801
1016
|
else throw new CliUsageError(`Unknown command: ${command}.`);
|
|
802
1017
|
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.5",
|
|
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.26"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"tsup": "^8.4.0",
|