shipmail-cli 0.6.5 → 0.6.7
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 +38 -1
- package/dist/cli.js +285 -12
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -149,6 +149,20 @@ shipmail audiences welcome add-step aud_123 --subject "Welcome" --markdown @welc
|
|
|
149
149
|
shipmail audiences welcome add-step aud_123 --subject "Our best posts" --markdown @day-3.md --wait-minutes 4320
|
|
150
150
|
shipmail audiences welcome enable aud_123
|
|
151
151
|
|
|
152
|
+
# Store a template, publish it, then send it by ID
|
|
153
|
+
shipmail templates create \
|
|
154
|
+
--name "Welcome" \
|
|
155
|
+
--subject "Welcome, {{name}}" \
|
|
156
|
+
--html @welcome.html \
|
|
157
|
+
--fallback plan=Free \
|
|
158
|
+
--publish
|
|
159
|
+
shipmail templates render tpl_123 --var name=Ada
|
|
160
|
+
shipmail messages send --from hello@example.com --to ada@example.com --template tpl_123 --var name=Ada
|
|
161
|
+
shipmail templates update tpl_123 --subject "Welcome aboard, {{name}}"
|
|
162
|
+
shipmail templates publish tpl_123
|
|
163
|
+
shipmail templates versions tpl_123
|
|
164
|
+
shipmail templates restore tpl_123 1
|
|
165
|
+
|
|
152
166
|
# Manage webhooks
|
|
153
167
|
shipmail webhooks create \
|
|
154
168
|
--url https://example.com/shipmail/webhook \
|
|
@@ -170,6 +184,11 @@ Shipmail converts headings, paragraphs, lists, quotes, code, images, links, divi
|
|
|
170
184
|
to blocks. A callout starts with a line `:::callout` (optionally `info`, `warning` or `success`,
|
|
171
185
|
then a title) and ends with a line `:::`.
|
|
172
186
|
|
|
187
|
+
With `messages send --template`, the template supplies the subject and body, so `--subject`,
|
|
188
|
+
`--html` and `--text` are refused. `--var key=value` sets a variable (repeat it; values are text),
|
|
189
|
+
and `--template-version` pins a version instead of the published one. A send missing a required
|
|
190
|
+
variable fails with HTTP 422 and prints each detail with its code, such as `missing_variable`.
|
|
191
|
+
|
|
173
192
|
`newsletter-assets upload` uses a five-minute direct storage PUT, then calls the single-use
|
|
174
193
|
completion URL. Video uploads require a JPEG poster through `--thumbnail`. `newsletter-assets list`
|
|
175
194
|
includes current newsletter media storage usage and a `pagination.next_cursor` value in the JSON
|
|
@@ -191,7 +210,9 @@ Use repeatable `--header "Name: value"` flags for request headers. Use `--output
|
|
|
191
210
|
- `api <METHOD> <PATH>`
|
|
192
211
|
- `domains list|search|create|get|verify|delete`
|
|
193
212
|
- `mailboxes list|create|get|update|suspend|resume|forwarding|forwarding-add|forwarding-remove|app-passwords|folders|identities|stage-attachment|prepare-attachment-upload`
|
|
194
|
-
- `members list|get`
|
|
213
|
+
- `members list|get|invite`
|
|
214
|
+
- `members invitations list|cancel`
|
|
215
|
+
- `members mailboxes grant|revoke`
|
|
195
216
|
- `inbox list|message|security-review|threads|thread|thread-state|draft|send-draft|reply-message|reply-thread|update|move`
|
|
196
217
|
- `reply-scans create|status|results`
|
|
197
218
|
- `messages send|list|analytics|get|reply` (test keys also support `--sandbox-outcome`)
|
|
@@ -199,6 +220,7 @@ Use repeatable `--header "Name: value"` flags for request headers. Use `--output
|
|
|
199
220
|
- `mailboxes sandbox-inbound`
|
|
200
221
|
- `webhooks list|create|update|test|deliveries|replay-delivery`
|
|
201
222
|
- `suppressions add|list|remove`
|
|
223
|
+
- `templates list|create|get|update|delete|publish|render|versions|version|restore` (`messages send --template` sends one)
|
|
202
224
|
- `audiences list|create|get|update|delete`
|
|
203
225
|
- `audiences feed get|update|rotate|revoke`
|
|
204
226
|
- `audiences welcome get|enable|disable|add-step|update-step|remove-step`
|
|
@@ -212,6 +234,15 @@ Member output uses the roles `owner`, `admin`, and `member`. Its `mailbox_access
|
|
|
212
234
|
`selected`; `mailbox_ids` is empty for `all`, while selected IDs may be limited to mailboxes the
|
|
213
235
|
API key can reach.
|
|
214
236
|
|
|
237
|
+
`members invite`, `members invitations cancel` and `members mailboxes grant|revoke` need the
|
|
238
|
+
`members:write` permission, which no preset includes. The invitee receives an email and creates
|
|
239
|
+
their own account; the CLI never receives their credentials:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
shipmail members invite sam@example.com --role member --mailbox mbx_support --newsletter-editor
|
|
243
|
+
shipmail members mailboxes grant mem_sam mbx_sales
|
|
244
|
+
```
|
|
245
|
+
|
|
215
246
|
Configure a publisher-owned Atom self URL and the number of recent newsletters:
|
|
216
247
|
|
|
217
248
|
```bash
|
|
@@ -221,3 +252,9 @@ shipmail audiences feed update aud_123 \
|
|
|
221
252
|
```
|
|
222
253
|
|
|
223
254
|
Run `shipmail --help` for flags and examples.
|
|
255
|
+
|
|
256
|
+
## Links
|
|
257
|
+
|
|
258
|
+
- [CLI guide](https://shipmail.to/docs/cli)
|
|
259
|
+
- [Source](https://github.com/shipmail-to/shipmail-node/tree/main/packages/shipmail-cli)
|
|
260
|
+
- [Issues](https://github.com/shipmail-to/shipmail-node/issues)
|
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ReconciliationRequiredError,
|
|
13
13
|
ShipmailClient,
|
|
14
14
|
ShipmailError,
|
|
15
|
+
ValidationError,
|
|
15
16
|
WEBHOOK_EVENT_TYPES
|
|
16
17
|
} from "shipmail";
|
|
17
18
|
|
|
@@ -351,6 +352,7 @@ var OAuthError = class extends Error {
|
|
|
351
352
|
this.code = code;
|
|
352
353
|
this.name = "OAuthError";
|
|
353
354
|
}
|
|
355
|
+
code;
|
|
354
356
|
};
|
|
355
357
|
function trimSlash(value) {
|
|
356
358
|
return value.replace(/\/+$/, "");
|
|
@@ -650,7 +652,7 @@ async function login(options) {
|
|
|
650
652
|
}
|
|
651
653
|
|
|
652
654
|
// src/version.ts
|
|
653
|
-
var VERSION = "0.6.
|
|
655
|
+
var VERSION = "0.6.7";
|
|
654
656
|
|
|
655
657
|
// src/cli.ts
|
|
656
658
|
var DEFAULT_BASE_URL = "https://shipmail.to/api/v1";
|
|
@@ -686,6 +688,8 @@ var CliHttpError = class extends Error {
|
|
|
686
688
|
this.body = body;
|
|
687
689
|
this.name = "CliHttpError";
|
|
688
690
|
}
|
|
691
|
+
status;
|
|
692
|
+
body;
|
|
689
693
|
};
|
|
690
694
|
function parseCliArgs(argv) {
|
|
691
695
|
const positional = [];
|
|
@@ -771,6 +775,20 @@ function getStringList(options, key) {
|
|
|
771
775
|
if (isStringArray(value)) return value.flatMap(splitValues);
|
|
772
776
|
return splitValues(value);
|
|
773
777
|
}
|
|
778
|
+
function getKeyValues(options, key) {
|
|
779
|
+
const value = optionValue(options, key);
|
|
780
|
+
if (value === void 0) return void 0;
|
|
781
|
+
const entries = isStringArray(value) ? value : [value];
|
|
782
|
+
const result = {};
|
|
783
|
+
for (const entry of entries) {
|
|
784
|
+
const index = typeof entry === "string" ? entry.indexOf("=") : -1;
|
|
785
|
+
if (typeof entry !== "string" || index <= 0) {
|
|
786
|
+
throw new CliUsageError(`--${key} must use key=value format.`);
|
|
787
|
+
}
|
|
788
|
+
result[entry.slice(0, index).trim()] = entry.slice(index + 1);
|
|
789
|
+
}
|
|
790
|
+
return result;
|
|
791
|
+
}
|
|
774
792
|
function validateBaseUrl(rawValue, allowInsecure) {
|
|
775
793
|
let parsed;
|
|
776
794
|
try {
|
|
@@ -1252,6 +1270,11 @@ Usage:
|
|
|
1252
1270
|
shipmail mailboxes sandbox-inbound <id> --from sender@example.com --subject "Test" (--text "Hello" | --html "<p>Hello</p>") [--to support@example.com]
|
|
1253
1271
|
shipmail members list [--limit 25] [--cursor cur]
|
|
1254
1272
|
shipmail members get <id>
|
|
1273
|
+
shipmail members invitations list
|
|
1274
|
+
shipmail members invite <email> --role admin|member [--all-mailboxes] [--mailbox <mailbox-id> ...] [--newsletter-editor] [--idempotency-key key]
|
|
1275
|
+
shipmail members invitations cancel <invitation-id>
|
|
1276
|
+
shipmail members mailboxes grant <member-id> <mailbox-id>
|
|
1277
|
+
shipmail members mailboxes revoke <member-id> <mailbox-id>
|
|
1255
1278
|
shipmail inbox list <mailbox-id> [--folder-role inbox] [--search text] [--from text] [--to text] [--subject text] [--has-attachment [true|false]] [--after ISO] [--before ISO] [--limit 25] [--cursor cur]
|
|
1256
1279
|
shipmail inbox message <mailbox-id> <message-id>
|
|
1257
1280
|
shipmail inbox security-review <mailbox-id> <message-id> --reason "Reviewed" --expected-content-sha256 HASH
|
|
@@ -1265,15 +1288,16 @@ Usage:
|
|
|
1265
1288
|
shipmail inbox update-draft <mailbox-id> <thread-id> <draft-id> (--text "Hello" | --html "<p>Hello</p>") (the new body replaces the old one)
|
|
1266
1289
|
shipmail inbox delete-draft <mailbox-id> <thread-id> <draft-id>
|
|
1267
1290
|
shipmail inbox send-draft <mailbox-id> <thread-id> <draft-id> [--expected-content-sha256 hash]
|
|
1268
|
-
shipmail inbox reply-message <mailbox-id> <message-id> --to user@example.com (--text "Hello" | --html "<p>Hello</p>") [--track-reply true|false]
|
|
1269
|
-
shipmail inbox reply-thread <mailbox-id> <thread-id> --to user@example.com (--text "Hello" | --html "<p>Hello</p>") [--track-reply true|false]
|
|
1291
|
+
shipmail inbox reply-message <mailbox-id> <message-id> --to user@example.com [--cc ...] [--bcc ...] (--text "Hello" | --html "<p>Hello</p>") [--track-reply true|false]
|
|
1292
|
+
shipmail inbox reply-thread <mailbox-id> <thread-id> --to user@example.com [--cc ...] [--bcc ...] (--text "Hello" | --html "<p>Hello</p>") [--track-reply true|false]
|
|
1270
1293
|
shipmail inbox update <mailbox-id> <message-id> [--read true] [--starred false]
|
|
1271
1294
|
shipmail inbox move <mailbox-id> <message-id> (--target-role archive | --target-folder-id fld)
|
|
1272
1295
|
shipmail messages send (--mailbox-id mbx | --from 'Acme Support <hello@example.com>') --to user@example.com --subject "Hi" --text "Hello" [--track-reply true|false] [--sandbox-outcome delivered|bounced|complained] [--client-reference crm-123] [--metadata JSON|@file] [--source-rfc-message-id '<crm-123@example.com>'] [--header 'X-CRM-ID: 123']
|
|
1296
|
+
shipmail messages send (--mailbox-id mbx | --from hello@example.com) --to user@example.com --template tpl_... [--template-version 2] [--var name=Ada ...]
|
|
1273
1297
|
shipmail messages list (--mailbox-id mbx | --client-reference crm-123) [--limit 25] [--cursor cur]
|
|
1274
1298
|
shipmail messages analytics [--updated-after ISO] [--updated-before ISO] [--limit 100] [--cursor cur]
|
|
1275
1299
|
shipmail messages get <message-id>
|
|
1276
|
-
shipmail messages reply <message-id> --to user@example.com (--text "Hello" | --html "<p>Hello</p>") [--track-reply true|false] [--client-reference crm-reply-123]
|
|
1300
|
+
shipmail messages reply <message-id> --to user@example.com [--cc ...] [--bcc ...] (--text "Hello" | --html "<p>Hello</p>") [--track-reply true|false] [--client-reference crm-reply-123]
|
|
1277
1301
|
shipmail scheduled-messages list [--include-held] [--search text] [--limit 100]
|
|
1278
1302
|
shipmail scheduled-messages get <message-id>
|
|
1279
1303
|
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_...]
|
|
@@ -1288,6 +1312,16 @@ Usage:
|
|
|
1288
1312
|
shipmail suppressions add <email> [--scope all|marketing]
|
|
1289
1313
|
shipmail suppressions list [--scope all|marketing] [--limit 25] [--cursor cur]
|
|
1290
1314
|
shipmail suppressions remove <email>
|
|
1315
|
+
shipmail templates list [--limit 25] [--cursor cur]
|
|
1316
|
+
shipmail templates create --name "Welcome" --subject "Welcome, {{name}}" (--html "<p>Hi {{name}}</p>" | --text "Hi {{name}}") [--fallback name=there ...] [--publish]
|
|
1317
|
+
shipmail templates get <template-id>
|
|
1318
|
+
shipmail templates update <template-id> [--name "..."] [--subject "..."] [--html "..." | --clear-html] [--text "..." | --clear-text] [--fallback key=value ... | --clear-fallbacks]
|
|
1319
|
+
shipmail templates delete <template-id>
|
|
1320
|
+
shipmail templates publish <template-id>
|
|
1321
|
+
shipmail templates render <template-id> [--var name=Ada ...] [--template-version 2]
|
|
1322
|
+
shipmail templates versions <template-id> [--limit 25] [--cursor cur]
|
|
1323
|
+
shipmail templates version <template-id> <version>
|
|
1324
|
+
shipmail templates restore <template-id> <version>
|
|
1291
1325
|
shipmail newsletters create \\
|
|
1292
1326
|
--audience-id aud_123 \\
|
|
1293
1327
|
--sender-identity-id nwsid_456 \\
|
|
@@ -1362,6 +1396,17 @@ One-off booking links:
|
|
|
1362
1396
|
Revoking a link stops it at once and cannot be undone. Other flags: --description,
|
|
1363
1397
|
--buffer-minutes, --minimum-notice-minutes, --conferencing-provider, --mailbox.
|
|
1364
1398
|
|
|
1399
|
+
Templates:
|
|
1400
|
+
{{key}} in the subject, html or text is a variable. --html and --text take the value or @file.
|
|
1401
|
+
--fallback key=value Default for a variable. Repeat it. A variable without one is required
|
|
1402
|
+
at send time. On update, the given fallbacks replace all of them.
|
|
1403
|
+
--publish On create, also publish the draft as version 1.
|
|
1404
|
+
update changes the draft; publish makes it the next version, which sends use. restore copies a
|
|
1405
|
+
version into the draft without publishing it. render previews the draft, or --template-version.
|
|
1406
|
+
messages send --template <id> sends the published version, or --template-version. --var
|
|
1407
|
+
key=value sets a variable (repeat it; values are text). --subject, --html and --text are refused
|
|
1408
|
+
with --template.
|
|
1409
|
+
|
|
1365
1410
|
Conversation IDs:
|
|
1366
1411
|
<thread-id> accepts a conversation ID (thd_...) or the mail-server thread ID. Store the
|
|
1367
1412
|
conversation_id every response carries. thread_id is deprecated and unchanged.
|
|
@@ -1370,6 +1415,11 @@ Member access output:
|
|
|
1370
1415
|
role is owner, admin, or member. mailbox_access.scope is all or selected. mailbox_ids is empty
|
|
1371
1416
|
for all; selected IDs may be limited to mailboxes the API key can reach.
|
|
1372
1417
|
|
|
1418
|
+
Member invitations and mailbox sharing:
|
|
1419
|
+
Needs the members:write permission, which is never granted by default. The invitee receives an
|
|
1420
|
+
email and creates their own account; no credential is returned. Without --all-mailboxes the
|
|
1421
|
+
invitation shares only the --mailbox IDs. --newsletter-editor applies to the member role only.
|
|
1422
|
+
|
|
1373
1423
|
Newsletter styling:
|
|
1374
1424
|
--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.
|
|
1375
1425
|
|
|
@@ -1577,24 +1627,17 @@ async function sendMessageParams(options) {
|
|
|
1577
1627
|
const cc = messageRecipients(options, "cc");
|
|
1578
1628
|
const bcc = messageRecipients(options, "bcc");
|
|
1579
1629
|
const replyTo = getString(options, "reply-to");
|
|
1580
|
-
const subject = getRequiredString(options, "subject");
|
|
1581
|
-
const text = getString(options, "text");
|
|
1582
|
-
const html = getString(options, "html");
|
|
1583
1630
|
const headers = outboundMessageHeaders(options);
|
|
1584
1631
|
const metadata = await messageMetadata(options);
|
|
1585
1632
|
if (!mailboxId && !from) throw new CliUsageError("Provide --mailbox-id or --from.");
|
|
1586
1633
|
if (to.length === 0) throw new CliUsageError("Provide at least one --to recipient.");
|
|
1587
|
-
|
|
1588
|
-
return {
|
|
1634
|
+
const common = {
|
|
1589
1635
|
...mailboxId ? { mailbox_id: mailboxId } : {},
|
|
1590
1636
|
...from ? { from } : {},
|
|
1591
1637
|
to,
|
|
1592
1638
|
...cc.length > 0 ? { cc } : {},
|
|
1593
1639
|
...bcc.length > 0 ? { bcc } : {},
|
|
1594
1640
|
...replyTo ? { reply_to: replyTo } : {},
|
|
1595
|
-
subject,
|
|
1596
|
-
...html ? { html } : {},
|
|
1597
|
-
...text ? { text } : {},
|
|
1598
1641
|
...getString(options, "client-reference") ? { client_reference: getString(options, "client-reference") } : {},
|
|
1599
1642
|
...metadata ? { metadata } : {},
|
|
1600
1643
|
...getString(options, "source-rfc-message-id") ? { source_rfc_message_id: getString(options, "source-rfc-message-id") } : {},
|
|
@@ -1613,10 +1656,44 @@ async function sendMessageParams(options) {
|
|
|
1613
1656
|
])
|
|
1614
1657
|
} : {}
|
|
1615
1658
|
};
|
|
1659
|
+
const templateId = getString(options, "template");
|
|
1660
|
+
const templateVersion = getNumber(options, "template-version");
|
|
1661
|
+
const variables = getKeyValues(options, "var");
|
|
1662
|
+
if (templateId) {
|
|
1663
|
+
for (const key of ["subject", "html", "text"]) {
|
|
1664
|
+
if (optionValue(options, key) !== void 0) {
|
|
1665
|
+
throw new CliUsageError(
|
|
1666
|
+
`--${key} cannot be used with --template: the template supplies the subject and body.`
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
return {
|
|
1671
|
+
...common,
|
|
1672
|
+
template: {
|
|
1673
|
+
id: templateId,
|
|
1674
|
+
...templateVersion !== void 0 ? { version: templateVersion } : {},
|
|
1675
|
+
...variables ? { variables } : {}
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
if (templateVersion !== void 0 || variables) {
|
|
1680
|
+
throw new CliUsageError("--template-version and --var need --template.");
|
|
1681
|
+
}
|
|
1682
|
+
const subject = getRequiredString(options, "subject");
|
|
1683
|
+
const text = getString(options, "text");
|
|
1684
|
+
const html = getString(options, "html");
|
|
1685
|
+
if (!text && !html) throw new CliUsageError("Provide --text or --html.");
|
|
1686
|
+
return {
|
|
1687
|
+
...common,
|
|
1688
|
+
subject,
|
|
1689
|
+
...html ? { html } : {},
|
|
1690
|
+
...text ? { text } : {}
|
|
1691
|
+
};
|
|
1616
1692
|
}
|
|
1617
1693
|
async function replyToMessageParams(options) {
|
|
1618
1694
|
const to = messageRecipients(options, "to");
|
|
1619
1695
|
const cc = messageRecipients(options, "cc");
|
|
1696
|
+
const bcc = messageRecipients(options, "bcc");
|
|
1620
1697
|
const text = getString(options, "text");
|
|
1621
1698
|
const html = getString(options, "html");
|
|
1622
1699
|
const headers = outboundMessageHeaders(options);
|
|
@@ -1626,6 +1703,7 @@ async function replyToMessageParams(options) {
|
|
|
1626
1703
|
return {
|
|
1627
1704
|
to,
|
|
1628
1705
|
...cc.length > 0 ? { cc } : {},
|
|
1706
|
+
...bcc.length > 0 ? { bcc } : {},
|
|
1629
1707
|
...html ? { html } : {},
|
|
1630
1708
|
...text ? { text } : {},
|
|
1631
1709
|
...getString(options, "client-reference") ? { client_reference: getString(options, "client-reference") } : {},
|
|
@@ -2529,6 +2607,45 @@ async function runMembers(client, parsed, out) {
|
|
|
2529
2607
|
);
|
|
2530
2608
|
return;
|
|
2531
2609
|
}
|
|
2610
|
+
if (subcommand === "invite") {
|
|
2611
|
+
const idempotencyKey = getString(parsed.options, "idempotency-key");
|
|
2612
|
+
writeJson(
|
|
2613
|
+
out,
|
|
2614
|
+
await client.members.invite(
|
|
2615
|
+
inviteMemberParams(parsed),
|
|
2616
|
+
idempotencyKey ? { idempotencyKey } : void 0
|
|
2617
|
+
)
|
|
2618
|
+
);
|
|
2619
|
+
return;
|
|
2620
|
+
}
|
|
2621
|
+
if (subcommand === "invitations") {
|
|
2622
|
+
const command = requirePositional(parsed.positional, 2, "invitations command");
|
|
2623
|
+
if (command === "list") {
|
|
2624
|
+
writeJson(out, await client.members.listInvitations());
|
|
2625
|
+
return;
|
|
2626
|
+
}
|
|
2627
|
+
if (command === "cancel") {
|
|
2628
|
+
const invitationId = requirePositional(parsed.positional, 3, "invitation id");
|
|
2629
|
+
await client.members.cancelInvitation(invitationId);
|
|
2630
|
+
writeJson(out, { ok: true, id: invitationId });
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
throw new CliUsageError("Unknown members invitations command.");
|
|
2634
|
+
}
|
|
2635
|
+
if (subcommand === "mailboxes") {
|
|
2636
|
+
const command = requirePositional(parsed.positional, 2, "mailboxes command");
|
|
2637
|
+
const memberId2 = requirePositional(parsed.positional, 3, "member id");
|
|
2638
|
+
const mailboxId = requirePositional(parsed.positional, 4, "mailbox id");
|
|
2639
|
+
if (command === "grant") {
|
|
2640
|
+
writeJson(out, await client.members.grantMailboxAccess(memberId2, mailboxId));
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
if (command === "revoke") {
|
|
2644
|
+
writeJson(out, await client.members.revokeMailboxAccess(memberId2, mailboxId));
|
|
2645
|
+
return;
|
|
2646
|
+
}
|
|
2647
|
+
throw new CliUsageError("Unknown members mailboxes command.");
|
|
2648
|
+
}
|
|
2532
2649
|
const memberId = requirePositional(parsed.positional, 2, "member id");
|
|
2533
2650
|
if (subcommand === "get") {
|
|
2534
2651
|
writeJson(out, await client.members.get(memberId));
|
|
@@ -2536,6 +2653,29 @@ async function runMembers(client, parsed, out) {
|
|
|
2536
2653
|
}
|
|
2537
2654
|
throw new CliUsageError("Unknown members command.");
|
|
2538
2655
|
}
|
|
2656
|
+
var INVITATION_ROLES = ["admin", "member"];
|
|
2657
|
+
function inviteMemberParams(parsed) {
|
|
2658
|
+
const email = requirePositional(parsed.positional, 2, "email");
|
|
2659
|
+
const rawRole = getRequiredString(parsed.options, "role");
|
|
2660
|
+
const role = INVITATION_ROLES.find((candidate) => candidate === rawRole);
|
|
2661
|
+
if (!role) throw new CliUsageError("--role must be admin or member.");
|
|
2662
|
+
const allMailboxes = getBoolean(parsed.options, "all-mailboxes") === true;
|
|
2663
|
+
const mailboxIds = getStringList(parsed.options, "mailbox");
|
|
2664
|
+
if (allMailboxes && mailboxIds.length > 0) {
|
|
2665
|
+
throw new CliUsageError("Use either --all-mailboxes or --mailbox, not both.");
|
|
2666
|
+
}
|
|
2667
|
+
const newsletterEditor = getBoolean(parsed.options, "newsletter-editor") === true;
|
|
2668
|
+
if (newsletterEditor && role !== "member") {
|
|
2669
|
+
throw new CliUsageError("--newsletter-editor applies to the member role only.");
|
|
2670
|
+
}
|
|
2671
|
+
return {
|
|
2672
|
+
email,
|
|
2673
|
+
role,
|
|
2674
|
+
...allMailboxes ? { mailbox_access: "all" } : {},
|
|
2675
|
+
...mailboxIds.length > 0 ? { mailbox_ids: mailboxIds } : {},
|
|
2676
|
+
...newsletterEditor ? { newsletter_role: "editor" } : {}
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2539
2679
|
var SUPPRESSION_SCOPES = ["all", "marketing"];
|
|
2540
2680
|
async function runSuppressions(client, parsed, out) {
|
|
2541
2681
|
const subcommand = parsed.positional[1];
|
|
@@ -2569,6 +2709,131 @@ async function runSuppressions(client, parsed, out) {
|
|
|
2569
2709
|
}
|
|
2570
2710
|
throw new CliUsageError("Unknown suppressions command.");
|
|
2571
2711
|
}
|
|
2712
|
+
async function templateCreateParams(options) {
|
|
2713
|
+
const html = await readTextInput(getString(options, "html"));
|
|
2714
|
+
const text = await readTextInput(getString(options, "text"));
|
|
2715
|
+
const fallbacks = getKeyValues(options, "fallback");
|
|
2716
|
+
const publish = getBoolean(options, "publish");
|
|
2717
|
+
if (!html && !text) throw new CliUsageError("Provide --html or --text.");
|
|
2718
|
+
return {
|
|
2719
|
+
name: getRequiredString(options, "name"),
|
|
2720
|
+
subject: getRequiredString(options, "subject"),
|
|
2721
|
+
...html ? { html } : {},
|
|
2722
|
+
...text ? { text } : {},
|
|
2723
|
+
...fallbacks ? { fallbacks } : {},
|
|
2724
|
+
...publish !== void 0 ? { publish } : {}
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
async function templateBodyUpdate(options, key) {
|
|
2728
|
+
const value = await readTextInput(getString(options, key));
|
|
2729
|
+
const clear = getBoolean(options, `clear-${key}`) === true;
|
|
2730
|
+
if (value !== void 0 && clear) {
|
|
2731
|
+
throw new CliUsageError(`Use either --${key} or --clear-${key}, not both.`);
|
|
2732
|
+
}
|
|
2733
|
+
return clear ? null : value;
|
|
2734
|
+
}
|
|
2735
|
+
async function templateUpdateParams(options) {
|
|
2736
|
+
const name = getString(options, "name");
|
|
2737
|
+
const subject = getString(options, "subject");
|
|
2738
|
+
const html = await templateBodyUpdate(options, "html");
|
|
2739
|
+
const text = await templateBodyUpdate(options, "text");
|
|
2740
|
+
const fallbacks = getKeyValues(options, "fallback");
|
|
2741
|
+
const clearFallbacks = getBoolean(options, "clear-fallbacks") === true;
|
|
2742
|
+
if (fallbacks && clearFallbacks) {
|
|
2743
|
+
throw new CliUsageError("Use either --fallback or --clear-fallbacks, not both.");
|
|
2744
|
+
}
|
|
2745
|
+
const params = {
|
|
2746
|
+
...name !== void 0 ? { name } : {},
|
|
2747
|
+
...subject !== void 0 ? { subject } : {},
|
|
2748
|
+
...html !== void 0 ? { html } : {},
|
|
2749
|
+
...text !== void 0 ? { text } : {},
|
|
2750
|
+
...fallbacks ? { fallbacks } : clearFallbacks ? { fallbacks: {} } : {}
|
|
2751
|
+
};
|
|
2752
|
+
if (name === void 0 && subject === void 0 && html === void 0 && text === void 0 && params.fallbacks === void 0) {
|
|
2753
|
+
throw new CliUsageError("Provide at least one field to change.");
|
|
2754
|
+
}
|
|
2755
|
+
return params;
|
|
2756
|
+
}
|
|
2757
|
+
function templateVersionPositional(positional) {
|
|
2758
|
+
const raw = requirePositional(positional, 3, "version");
|
|
2759
|
+
const version = Number(raw);
|
|
2760
|
+
if (!Number.isInteger(version) || version < 1) {
|
|
2761
|
+
throw new CliUsageError("Version must be a positive integer.");
|
|
2762
|
+
}
|
|
2763
|
+
return version;
|
|
2764
|
+
}
|
|
2765
|
+
async function runTemplates(client, parsed, out) {
|
|
2766
|
+
const subcommand = parsed.positional[1];
|
|
2767
|
+
if (subcommand === "list") {
|
|
2768
|
+
writeJson(
|
|
2769
|
+
out,
|
|
2770
|
+
await client.templates.list({
|
|
2771
|
+
cursor: getString(parsed.options, "cursor"),
|
|
2772
|
+
limit: getNumber(parsed.options, "limit")
|
|
2773
|
+
})
|
|
2774
|
+
);
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2777
|
+
if (subcommand === "create") {
|
|
2778
|
+
writeJson(out, await client.templates.create(await templateCreateParams(parsed.options)));
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
const id = requirePositional(parsed.positional, 2, "template id");
|
|
2782
|
+
if (subcommand === "get") {
|
|
2783
|
+
writeJson(out, await client.templates.get(id));
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
if (subcommand === "update") {
|
|
2787
|
+
writeJson(out, await client.templates.update(id, await templateUpdateParams(parsed.options)));
|
|
2788
|
+
return;
|
|
2789
|
+
}
|
|
2790
|
+
if (subcommand === "delete") {
|
|
2791
|
+
await client.templates.delete(id);
|
|
2792
|
+
writeJson(out, { ok: true, id });
|
|
2793
|
+
return;
|
|
2794
|
+
}
|
|
2795
|
+
if (subcommand === "publish") {
|
|
2796
|
+
writeJson(out, await client.templates.publish(id));
|
|
2797
|
+
return;
|
|
2798
|
+
}
|
|
2799
|
+
if (subcommand === "render") {
|
|
2800
|
+
const variables = getKeyValues(parsed.options, "var");
|
|
2801
|
+
const version = getNumber(parsed.options, "template-version");
|
|
2802
|
+
writeJson(
|
|
2803
|
+
out,
|
|
2804
|
+
await client.templates.render(id, {
|
|
2805
|
+
...variables ? { variables } : {},
|
|
2806
|
+
...version !== void 0 ? { version } : {}
|
|
2807
|
+
})
|
|
2808
|
+
);
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2811
|
+
if (subcommand === "versions") {
|
|
2812
|
+
writeJson(
|
|
2813
|
+
out,
|
|
2814
|
+
await client.templates.listVersions(id, {
|
|
2815
|
+
cursor: getString(parsed.options, "cursor"),
|
|
2816
|
+
limit: getNumber(parsed.options, "limit")
|
|
2817
|
+
})
|
|
2818
|
+
);
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
2821
|
+
if (subcommand === "version") {
|
|
2822
|
+
writeJson(
|
|
2823
|
+
out,
|
|
2824
|
+
await client.templates.getVersion(id, templateVersionPositional(parsed.positional))
|
|
2825
|
+
);
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
if (subcommand === "restore") {
|
|
2829
|
+
writeJson(
|
|
2830
|
+
out,
|
|
2831
|
+
await client.templates.restoreVersion(id, templateVersionPositional(parsed.positional))
|
|
2832
|
+
);
|
|
2833
|
+
return;
|
|
2834
|
+
}
|
|
2835
|
+
throw new CliUsageError("Unknown templates command.");
|
|
2836
|
+
}
|
|
2572
2837
|
var SUBSCRIBER_STATUS_VALUES = [
|
|
2573
2838
|
"subscribed",
|
|
2574
2839
|
"pending",
|
|
@@ -3517,6 +3782,13 @@ ${JSON.stringify(err.body, null, 2)}
|
|
|
3517
3782
|
const messageId = shipmailError instanceof ReconciliationRequiredError && shipmailError.messageId ? ` message_id=${shipmailError.messageId}` : "";
|
|
3518
3783
|
errOut.write(`Error: ${shipmailError.message}${status}${requestId}${messageId}
|
|
3519
3784
|
`);
|
|
3785
|
+
if (shipmailError instanceof ValidationError) {
|
|
3786
|
+
for (const detail of shipmailError.details ?? []) {
|
|
3787
|
+
const code = detail.code ? ` (${detail.code})` : "";
|
|
3788
|
+
errOut.write(` ${detail.field}: ${detail.message}${code}
|
|
3789
|
+
`);
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3520
3792
|
if (shipmailError.actionUrl) {
|
|
3521
3793
|
const reason = shipmailError.reason ? ` (${shipmailError.reason})` : "";
|
|
3522
3794
|
errOut.write(`Ask the organization owner to open${reason}: ${shipmailError.actionUrl}
|
|
@@ -3600,6 +3872,7 @@ async function runCli(argv, options = {}) {
|
|
|
3600
3872
|
else if (command === "reply-scans") await runReplyScans(client, parsed, out);
|
|
3601
3873
|
else if (command === "webhooks") await runWebhooks(client, parsed, out);
|
|
3602
3874
|
else if (command === "suppressions") await runSuppressions(client, parsed, out);
|
|
3875
|
+
else if (command === "templates") await runTemplates(client, parsed, out);
|
|
3603
3876
|
else if (command === "newsletters") await runNewsletters(client, parsed, out);
|
|
3604
3877
|
else if (command === "newsletter-assets")
|
|
3605
3878
|
await runNewsletterAssets(client, parsed, out, options.fetch ?? globalThis.fetch);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shipmail-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "Official CLI for the Shipmail API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,9 +31,12 @@
|
|
|
31
31
|
"homepage": "https://shipmail.to/docs/cli",
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/shipmail-to/
|
|
34
|
+
"url": "git+https://github.com/shipmail-to/shipmail-node.git",
|
|
35
35
|
"directory": "packages/shipmail-cli"
|
|
36
36
|
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/shipmail-to/shipmail-node/issues"
|
|
39
|
+
},
|
|
37
40
|
"engines": {
|
|
38
41
|
"node": ">=18"
|
|
39
42
|
},
|
|
@@ -41,7 +44,7 @@
|
|
|
41
44
|
"access": "public"
|
|
42
45
|
},
|
|
43
46
|
"dependencies": {
|
|
44
|
-
"shipmail": "0.6.
|
|
47
|
+
"shipmail": "0.6.8",
|
|
45
48
|
"zod": "^4.6.5"
|
|
46
49
|
},
|
|
47
50
|
"devDependencies": {
|