shipmail-cli 0.6.6 → 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 +32 -1
- package/dist/cli.js +282 -12
- package/package.json +2 -2
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
|
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
|
|
|
@@ -651,7 +652,7 @@ async function login(options) {
|
|
|
651
652
|
}
|
|
652
653
|
|
|
653
654
|
// src/version.ts
|
|
654
|
-
var VERSION = "0.6.
|
|
655
|
+
var VERSION = "0.6.7";
|
|
655
656
|
|
|
656
657
|
// src/cli.ts
|
|
657
658
|
var DEFAULT_BASE_URL = "https://shipmail.to/api/v1";
|
|
@@ -774,6 +775,20 @@ function getStringList(options, key) {
|
|
|
774
775
|
if (isStringArray(value)) return value.flatMap(splitValues);
|
|
775
776
|
return splitValues(value);
|
|
776
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
|
+
}
|
|
777
792
|
function validateBaseUrl(rawValue, allowInsecure) {
|
|
778
793
|
let parsed;
|
|
779
794
|
try {
|
|
@@ -1255,6 +1270,11 @@ Usage:
|
|
|
1255
1270
|
shipmail mailboxes sandbox-inbound <id> --from sender@example.com --subject "Test" (--text "Hello" | --html "<p>Hello</p>") [--to support@example.com]
|
|
1256
1271
|
shipmail members list [--limit 25] [--cursor cur]
|
|
1257
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>
|
|
1258
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]
|
|
1259
1279
|
shipmail inbox message <mailbox-id> <message-id>
|
|
1260
1280
|
shipmail inbox security-review <mailbox-id> <message-id> --reason "Reviewed" --expected-content-sha256 HASH
|
|
@@ -1268,15 +1288,16 @@ Usage:
|
|
|
1268
1288
|
shipmail inbox update-draft <mailbox-id> <thread-id> <draft-id> (--text "Hello" | --html "<p>Hello</p>") (the new body replaces the old one)
|
|
1269
1289
|
shipmail inbox delete-draft <mailbox-id> <thread-id> <draft-id>
|
|
1270
1290
|
shipmail inbox send-draft <mailbox-id> <thread-id> <draft-id> [--expected-content-sha256 hash]
|
|
1271
|
-
shipmail inbox reply-message <mailbox-id> <message-id> --to user@example.com (--text "Hello" | --html "<p>Hello</p>") [--track-reply true|false]
|
|
1272
|
-
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]
|
|
1273
1293
|
shipmail inbox update <mailbox-id> <message-id> [--read true] [--starred false]
|
|
1274
1294
|
shipmail inbox move <mailbox-id> <message-id> (--target-role archive | --target-folder-id fld)
|
|
1275
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 ...]
|
|
1276
1297
|
shipmail messages list (--mailbox-id mbx | --client-reference crm-123) [--limit 25] [--cursor cur]
|
|
1277
1298
|
shipmail messages analytics [--updated-after ISO] [--updated-before ISO] [--limit 100] [--cursor cur]
|
|
1278
1299
|
shipmail messages get <message-id>
|
|
1279
|
-
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]
|
|
1280
1301
|
shipmail scheduled-messages list [--include-held] [--search text] [--limit 100]
|
|
1281
1302
|
shipmail scheduled-messages get <message-id>
|
|
1282
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_...]
|
|
@@ -1291,6 +1312,16 @@ Usage:
|
|
|
1291
1312
|
shipmail suppressions add <email> [--scope all|marketing]
|
|
1292
1313
|
shipmail suppressions list [--scope all|marketing] [--limit 25] [--cursor cur]
|
|
1293
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>
|
|
1294
1325
|
shipmail newsletters create \\
|
|
1295
1326
|
--audience-id aud_123 \\
|
|
1296
1327
|
--sender-identity-id nwsid_456 \\
|
|
@@ -1365,6 +1396,17 @@ One-off booking links:
|
|
|
1365
1396
|
Revoking a link stops it at once and cannot be undone. Other flags: --description,
|
|
1366
1397
|
--buffer-minutes, --minimum-notice-minutes, --conferencing-provider, --mailbox.
|
|
1367
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
|
+
|
|
1368
1410
|
Conversation IDs:
|
|
1369
1411
|
<thread-id> accepts a conversation ID (thd_...) or the mail-server thread ID. Store the
|
|
1370
1412
|
conversation_id every response carries. thread_id is deprecated and unchanged.
|
|
@@ -1373,6 +1415,11 @@ Member access output:
|
|
|
1373
1415
|
role is owner, admin, or member. mailbox_access.scope is all or selected. mailbox_ids is empty
|
|
1374
1416
|
for all; selected IDs may be limited to mailboxes the API key can reach.
|
|
1375
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
|
+
|
|
1376
1423
|
Newsletter styling:
|
|
1377
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.
|
|
1378
1425
|
|
|
@@ -1580,24 +1627,17 @@ async function sendMessageParams(options) {
|
|
|
1580
1627
|
const cc = messageRecipients(options, "cc");
|
|
1581
1628
|
const bcc = messageRecipients(options, "bcc");
|
|
1582
1629
|
const replyTo = getString(options, "reply-to");
|
|
1583
|
-
const subject = getRequiredString(options, "subject");
|
|
1584
|
-
const text = getString(options, "text");
|
|
1585
|
-
const html = getString(options, "html");
|
|
1586
1630
|
const headers = outboundMessageHeaders(options);
|
|
1587
1631
|
const metadata = await messageMetadata(options);
|
|
1588
1632
|
if (!mailboxId && !from) throw new CliUsageError("Provide --mailbox-id or --from.");
|
|
1589
1633
|
if (to.length === 0) throw new CliUsageError("Provide at least one --to recipient.");
|
|
1590
|
-
|
|
1591
|
-
return {
|
|
1634
|
+
const common = {
|
|
1592
1635
|
...mailboxId ? { mailbox_id: mailboxId } : {},
|
|
1593
1636
|
...from ? { from } : {},
|
|
1594
1637
|
to,
|
|
1595
1638
|
...cc.length > 0 ? { cc } : {},
|
|
1596
1639
|
...bcc.length > 0 ? { bcc } : {},
|
|
1597
1640
|
...replyTo ? { reply_to: replyTo } : {},
|
|
1598
|
-
subject,
|
|
1599
|
-
...html ? { html } : {},
|
|
1600
|
-
...text ? { text } : {},
|
|
1601
1641
|
...getString(options, "client-reference") ? { client_reference: getString(options, "client-reference") } : {},
|
|
1602
1642
|
...metadata ? { metadata } : {},
|
|
1603
1643
|
...getString(options, "source-rfc-message-id") ? { source_rfc_message_id: getString(options, "source-rfc-message-id") } : {},
|
|
@@ -1616,10 +1656,44 @@ async function sendMessageParams(options) {
|
|
|
1616
1656
|
])
|
|
1617
1657
|
} : {}
|
|
1618
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
|
+
};
|
|
1619
1692
|
}
|
|
1620
1693
|
async function replyToMessageParams(options) {
|
|
1621
1694
|
const to = messageRecipients(options, "to");
|
|
1622
1695
|
const cc = messageRecipients(options, "cc");
|
|
1696
|
+
const bcc = messageRecipients(options, "bcc");
|
|
1623
1697
|
const text = getString(options, "text");
|
|
1624
1698
|
const html = getString(options, "html");
|
|
1625
1699
|
const headers = outboundMessageHeaders(options);
|
|
@@ -1629,6 +1703,7 @@ async function replyToMessageParams(options) {
|
|
|
1629
1703
|
return {
|
|
1630
1704
|
to,
|
|
1631
1705
|
...cc.length > 0 ? { cc } : {},
|
|
1706
|
+
...bcc.length > 0 ? { bcc } : {},
|
|
1632
1707
|
...html ? { html } : {},
|
|
1633
1708
|
...text ? { text } : {},
|
|
1634
1709
|
...getString(options, "client-reference") ? { client_reference: getString(options, "client-reference") } : {},
|
|
@@ -2532,6 +2607,45 @@ async function runMembers(client, parsed, out) {
|
|
|
2532
2607
|
);
|
|
2533
2608
|
return;
|
|
2534
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
|
+
}
|
|
2535
2649
|
const memberId = requirePositional(parsed.positional, 2, "member id");
|
|
2536
2650
|
if (subcommand === "get") {
|
|
2537
2651
|
writeJson(out, await client.members.get(memberId));
|
|
@@ -2539,6 +2653,29 @@ async function runMembers(client, parsed, out) {
|
|
|
2539
2653
|
}
|
|
2540
2654
|
throw new CliUsageError("Unknown members command.");
|
|
2541
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
|
+
}
|
|
2542
2679
|
var SUPPRESSION_SCOPES = ["all", "marketing"];
|
|
2543
2680
|
async function runSuppressions(client, parsed, out) {
|
|
2544
2681
|
const subcommand = parsed.positional[1];
|
|
@@ -2572,6 +2709,131 @@ async function runSuppressions(client, parsed, out) {
|
|
|
2572
2709
|
}
|
|
2573
2710
|
throw new CliUsageError("Unknown suppressions command.");
|
|
2574
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
|
+
}
|
|
2575
2837
|
var SUBSCRIBER_STATUS_VALUES = [
|
|
2576
2838
|
"subscribed",
|
|
2577
2839
|
"pending",
|
|
@@ -3520,6 +3782,13 @@ ${JSON.stringify(err.body, null, 2)}
|
|
|
3520
3782
|
const messageId = shipmailError instanceof ReconciliationRequiredError && shipmailError.messageId ? ` message_id=${shipmailError.messageId}` : "";
|
|
3521
3783
|
errOut.write(`Error: ${shipmailError.message}${status}${requestId}${messageId}
|
|
3522
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
|
+
}
|
|
3523
3792
|
if (shipmailError.actionUrl) {
|
|
3524
3793
|
const reason = shipmailError.reason ? ` (${shipmailError.reason})` : "";
|
|
3525
3794
|
errOut.write(`Ask the organization owner to open${reason}: ${shipmailError.actionUrl}
|
|
@@ -3603,6 +3872,7 @@ async function runCli(argv, options = {}) {
|
|
|
3603
3872
|
else if (command === "reply-scans") await runReplyScans(client, parsed, out);
|
|
3604
3873
|
else if (command === "webhooks") await runWebhooks(client, parsed, out);
|
|
3605
3874
|
else if (command === "suppressions") await runSuppressions(client, parsed, out);
|
|
3875
|
+
else if (command === "templates") await runTemplates(client, parsed, out);
|
|
3606
3876
|
else if (command === "newsletters") await runNewsletters(client, parsed, out);
|
|
3607
3877
|
else if (command === "newsletter-assets")
|
|
3608
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": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"shipmail": "0.6.
|
|
47
|
+
"shipmail": "0.6.8",
|
|
48
48
|
"zod": "^4.6.5"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|