shipmail-cli 0.6.6 → 0.6.8
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 +45 -2
- package/dist/cli.js +555 -16
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -69,6 +69,11 @@ shipmail messages list --client-reference crm-123
|
|
|
69
69
|
shipmail messages analytics --updated-after 2026-07-01T00:00:00Z --limit 100
|
|
70
70
|
shipmail messages reply msg_123 --to user@example.com --text "Thanks"
|
|
71
71
|
|
|
72
|
+
# SMTP credentials for a transactional sender (the password is printed once)
|
|
73
|
+
shipmail smtp-credentials create --from notifications@notify.example.com --name "Supabase Auth"
|
|
74
|
+
shipmail smtp-credentials list --from notifications@notify.example.com
|
|
75
|
+
shipmail smtp-credentials revoke smtpc_123
|
|
76
|
+
|
|
72
77
|
# Stage a raw file, then pass the returned sat_... ID when sending
|
|
73
78
|
shipmail mailboxes stage-attachment mbx_123 ./invoice.pdf --content-type application/pdf
|
|
74
79
|
|
|
@@ -149,6 +154,25 @@ shipmail audiences welcome add-step aud_123 --subject "Welcome" --markdown @welc
|
|
|
149
154
|
shipmail audiences welcome add-step aud_123 --subject "Our best posts" --markdown @day-3.md --wait-minutes 4320
|
|
150
155
|
shipmail audiences welcome enable aud_123
|
|
151
156
|
|
|
157
|
+
# Save a group of subscribers, then target it with segment_id
|
|
158
|
+
shipmail audiences segments create aud_123 --name "Founders" \
|
|
159
|
+
--definition '{"match":"all","rules":[{"kind":"merge_field","field":"role","operator":"equals","value":"founder"}]}'
|
|
160
|
+
shipmail audiences segments list aud_123
|
|
161
|
+
|
|
162
|
+
# Store a template, publish it, then send it by ID
|
|
163
|
+
shipmail templates create \
|
|
164
|
+
--name "Welcome" \
|
|
165
|
+
--subject "Welcome, {{name}}" \
|
|
166
|
+
--html @welcome.html \
|
|
167
|
+
--fallback plan=Free \
|
|
168
|
+
--publish
|
|
169
|
+
shipmail templates render tpl_123 --var name=Ada
|
|
170
|
+
shipmail messages send --from hello@example.com --to ada@example.com --template tpl_123 --var name=Ada
|
|
171
|
+
shipmail templates update tpl_123 --subject "Welcome aboard, {{name}}"
|
|
172
|
+
shipmail templates publish tpl_123
|
|
173
|
+
shipmail templates versions tpl_123
|
|
174
|
+
shipmail templates restore tpl_123 1
|
|
175
|
+
|
|
152
176
|
# Manage webhooks
|
|
153
177
|
shipmail webhooks create \
|
|
154
178
|
--url https://example.com/shipmail/webhook \
|
|
@@ -170,6 +194,11 @@ Shipmail converts headings, paragraphs, lists, quotes, code, images, links, divi
|
|
|
170
194
|
to blocks. A callout starts with a line `:::callout` (optionally `info`, `warning` or `success`,
|
|
171
195
|
then a title) and ends with a line `:::`.
|
|
172
196
|
|
|
197
|
+
With `messages send --template`, the template supplies the subject and body, so `--subject`,
|
|
198
|
+
`--html` and `--text` are refused. `--var key=value` sets a variable (repeat it; values are text),
|
|
199
|
+
and `--template-version` pins a version instead of the published one. A send missing a required
|
|
200
|
+
variable fails with HTTP 422 and prints each detail with its code, such as `missing_variable`.
|
|
201
|
+
|
|
173
202
|
`newsletter-assets upload` uses a five-minute direct storage PUT, then calls the single-use
|
|
174
203
|
completion URL. Video uploads require a JPEG poster through `--thumbnail`. `newsletter-assets list`
|
|
175
204
|
includes current newsletter media storage usage and a `pagination.next_cursor` value in the JSON
|
|
@@ -191,18 +220,23 @@ Use repeatable `--header "Name: value"` flags for request headers. Use `--output
|
|
|
191
220
|
- `api <METHOD> <PATH>`
|
|
192
221
|
- `domains list|search|create|get|verify|delete`
|
|
193
222
|
- `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`
|
|
223
|
+
- `members list|get|invite`
|
|
224
|
+
- `members invitations list|cancel`
|
|
225
|
+
- `members mailboxes grant|revoke`
|
|
195
226
|
- `inbox list|message|security-review|threads|thread|thread-state|draft|send-draft|reply-message|reply-thread|update|move`
|
|
196
227
|
- `reply-scans create|status|results`
|
|
197
228
|
- `messages send|list|analytics|get|reply` (test keys also support `--sandbox-outcome`)
|
|
198
229
|
- `scheduled-messages list|get|update|cancel`
|
|
199
230
|
- `mailboxes sandbox-inbound`
|
|
200
231
|
- `webhooks list|create|update|test|deliveries|replay-delivery`
|
|
232
|
+
- `smtp-credentials list|create|revoke`
|
|
201
233
|
- `suppressions add|list|remove`
|
|
234
|
+
- `templates list|create|get|update|delete|publish|render|versions|version|restore` (`messages send --template` sends one)
|
|
202
235
|
- `audiences list|create|get|update|delete`
|
|
203
236
|
- `audiences feed get|update|rotate|revoke`
|
|
204
237
|
- `audiences welcome get|enable|disable|add-step|update-step|remove-step`
|
|
205
|
-
- `audiences
|
|
238
|
+
- `audiences segments list|create|get|update|delete`
|
|
239
|
+
- `audiences subscribers add|list|get|by-email|update|unsubscribe|resubscribe|resend-confirmation|remove`
|
|
206
240
|
- `newsletter-sender-identities list`
|
|
207
241
|
- `newsletter-domains list`
|
|
208
242
|
- `newsletters create|update|preview|preflight|test-send|schedule|cancel|resume`
|
|
@@ -212,6 +246,15 @@ Member output uses the roles `owner`, `admin`, and `member`. Its `mailbox_access
|
|
|
212
246
|
`selected`; `mailbox_ids` is empty for `all`, while selected IDs may be limited to mailboxes the
|
|
213
247
|
API key can reach.
|
|
214
248
|
|
|
249
|
+
`members invite`, `members invitations cancel` and `members mailboxes grant|revoke` need the
|
|
250
|
+
`members:write` permission, which no preset includes. The invitee receives an email and creates
|
|
251
|
+
their own account; the CLI never receives their credentials:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
shipmail members invite sam@example.com --role member --mailbox mbx_support --newsletter-editor
|
|
255
|
+
shipmail members mailboxes grant mem_sam mbx_sales
|
|
256
|
+
```
|
|
257
|
+
|
|
215
258
|
Configure a publisher-owned Atom self URL and the number of recent newsletters:
|
|
216
259
|
|
|
217
260
|
```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.8";
|
|
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 {
|
|
@@ -1020,6 +1035,18 @@ function parseStylingMode(options) {
|
|
|
1020
1035
|
if (value === "styled" || value === "plain") return value;
|
|
1021
1036
|
throw new CliUsageError("--styling-mode must be styled or plain.");
|
|
1022
1037
|
}
|
|
1038
|
+
function isSegmentDefinition(value) {
|
|
1039
|
+
return isJsonObject(value) && (value["match"] === "all" || value["match"] === "any") && Array.isArray(value["rules"]) && value["rules"].length > 0 && value["rules"].every((rule) => isJsonObject(rule) && typeof rule["kind"] === "string");
|
|
1040
|
+
}
|
|
1041
|
+
async function segmentDefinition(value) {
|
|
1042
|
+
const parsed = await readJsonInput(value);
|
|
1043
|
+
if (!isSegmentDefinition(parsed)) {
|
|
1044
|
+
throw new CliUsageError(
|
|
1045
|
+
'--definition must be a JSON object with match ("all" or "any") and a non-empty rules array.'
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
return parsed;
|
|
1049
|
+
}
|
|
1023
1050
|
async function automationDefinition(value) {
|
|
1024
1051
|
const parsed = await readJsonInput(value);
|
|
1025
1052
|
if (!isAutomationDefinition(parsed)) {
|
|
@@ -1255,6 +1282,11 @@ Usage:
|
|
|
1255
1282
|
shipmail mailboxes sandbox-inbound <id> --from sender@example.com --subject "Test" (--text "Hello" | --html "<p>Hello</p>") [--to support@example.com]
|
|
1256
1283
|
shipmail members list [--limit 25] [--cursor cur]
|
|
1257
1284
|
shipmail members get <id>
|
|
1285
|
+
shipmail members invitations list
|
|
1286
|
+
shipmail members invite <email> --role admin|member [--all-mailboxes] [--mailbox <mailbox-id> ...] [--newsletter-editor] [--idempotency-key key]
|
|
1287
|
+
shipmail members invitations cancel <invitation-id>
|
|
1288
|
+
shipmail members mailboxes grant <member-id> <mailbox-id>
|
|
1289
|
+
shipmail members mailboxes revoke <member-id> <mailbox-id>
|
|
1258
1290
|
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
1291
|
shipmail inbox message <mailbox-id> <message-id>
|
|
1260
1292
|
shipmail inbox security-review <mailbox-id> <message-id> --reason "Reviewed" --expected-content-sha256 HASH
|
|
@@ -1268,15 +1300,17 @@ Usage:
|
|
|
1268
1300
|
shipmail inbox update-draft <mailbox-id> <thread-id> <draft-id> (--text "Hello" | --html "<p>Hello</p>") (the new body replaces the old one)
|
|
1269
1301
|
shipmail inbox delete-draft <mailbox-id> <thread-id> <draft-id>
|
|
1270
1302
|
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]
|
|
1303
|
+
shipmail inbox reply-message <mailbox-id> <message-id> --to user@example.com [--cc ...] [--bcc ...] (--text "Hello" | --html "<p>Hello</p>") [--track-reply true|false] [--add-signature]
|
|
1304
|
+
shipmail inbox reply-thread <mailbox-id> <thread-id> --to user@example.com [--cc ...] [--bcc ...] (--text "Hello" | --html "<p>Hello</p>") [--track-reply true|false] [--add-signature]
|
|
1273
1305
|
shipmail inbox update <mailbox-id> <message-id> [--read true] [--starred false]
|
|
1274
1306
|
shipmail inbox move <mailbox-id> <message-id> (--target-role archive | --target-folder-id fld)
|
|
1275
|
-
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']
|
|
1307
|
+
shipmail messages send (--mailbox-id mbx | --from 'Acme Support <hello@example.com>') --to user@example.com --subject "Hi" --text "Hello" [--track-reply true|false] [--add-signature] [--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']
|
|
1308
|
+
shipmail messages send (--mailbox-id mbx | --from hello@example.com) --to user@example.com --template tpl_... [--template-version 2] [--var name=Ada ...]
|
|
1276
1309
|
shipmail messages list (--mailbox-id mbx | --client-reference crm-123) [--limit 25] [--cursor cur]
|
|
1277
1310
|
shipmail messages analytics [--updated-after ISO] [--updated-before ISO] [--limit 100] [--cursor cur]
|
|
1278
1311
|
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]
|
|
1312
|
+
shipmail messages reply <message-id> --to user@example.com [--cc ...] [--bcc ...] (--text "Hello" | --html "<p>Hello</p>") [--track-reply true|false] [--add-signature] [--client-reference crm-reply-123]
|
|
1313
|
+
shipmail messages invitation respond <message-id> --response accepted|tentative|declined
|
|
1280
1314
|
shipmail scheduled-messages list [--include-held] [--search text] [--limit 100]
|
|
1281
1315
|
shipmail scheduled-messages get <message-id>
|
|
1282
1316
|
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,9 +1322,30 @@ Usage:
|
|
|
1288
1322
|
shipmail webhooks create --url https://example.com/hook --events message.received,message.bounced [--authorization "Bearer key"] [--standard-webhooks-secret whsec_...] [--include-reply-text] [--authenticated-senders-only] [--allowed-sender jane@customer.com --allowed-sender customer.com]
|
|
1289
1323
|
shipmail webhooks update <webhook-id> [--url https://example.com/hook] [--events message.received] [--description text] [--active true|false] [--authorization "Bearer key" | --clear-authorization] [--standard-webhooks-secret whsec_... | --clear-standard-webhooks-secret] [--include-reply-text true|false] [--authenticated-senders-only true|false] [--allowed-sender customer.com ... | --clear-allowed-senders]
|
|
1290
1324
|
shipmail webhooks replay-delivery <webhook-id> <delivery-id> [--idempotency-key key]
|
|
1325
|
+
shipmail smtp-credentials list [--from notifications@notify.example.com]
|
|
1326
|
+
shipmail smtp-credentials create --from notifications@notify.example.com --name "Supabase Auth" (prints the password once)
|
|
1327
|
+
shipmail smtp-credentials revoke <smtp-credential-id>
|
|
1291
1328
|
shipmail suppressions add <email> [--scope all|marketing]
|
|
1292
|
-
shipmail suppressions list [--scope all|marketing] [--limit 25] [--cursor cur]
|
|
1329
|
+
shipmail suppressions list [--scope all|marketing|transactional|mailbox] [--limit 25] [--cursor cur]
|
|
1293
1330
|
shipmail suppressions remove <email>
|
|
1331
|
+
shipmail customers list [--email jane@acme.com | --domain acme.com | --external-source hubspot --external-id 42 | --q text] [--tag vip] [--limit 25] [--cursor cur]
|
|
1332
|
+
shipmail customers get <customer-id>
|
|
1333
|
+
shipmail customers create [--name "Jane Doe"] [--company Acme] [--notes text] [--service-level-id svl_...] [--email jane@acme.com ...] [--domain acme.com ...] [--fields JSON|@file] [--tag vip ...]
|
|
1334
|
+
shipmail customers update <customer-id> [--name ...] [--company ...] [--notes ...] [--service-level-id svl_... | --clear-service-level] [--email ... --domain ...] [--fields JSON|@file] [--tag vip ... | --clear-tags] (--email and --domain replace all identifiers; --tag replaces all tags)
|
|
1335
|
+
shipmail customers upsert <source> <external-id> [--url https://crm.example.com/42] [same flags as update] (creates or updates the customer that system knows by that id)
|
|
1336
|
+
shipmail customers delete <customer-id>
|
|
1337
|
+
shipmail customers reply-times
|
|
1338
|
+
shipmail customers fields
|
|
1339
|
+
shipmail templates list [--limit 25] [--cursor cur]
|
|
1340
|
+
shipmail templates create --name "Welcome" --subject "Welcome, {{name}}" (--html "<p>Hi {{name}}</p>" | --text "Hi {{name}}") [--fallback name=there ...] [--publish]
|
|
1341
|
+
shipmail templates get <template-id>
|
|
1342
|
+
shipmail templates update <template-id> [--name "..."] [--subject "..."] [--html "..." | --clear-html] [--text "..." | --clear-text] [--fallback key=value ... | --clear-fallbacks]
|
|
1343
|
+
shipmail templates delete <template-id>
|
|
1344
|
+
shipmail templates publish <template-id>
|
|
1345
|
+
shipmail templates render <template-id> [--var name=Ada ...] [--template-version 2]
|
|
1346
|
+
shipmail templates versions <template-id> [--limit 25] [--cursor cur]
|
|
1347
|
+
shipmail templates version <template-id> <version>
|
|
1348
|
+
shipmail templates restore <template-id> <version>
|
|
1294
1349
|
shipmail newsletters create \\
|
|
1295
1350
|
--audience-id aud_123 \\
|
|
1296
1351
|
--sender-identity-id nwsid_456 \\
|
|
@@ -1325,6 +1380,11 @@ Usage:
|
|
|
1325
1380
|
shipmail audiences welcome add-step <audience-id> --subject "Welcome" (--markdown @welcome.md | --blocks @blocks.json) [--wait-minutes 0] [--preview-text "..."] [--sender-identity-id nwsid_456]
|
|
1326
1381
|
shipmail audiences welcome update-step <audience-id> <step-id> [--subject "..."] [--markdown @welcome.md | --blocks @blocks.json] [--wait-minutes 1440]
|
|
1327
1382
|
shipmail audiences welcome remove-step <audience-id> <step-id>
|
|
1383
|
+
shipmail audiences segments list <audience-id>
|
|
1384
|
+
shipmail audiences segments create <audience-id> --name "Founders" --definition JSON|@segment.json [--idempotency-key key]
|
|
1385
|
+
shipmail audiences segments get <audience-id> <segment-id>
|
|
1386
|
+
shipmail audiences segments update <audience-id> <segment-id> [--name "..."] [--definition JSON|@segment.json]
|
|
1387
|
+
shipmail audiences segments delete <audience-id> <segment-id>
|
|
1328
1388
|
shipmail audiences subscribers add <audience-id> <email> [--name "Jane"] [--consent-source "..."]
|
|
1329
1389
|
shipmail audiences subscribers list <audience-id> [--status subscribed] [--email jane@x.com] [--limit 25]
|
|
1330
1390
|
shipmail audiences subscribers get <audience-id> <subscriber-id>
|
|
@@ -1332,6 +1392,7 @@ Usage:
|
|
|
1332
1392
|
shipmail audiences subscribers update <audience-id> <subscriber-id> [--name "..."]
|
|
1333
1393
|
shipmail audiences subscribers unsubscribe <audience-id> <subscriber-id>
|
|
1334
1394
|
shipmail audiences subscribers resubscribe <audience-id> <subscriber-id> --consent-source "Signup form"
|
|
1395
|
+
shipmail audiences subscribers resend-confirmation <audience-id> [--subscriber <subscriber-id> ...]
|
|
1335
1396
|
shipmail audiences subscribers remove <audience-id> <subscriber-id>
|
|
1336
1397
|
shipmail calendar events list --mailbox hello@example.com --from 2026-08-01T00:00:00Z --to 2026-08-08T00:00:00Z [--expand true] [--limit 100]
|
|
1337
1398
|
shipmail calendar events create --mailbox hello@example.com --title "Sync" --start 2026-08-01T14:00:00 [--timezone Europe/Paris] [--duration PT30M] [--invitation-language en|fr|es] [--attendees JSON|@file] [--reminders JSON|@file] [--operation-key key] [--send-invitations true|false]
|
|
@@ -1365,6 +1426,18 @@ One-off booking links:
|
|
|
1365
1426
|
Revoking a link stops it at once and cannot be undone. Other flags: --description,
|
|
1366
1427
|
--buffer-minutes, --minimum-notice-minutes, --conferencing-provider, --mailbox.
|
|
1367
1428
|
|
|
1429
|
+
Templates:
|
|
1430
|
+
{{key}} in the subject, html or text is a variable. --html and --text take the value or @file.
|
|
1431
|
+
--fallback key=value Default for a variable. Repeat it. A variable without one is required
|
|
1432
|
+
at send time. On update, the given fallbacks replace all of them.
|
|
1433
|
+
--publish On create, also publish the draft as version 1.
|
|
1434
|
+
update changes the draft; publish makes it the next version, which sends use. restore copies a
|
|
1435
|
+
version into the draft without publishing it. render previews the draft, or --template-version.
|
|
1436
|
+
messages send --template <id> sends the published version, or --template-version. --var
|
|
1437
|
+
key=value sets a variable (repeat it; values are text). --subject, --html and --text are refused
|
|
1438
|
+
with --template. A transactional sender refuses --staged-attachment-id, and an email it sends can
|
|
1439
|
+
be up to 10 MB, encoding included.
|
|
1440
|
+
|
|
1368
1441
|
Conversation IDs:
|
|
1369
1442
|
<thread-id> accepts a conversation ID (thd_...) or the mail-server thread ID. Store the
|
|
1370
1443
|
conversation_id every response carries. thread_id is deprecated and unchanged.
|
|
@@ -1373,6 +1446,11 @@ Member access output:
|
|
|
1373
1446
|
role is owner, admin, or member. mailbox_access.scope is all or selected. mailbox_ids is empty
|
|
1374
1447
|
for all; selected IDs may be limited to mailboxes the API key can reach.
|
|
1375
1448
|
|
|
1449
|
+
Member invitations and mailbox sharing:
|
|
1450
|
+
Needs the members:write permission, which is never granted by default. The invitee receives an
|
|
1451
|
+
email and creates their own account; no credential is returned. Without --all-mailboxes the
|
|
1452
|
+
invitation shares only the --mailbox IDs. --newsletter-editor applies to the member role only.
|
|
1453
|
+
|
|
1376
1454
|
Newsletter styling:
|
|
1377
1455
|
--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
1456
|
|
|
@@ -1398,6 +1476,10 @@ List cleanup:
|
|
|
1398
1476
|
Newsletter block prose:
|
|
1399
1477
|
Paragraph, quote, callout, list-item, and column bodies accept bare text or sanitized inline HTML. Use <p> and <br> for line breaks.
|
|
1400
1478
|
|
|
1479
|
+
Audience segments:
|
|
1480
|
+
--definition <JSON|@file> {"match":"all"|"any","rules":[...]} with 1 to 10 rules, for example {"kind":"merge_field","field":"role","operator":"equals","value":"founder"}. Rule kinds: merge_field, join_date, engagement_tier, last_sign_of_life, issues_received.
|
|
1481
|
+
A segment's id is the segment_id a newsletter block's visibility takes. Delete is refused while a newsletter sends to the segment.
|
|
1482
|
+
|
|
1401
1483
|
Newsletter feeds:
|
|
1402
1484
|
--feed-entry-url <url> Link this newsletter's Atom feed title to an http(s) URL instead of its Shipmail archive.
|
|
1403
1485
|
|
|
@@ -1580,24 +1662,17 @@ async function sendMessageParams(options) {
|
|
|
1580
1662
|
const cc = messageRecipients(options, "cc");
|
|
1581
1663
|
const bcc = messageRecipients(options, "bcc");
|
|
1582
1664
|
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
1665
|
const headers = outboundMessageHeaders(options);
|
|
1587
1666
|
const metadata = await messageMetadata(options);
|
|
1588
1667
|
if (!mailboxId && !from) throw new CliUsageError("Provide --mailbox-id or --from.");
|
|
1589
1668
|
if (to.length === 0) throw new CliUsageError("Provide at least one --to recipient.");
|
|
1590
|
-
|
|
1591
|
-
return {
|
|
1669
|
+
const common = {
|
|
1592
1670
|
...mailboxId ? { mailbox_id: mailboxId } : {},
|
|
1593
1671
|
...from ? { from } : {},
|
|
1594
1672
|
to,
|
|
1595
1673
|
...cc.length > 0 ? { cc } : {},
|
|
1596
1674
|
...bcc.length > 0 ? { bcc } : {},
|
|
1597
1675
|
...replyTo ? { reply_to: replyTo } : {},
|
|
1598
|
-
subject,
|
|
1599
|
-
...html ? { html } : {},
|
|
1600
|
-
...text ? { text } : {},
|
|
1601
1676
|
...getString(options, "client-reference") ? { client_reference: getString(options, "client-reference") } : {},
|
|
1602
1677
|
...metadata ? { metadata } : {},
|
|
1603
1678
|
...getString(options, "source-rfc-message-id") ? { source_rfc_message_id: getString(options, "source-rfc-message-id") } : {},
|
|
@@ -1608,6 +1683,10 @@ async function sendMessageParams(options) {
|
|
|
1608
1683
|
const trackReply = getBoolean(options, "track-reply");
|
|
1609
1684
|
return trackReply === void 0 ? {} : { track_reply: trackReply };
|
|
1610
1685
|
})(),
|
|
1686
|
+
...(() => {
|
|
1687
|
+
const addSignature = getBoolean(options, "add-signature");
|
|
1688
|
+
return addSignature === void 0 ? {} : { add_signature: addSignature };
|
|
1689
|
+
})(),
|
|
1611
1690
|
...getString(options, "sandbox-outcome") ? {
|
|
1612
1691
|
sandbox_outcome: getEnum(options, "sandbox-outcome", [
|
|
1613
1692
|
"delivered",
|
|
@@ -1616,10 +1695,44 @@ async function sendMessageParams(options) {
|
|
|
1616
1695
|
])
|
|
1617
1696
|
} : {}
|
|
1618
1697
|
};
|
|
1698
|
+
const templateId = getString(options, "template");
|
|
1699
|
+
const templateVersion = getNumber(options, "template-version");
|
|
1700
|
+
const variables = getKeyValues(options, "var");
|
|
1701
|
+
if (templateId) {
|
|
1702
|
+
for (const key of ["subject", "html", "text"]) {
|
|
1703
|
+
if (optionValue(options, key) !== void 0) {
|
|
1704
|
+
throw new CliUsageError(
|
|
1705
|
+
`--${key} cannot be used with --template: the template supplies the subject and body.`
|
|
1706
|
+
);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
return {
|
|
1710
|
+
...common,
|
|
1711
|
+
template: {
|
|
1712
|
+
id: templateId,
|
|
1713
|
+
...templateVersion !== void 0 ? { version: templateVersion } : {},
|
|
1714
|
+
...variables ? { variables } : {}
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
if (templateVersion !== void 0 || variables) {
|
|
1719
|
+
throw new CliUsageError("--template-version and --var need --template.");
|
|
1720
|
+
}
|
|
1721
|
+
const subject = getRequiredString(options, "subject");
|
|
1722
|
+
const text = getString(options, "text");
|
|
1723
|
+
const html = getString(options, "html");
|
|
1724
|
+
if (!text && !html) throw new CliUsageError("Provide --text or --html.");
|
|
1725
|
+
return {
|
|
1726
|
+
...common,
|
|
1727
|
+
subject,
|
|
1728
|
+
...html ? { html } : {},
|
|
1729
|
+
...text ? { text } : {}
|
|
1730
|
+
};
|
|
1619
1731
|
}
|
|
1620
1732
|
async function replyToMessageParams(options) {
|
|
1621
1733
|
const to = messageRecipients(options, "to");
|
|
1622
1734
|
const cc = messageRecipients(options, "cc");
|
|
1735
|
+
const bcc = messageRecipients(options, "bcc");
|
|
1623
1736
|
const text = getString(options, "text");
|
|
1624
1737
|
const html = getString(options, "html");
|
|
1625
1738
|
const headers = outboundMessageHeaders(options);
|
|
@@ -1629,6 +1742,7 @@ async function replyToMessageParams(options) {
|
|
|
1629
1742
|
return {
|
|
1630
1743
|
to,
|
|
1631
1744
|
...cc.length > 0 ? { cc } : {},
|
|
1745
|
+
...bcc.length > 0 ? { bcc } : {},
|
|
1632
1746
|
...html ? { html } : {},
|
|
1633
1747
|
...text ? { text } : {},
|
|
1634
1748
|
...getString(options, "client-reference") ? { client_reference: getString(options, "client-reference") } : {},
|
|
@@ -1640,6 +1754,10 @@ async function replyToMessageParams(options) {
|
|
|
1640
1754
|
const trackReply = getBoolean(options, "track-reply");
|
|
1641
1755
|
return trackReply === void 0 ? {} : { track_reply: trackReply };
|
|
1642
1756
|
})(),
|
|
1757
|
+
...(() => {
|
|
1758
|
+
const addSignature = getBoolean(options, "add-signature");
|
|
1759
|
+
return addSignature === void 0 ? {} : { add_signature: addSignature };
|
|
1760
|
+
})(),
|
|
1643
1761
|
...getString(options, "sandbox-outcome") ? {
|
|
1644
1762
|
sandbox_outcome: getEnum(options, "sandbox-outcome", [
|
|
1645
1763
|
"delivered",
|
|
@@ -2392,6 +2510,24 @@ async function runMessages(client, parsed, out) {
|
|
|
2392
2510
|
);
|
|
2393
2511
|
return;
|
|
2394
2512
|
}
|
|
2513
|
+
if (subcommand === "invitation" && parsed.positional[2] === "respond") {
|
|
2514
|
+
const response = getEnum(parsed.options, "response", [
|
|
2515
|
+
"accepted",
|
|
2516
|
+
"tentative",
|
|
2517
|
+
"declined"
|
|
2518
|
+
]);
|
|
2519
|
+
if (!response) {
|
|
2520
|
+
throw new CliUsageError("--response must be one of: accepted, tentative, declined.");
|
|
2521
|
+
}
|
|
2522
|
+
writeJson(
|
|
2523
|
+
out,
|
|
2524
|
+
await client.messages.respondToInvitation(
|
|
2525
|
+
requirePositional(parsed.positional, 3, "message id"),
|
|
2526
|
+
{ response }
|
|
2527
|
+
)
|
|
2528
|
+
);
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2395
2531
|
if (subcommand === "get") {
|
|
2396
2532
|
writeJson(
|
|
2397
2533
|
out,
|
|
@@ -2532,6 +2668,45 @@ async function runMembers(client, parsed, out) {
|
|
|
2532
2668
|
);
|
|
2533
2669
|
return;
|
|
2534
2670
|
}
|
|
2671
|
+
if (subcommand === "invite") {
|
|
2672
|
+
const idempotencyKey = getString(parsed.options, "idempotency-key");
|
|
2673
|
+
writeJson(
|
|
2674
|
+
out,
|
|
2675
|
+
await client.members.invite(
|
|
2676
|
+
inviteMemberParams(parsed),
|
|
2677
|
+
idempotencyKey ? { idempotencyKey } : void 0
|
|
2678
|
+
)
|
|
2679
|
+
);
|
|
2680
|
+
return;
|
|
2681
|
+
}
|
|
2682
|
+
if (subcommand === "invitations") {
|
|
2683
|
+
const command = requirePositional(parsed.positional, 2, "invitations command");
|
|
2684
|
+
if (command === "list") {
|
|
2685
|
+
writeJson(out, await client.members.listInvitations());
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
if (command === "cancel") {
|
|
2689
|
+
const invitationId = requirePositional(parsed.positional, 3, "invitation id");
|
|
2690
|
+
await client.members.cancelInvitation(invitationId);
|
|
2691
|
+
writeJson(out, { ok: true, id: invitationId });
|
|
2692
|
+
return;
|
|
2693
|
+
}
|
|
2694
|
+
throw new CliUsageError("Unknown members invitations command.");
|
|
2695
|
+
}
|
|
2696
|
+
if (subcommand === "mailboxes") {
|
|
2697
|
+
const command = requirePositional(parsed.positional, 2, "mailboxes command");
|
|
2698
|
+
const memberId2 = requirePositional(parsed.positional, 3, "member id");
|
|
2699
|
+
const mailboxId = requirePositional(parsed.positional, 4, "mailbox id");
|
|
2700
|
+
if (command === "grant") {
|
|
2701
|
+
writeJson(out, await client.members.grantMailboxAccess(memberId2, mailboxId));
|
|
2702
|
+
return;
|
|
2703
|
+
}
|
|
2704
|
+
if (command === "revoke") {
|
|
2705
|
+
writeJson(out, await client.members.revokeMailboxAccess(memberId2, mailboxId));
|
|
2706
|
+
return;
|
|
2707
|
+
}
|
|
2708
|
+
throw new CliUsageError("Unknown members mailboxes command.");
|
|
2709
|
+
}
|
|
2535
2710
|
const memberId = requirePositional(parsed.positional, 2, "member id");
|
|
2536
2711
|
if (subcommand === "get") {
|
|
2537
2712
|
writeJson(out, await client.members.get(memberId));
|
|
@@ -2539,11 +2714,59 @@ async function runMembers(client, parsed, out) {
|
|
|
2539
2714
|
}
|
|
2540
2715
|
throw new CliUsageError("Unknown members command.");
|
|
2541
2716
|
}
|
|
2542
|
-
var
|
|
2717
|
+
var INVITATION_ROLES = ["admin", "member"];
|
|
2718
|
+
function inviteMemberParams(parsed) {
|
|
2719
|
+
const email = requirePositional(parsed.positional, 2, "email");
|
|
2720
|
+
const rawRole = getRequiredString(parsed.options, "role");
|
|
2721
|
+
const role = INVITATION_ROLES.find((candidate) => candidate === rawRole);
|
|
2722
|
+
if (!role) throw new CliUsageError("--role must be admin or member.");
|
|
2723
|
+
const allMailboxes = getBoolean(parsed.options, "all-mailboxes") === true;
|
|
2724
|
+
const mailboxIds = getStringList(parsed.options, "mailbox");
|
|
2725
|
+
if (allMailboxes && mailboxIds.length > 0) {
|
|
2726
|
+
throw new CliUsageError("Use either --all-mailboxes or --mailbox, not both.");
|
|
2727
|
+
}
|
|
2728
|
+
const newsletterEditor = getBoolean(parsed.options, "newsletter-editor") === true;
|
|
2729
|
+
if (newsletterEditor && role !== "member") {
|
|
2730
|
+
throw new CliUsageError("--newsletter-editor applies to the member role only.");
|
|
2731
|
+
}
|
|
2732
|
+
return {
|
|
2733
|
+
email,
|
|
2734
|
+
role,
|
|
2735
|
+
...allMailboxes ? { mailbox_access: "all" } : {},
|
|
2736
|
+
...mailboxIds.length > 0 ? { mailbox_ids: mailboxIds } : {},
|
|
2737
|
+
...newsletterEditor ? { newsletter_role: "editor" } : {}
|
|
2738
|
+
};
|
|
2739
|
+
}
|
|
2740
|
+
async function runSmtpCredentials(client, parsed, out) {
|
|
2741
|
+
const subcommand = parsed.positional[1];
|
|
2742
|
+
if (subcommand === "list") {
|
|
2743
|
+
writeJson(out, await client.smtpCredentials.list({ from: getString(parsed.options, "from") }));
|
|
2744
|
+
return;
|
|
2745
|
+
}
|
|
2746
|
+
if (subcommand === "create") {
|
|
2747
|
+
writeJson(
|
|
2748
|
+
out,
|
|
2749
|
+
await client.smtpCredentials.create({
|
|
2750
|
+
from: getRequiredString(parsed.options, "from"),
|
|
2751
|
+
name: getRequiredString(parsed.options, "name")
|
|
2752
|
+
})
|
|
2753
|
+
);
|
|
2754
|
+
return;
|
|
2755
|
+
}
|
|
2756
|
+
if (subcommand === "revoke") {
|
|
2757
|
+
const id = requirePositional(parsed.positional, 2, "SMTP credential id");
|
|
2758
|
+
await client.smtpCredentials.revoke(id);
|
|
2759
|
+
writeJson(out, { ok: true, id });
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2762
|
+
throw new CliUsageError("Unknown smtp-credentials command.");
|
|
2763
|
+
}
|
|
2764
|
+
var SUPPRESSION_SCOPES = ["all", "marketing", "transactional", "mailbox"];
|
|
2765
|
+
var CREATE_SUPPRESSION_SCOPES = ["all", "marketing"];
|
|
2543
2766
|
async function runSuppressions(client, parsed, out) {
|
|
2544
2767
|
const subcommand = parsed.positional[1];
|
|
2545
2768
|
if (subcommand === "add") {
|
|
2546
|
-
const scope = getEnum(parsed.options, "scope",
|
|
2769
|
+
const scope = getEnum(parsed.options, "scope", CREATE_SUPPRESSION_SCOPES);
|
|
2547
2770
|
writeJson(
|
|
2548
2771
|
out,
|
|
2549
2772
|
await client.suppressions.create({
|
|
@@ -2572,6 +2795,241 @@ async function runSuppressions(client, parsed, out) {
|
|
|
2572
2795
|
}
|
|
2573
2796
|
throw new CliUsageError("Unknown suppressions command.");
|
|
2574
2797
|
}
|
|
2798
|
+
function isCustomerFields(value) {
|
|
2799
|
+
return isJsonObject(value) && Object.values(value).every(
|
|
2800
|
+
(item) => typeof item === "string" || typeof item === "number" || typeof item === "boolean"
|
|
2801
|
+
);
|
|
2802
|
+
}
|
|
2803
|
+
async function customerProfileParams(options) {
|
|
2804
|
+
const emails = getStringList(options, "email");
|
|
2805
|
+
const domains = getStringList(options, "domain");
|
|
2806
|
+
const rawFields = getString(options, "fields");
|
|
2807
|
+
const fields = rawFields === void 0 ? void 0 : await readJsonInput(rawFields);
|
|
2808
|
+
if (fields !== void 0 && !isCustomerFields(fields)) {
|
|
2809
|
+
throw new CliUsageError(
|
|
2810
|
+
"--fields must be a JSON object of string, number or boolean values, or @path/to/file.json."
|
|
2811
|
+
);
|
|
2812
|
+
}
|
|
2813
|
+
const serviceLevelId = getString(options, "service-level-id");
|
|
2814
|
+
const clearServiceLevel = getBoolean(options, "clear-service-level") === true;
|
|
2815
|
+
if (serviceLevelId !== void 0 && clearServiceLevel) {
|
|
2816
|
+
throw new CliUsageError("Use either --service-level-id or --clear-service-level, not both.");
|
|
2817
|
+
}
|
|
2818
|
+
const tagFlag = optionValue(options, "tag");
|
|
2819
|
+
if (typeof tagFlag === "boolean") throw new CliUsageError("Missing value for --tag.");
|
|
2820
|
+
const tags = tagFlag === void 0 ? void 0 : getStringList(options, "tag");
|
|
2821
|
+
const clearTags = getBoolean(options, "clear-tags") === true;
|
|
2822
|
+
if (tags !== void 0 && clearTags) {
|
|
2823
|
+
throw new CliUsageError("Use --tag or --clear-tags, not both.");
|
|
2824
|
+
}
|
|
2825
|
+
const name = getString(options, "name");
|
|
2826
|
+
const company = getString(options, "company");
|
|
2827
|
+
const notes = await readTextInput(getString(options, "notes"));
|
|
2828
|
+
return {
|
|
2829
|
+
...name !== void 0 ? { name } : {},
|
|
2830
|
+
...company !== void 0 ? { company } : {},
|
|
2831
|
+
...notes !== void 0 ? { notes } : {},
|
|
2832
|
+
...serviceLevelId !== void 0 ? { service_level_id: serviceLevelId } : {},
|
|
2833
|
+
...clearServiceLevel ? { service_level_id: null } : {},
|
|
2834
|
+
...fields !== void 0 ? { fields } : {},
|
|
2835
|
+
...tags !== void 0 ? { tags } : {},
|
|
2836
|
+
...clearTags ? { tags: [] } : {},
|
|
2837
|
+
...emails.length > 0 || domains.length > 0 ? {
|
|
2838
|
+
identifiers: [
|
|
2839
|
+
...emails.map((value) => ({ kind: "email", value })),
|
|
2840
|
+
...domains.map((value) => ({ kind: "domain", value }))
|
|
2841
|
+
]
|
|
2842
|
+
} : {}
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
async function runCustomers(client, parsed, out) {
|
|
2846
|
+
const subcommand = parsed.positional[1];
|
|
2847
|
+
if (subcommand === "list") {
|
|
2848
|
+
writeJson(
|
|
2849
|
+
out,
|
|
2850
|
+
await client.customers.list({
|
|
2851
|
+
cursor: getString(parsed.options, "cursor"),
|
|
2852
|
+
limit: getNumber(parsed.options, "limit"),
|
|
2853
|
+
email: getString(parsed.options, "email"),
|
|
2854
|
+
domain: getString(parsed.options, "domain"),
|
|
2855
|
+
external_source: getString(parsed.options, "external-source"),
|
|
2856
|
+
external_id: getString(parsed.options, "external-id"),
|
|
2857
|
+
q: getString(parsed.options, "q"),
|
|
2858
|
+
tag: getString(parsed.options, "tag")
|
|
2859
|
+
})
|
|
2860
|
+
);
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
if (subcommand === "get") {
|
|
2864
|
+
writeJson(
|
|
2865
|
+
out,
|
|
2866
|
+
await client.customers.get(requirePositional(parsed.positional, 2, "customer-id"))
|
|
2867
|
+
);
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
if (subcommand === "create") {
|
|
2871
|
+
writeJson(out, await client.customers.create(await customerProfileParams(parsed.options)));
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
if (subcommand === "update") {
|
|
2875
|
+
const id = requirePositional(parsed.positional, 2, "customer-id");
|
|
2876
|
+
writeJson(out, await client.customers.update(id, await customerProfileParams(parsed.options)));
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
if (subcommand === "upsert") {
|
|
2880
|
+
const source = requirePositional(parsed.positional, 2, "source");
|
|
2881
|
+
const externalId = requirePositional(parsed.positional, 3, "external-id");
|
|
2882
|
+
const url = getString(parsed.options, "url");
|
|
2883
|
+
writeJson(
|
|
2884
|
+
out,
|
|
2885
|
+
await client.customers.upsertByExternalId(source, externalId, {
|
|
2886
|
+
...await customerProfileParams(parsed.options),
|
|
2887
|
+
...url !== void 0 ? { url } : {}
|
|
2888
|
+
})
|
|
2889
|
+
);
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
if (subcommand === "delete") {
|
|
2893
|
+
const id = requirePositional(parsed.positional, 2, "customer-id");
|
|
2894
|
+
await client.customers.remove(id);
|
|
2895
|
+
writeJson(out, { ok: true, id });
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
if (subcommand === "reply-times") {
|
|
2899
|
+
writeJson(out, await client.customers.listServiceLevels());
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
if (subcommand === "fields") {
|
|
2903
|
+
writeJson(out, await client.customers.listFields());
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
throw new CliUsageError("Unknown customers command.");
|
|
2907
|
+
}
|
|
2908
|
+
async function templateCreateParams(options) {
|
|
2909
|
+
const html = await readTextInput(getString(options, "html"));
|
|
2910
|
+
const text = await readTextInput(getString(options, "text"));
|
|
2911
|
+
const fallbacks = getKeyValues(options, "fallback");
|
|
2912
|
+
const publish = getBoolean(options, "publish");
|
|
2913
|
+
if (!html && !text) throw new CliUsageError("Provide --html or --text.");
|
|
2914
|
+
return {
|
|
2915
|
+
name: getRequiredString(options, "name"),
|
|
2916
|
+
subject: getRequiredString(options, "subject"),
|
|
2917
|
+
...html ? { html } : {},
|
|
2918
|
+
...text ? { text } : {},
|
|
2919
|
+
...fallbacks ? { fallbacks } : {},
|
|
2920
|
+
...publish !== void 0 ? { publish } : {}
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
async function templateBodyUpdate(options, key) {
|
|
2924
|
+
const value = await readTextInput(getString(options, key));
|
|
2925
|
+
const clear = getBoolean(options, `clear-${key}`) === true;
|
|
2926
|
+
if (value !== void 0 && clear) {
|
|
2927
|
+
throw new CliUsageError(`Use either --${key} or --clear-${key}, not both.`);
|
|
2928
|
+
}
|
|
2929
|
+
return clear ? null : value;
|
|
2930
|
+
}
|
|
2931
|
+
async function templateUpdateParams(options) {
|
|
2932
|
+
const name = getString(options, "name");
|
|
2933
|
+
const subject = getString(options, "subject");
|
|
2934
|
+
const html = await templateBodyUpdate(options, "html");
|
|
2935
|
+
const text = await templateBodyUpdate(options, "text");
|
|
2936
|
+
const fallbacks = getKeyValues(options, "fallback");
|
|
2937
|
+
const clearFallbacks = getBoolean(options, "clear-fallbacks") === true;
|
|
2938
|
+
if (fallbacks && clearFallbacks) {
|
|
2939
|
+
throw new CliUsageError("Use either --fallback or --clear-fallbacks, not both.");
|
|
2940
|
+
}
|
|
2941
|
+
const params = {
|
|
2942
|
+
...name !== void 0 ? { name } : {},
|
|
2943
|
+
...subject !== void 0 ? { subject } : {},
|
|
2944
|
+
...html !== void 0 ? { html } : {},
|
|
2945
|
+
...text !== void 0 ? { text } : {},
|
|
2946
|
+
...fallbacks ? { fallbacks } : clearFallbacks ? { fallbacks: {} } : {}
|
|
2947
|
+
};
|
|
2948
|
+
if (name === void 0 && subject === void 0 && html === void 0 && text === void 0 && params.fallbacks === void 0) {
|
|
2949
|
+
throw new CliUsageError("Provide at least one field to change.");
|
|
2950
|
+
}
|
|
2951
|
+
return params;
|
|
2952
|
+
}
|
|
2953
|
+
function templateVersionPositional(positional) {
|
|
2954
|
+
const raw = requirePositional(positional, 3, "version");
|
|
2955
|
+
const version = Number(raw);
|
|
2956
|
+
if (!Number.isInteger(version) || version < 1) {
|
|
2957
|
+
throw new CliUsageError("Version must be a positive integer.");
|
|
2958
|
+
}
|
|
2959
|
+
return version;
|
|
2960
|
+
}
|
|
2961
|
+
async function runTemplates(client, parsed, out) {
|
|
2962
|
+
const subcommand = parsed.positional[1];
|
|
2963
|
+
if (subcommand === "list") {
|
|
2964
|
+
writeJson(
|
|
2965
|
+
out,
|
|
2966
|
+
await client.templates.list({
|
|
2967
|
+
cursor: getString(parsed.options, "cursor"),
|
|
2968
|
+
limit: getNumber(parsed.options, "limit")
|
|
2969
|
+
})
|
|
2970
|
+
);
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
if (subcommand === "create") {
|
|
2974
|
+
writeJson(out, await client.templates.create(await templateCreateParams(parsed.options)));
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
const id = requirePositional(parsed.positional, 2, "template id");
|
|
2978
|
+
if (subcommand === "get") {
|
|
2979
|
+
writeJson(out, await client.templates.get(id));
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
if (subcommand === "update") {
|
|
2983
|
+
writeJson(out, await client.templates.update(id, await templateUpdateParams(parsed.options)));
|
|
2984
|
+
return;
|
|
2985
|
+
}
|
|
2986
|
+
if (subcommand === "delete") {
|
|
2987
|
+
await client.templates.delete(id);
|
|
2988
|
+
writeJson(out, { ok: true, id });
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
if (subcommand === "publish") {
|
|
2992
|
+
writeJson(out, await client.templates.publish(id));
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
2995
|
+
if (subcommand === "render") {
|
|
2996
|
+
const variables = getKeyValues(parsed.options, "var");
|
|
2997
|
+
const version = getNumber(parsed.options, "template-version");
|
|
2998
|
+
writeJson(
|
|
2999
|
+
out,
|
|
3000
|
+
await client.templates.render(id, {
|
|
3001
|
+
...variables ? { variables } : {},
|
|
3002
|
+
...version !== void 0 ? { version } : {}
|
|
3003
|
+
})
|
|
3004
|
+
);
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
if (subcommand === "versions") {
|
|
3008
|
+
writeJson(
|
|
3009
|
+
out,
|
|
3010
|
+
await client.templates.listVersions(id, {
|
|
3011
|
+
cursor: getString(parsed.options, "cursor"),
|
|
3012
|
+
limit: getNumber(parsed.options, "limit")
|
|
3013
|
+
})
|
|
3014
|
+
);
|
|
3015
|
+
return;
|
|
3016
|
+
}
|
|
3017
|
+
if (subcommand === "version") {
|
|
3018
|
+
writeJson(
|
|
3019
|
+
out,
|
|
3020
|
+
await client.templates.getVersion(id, templateVersionPositional(parsed.positional))
|
|
3021
|
+
);
|
|
3022
|
+
return;
|
|
3023
|
+
}
|
|
3024
|
+
if (subcommand === "restore") {
|
|
3025
|
+
writeJson(
|
|
3026
|
+
out,
|
|
3027
|
+
await client.templates.restoreVersion(id, templateVersionPositional(parsed.positional))
|
|
3028
|
+
);
|
|
3029
|
+
return;
|
|
3030
|
+
}
|
|
3031
|
+
throw new CliUsageError("Unknown templates command.");
|
|
3032
|
+
}
|
|
2575
3033
|
var SUBSCRIBER_STATUS_VALUES = [
|
|
2576
3034
|
"subscribed",
|
|
2577
3035
|
"pending",
|
|
@@ -2673,6 +3131,17 @@ async function runSubscribers(client, parsed, out) {
|
|
|
2673
3131
|
);
|
|
2674
3132
|
return;
|
|
2675
3133
|
}
|
|
3134
|
+
if (action === "resend-confirmation") {
|
|
3135
|
+
const subscriberIds = getStringList(parsed.options, "subscriber");
|
|
3136
|
+
writeJson(
|
|
3137
|
+
out,
|
|
3138
|
+
await client.audiences.subscribers.resendConfirmation(
|
|
3139
|
+
audienceId,
|
|
3140
|
+
subscriberIds.length > 0 ? { subscriber_ids: subscriberIds } : {}
|
|
3141
|
+
)
|
|
3142
|
+
);
|
|
3143
|
+
return;
|
|
3144
|
+
}
|
|
2676
3145
|
if (action === "remove") {
|
|
2677
3146
|
const subscriberId = requirePositional(parsed.positional, 4, "subscriber id");
|
|
2678
3147
|
await client.audiences.subscribers.remove(audienceId, subscriberId);
|
|
@@ -2820,6 +3289,62 @@ async function runAudienceWelcome(client, parsed, out) {
|
|
|
2820
3289
|
}
|
|
2821
3290
|
throw new CliUsageError("Unknown audiences welcome command.");
|
|
2822
3291
|
}
|
|
3292
|
+
async function runAudienceSegments(client, parsed, out) {
|
|
3293
|
+
const action = parsed.positional[2];
|
|
3294
|
+
const audienceId = requirePositional(parsed.positional, 3, "audience id");
|
|
3295
|
+
if (action === "list") {
|
|
3296
|
+
writeJson(out, await client.audiences.segments.list(audienceId));
|
|
3297
|
+
return;
|
|
3298
|
+
}
|
|
3299
|
+
const idempotencyKey = getString(parsed.options, "idempotency-key");
|
|
3300
|
+
const methodOptions = idempotencyKey ? { idempotencyKey } : void 0;
|
|
3301
|
+
if (action === "create") {
|
|
3302
|
+
writeJson(
|
|
3303
|
+
out,
|
|
3304
|
+
await client.audiences.segments.create(
|
|
3305
|
+
audienceId,
|
|
3306
|
+
{
|
|
3307
|
+
name: getRequiredString(parsed.options, "name"),
|
|
3308
|
+
definition: await segmentDefinition(getRequiredString(parsed.options, "definition"))
|
|
3309
|
+
},
|
|
3310
|
+
methodOptions
|
|
3311
|
+
)
|
|
3312
|
+
);
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3315
|
+
const segmentId = requirePositional(parsed.positional, 4, "segment id");
|
|
3316
|
+
if (action === "get") {
|
|
3317
|
+
writeJson(out, await client.audiences.segments.get(audienceId, segmentId));
|
|
3318
|
+
return;
|
|
3319
|
+
}
|
|
3320
|
+
if (action === "update") {
|
|
3321
|
+
const name = getString(parsed.options, "name");
|
|
3322
|
+
const definitionInput = getString(parsed.options, "definition");
|
|
3323
|
+
if (name === void 0 && definitionInput === void 0) {
|
|
3324
|
+
throw new CliUsageError("Provide --name, --definition, or both.");
|
|
3325
|
+
}
|
|
3326
|
+
const definition = definitionInput === void 0 ? void 0 : await segmentDefinition(definitionInput);
|
|
3327
|
+
writeJson(
|
|
3328
|
+
out,
|
|
3329
|
+
await client.audiences.segments.update(
|
|
3330
|
+
audienceId,
|
|
3331
|
+
segmentId,
|
|
3332
|
+
{
|
|
3333
|
+
...name !== void 0 ? { name } : {},
|
|
3334
|
+
...definition ? { definition } : {}
|
|
3335
|
+
},
|
|
3336
|
+
methodOptions
|
|
3337
|
+
)
|
|
3338
|
+
);
|
|
3339
|
+
return;
|
|
3340
|
+
}
|
|
3341
|
+
if (action === "delete") {
|
|
3342
|
+
await client.audiences.segments.delete(audienceId, segmentId);
|
|
3343
|
+
writeJson(out, { ok: true, id: segmentId });
|
|
3344
|
+
return;
|
|
3345
|
+
}
|
|
3346
|
+
throw new CliUsageError("Unknown audiences segments command.");
|
|
3347
|
+
}
|
|
2823
3348
|
async function runAudiences(client, parsed, out) {
|
|
2824
3349
|
const subcommand = parsed.positional[1];
|
|
2825
3350
|
if (subcommand === "subscribers") {
|
|
@@ -2834,6 +3359,10 @@ async function runAudiences(client, parsed, out) {
|
|
|
2834
3359
|
await runAudienceWelcome(client, parsed, out);
|
|
2835
3360
|
return;
|
|
2836
3361
|
}
|
|
3362
|
+
if (subcommand === "segments") {
|
|
3363
|
+
await runAudienceSegments(client, parsed, out);
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
2837
3366
|
if (subcommand === "list") {
|
|
2838
3367
|
writeJson(
|
|
2839
3368
|
out,
|
|
@@ -3520,6 +4049,13 @@ ${JSON.stringify(err.body, null, 2)}
|
|
|
3520
4049
|
const messageId = shipmailError instanceof ReconciliationRequiredError && shipmailError.messageId ? ` message_id=${shipmailError.messageId}` : "";
|
|
3521
4050
|
errOut.write(`Error: ${shipmailError.message}${status}${requestId}${messageId}
|
|
3522
4051
|
`);
|
|
4052
|
+
if (shipmailError instanceof ValidationError) {
|
|
4053
|
+
for (const detail of shipmailError.details ?? []) {
|
|
4054
|
+
const code = detail.code ? ` (${detail.code})` : "";
|
|
4055
|
+
errOut.write(` ${detail.field}: ${detail.message}${code}
|
|
4056
|
+
`);
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
3523
4059
|
if (shipmailError.actionUrl) {
|
|
3524
4060
|
const reason = shipmailError.reason ? ` (${shipmailError.reason})` : "";
|
|
3525
4061
|
errOut.write(`Ask the organization owner to open${reason}: ${shipmailError.actionUrl}
|
|
@@ -3602,7 +4138,10 @@ async function runCli(argv, options = {}) {
|
|
|
3602
4138
|
else if (command === "scheduled-messages") await runScheduledMessages(client, parsed, out);
|
|
3603
4139
|
else if (command === "reply-scans") await runReplyScans(client, parsed, out);
|
|
3604
4140
|
else if (command === "webhooks") await runWebhooks(client, parsed, out);
|
|
4141
|
+
else if (command === "smtp-credentials") await runSmtpCredentials(client, parsed, out);
|
|
3605
4142
|
else if (command === "suppressions") await runSuppressions(client, parsed, out);
|
|
4143
|
+
else if (command === "customers") await runCustomers(client, parsed, out);
|
|
4144
|
+
else if (command === "templates") await runTemplates(client, parsed, out);
|
|
3606
4145
|
else if (command === "newsletters") await runNewsletters(client, parsed, out);
|
|
3607
4146
|
else if (command === "newsletter-assets")
|
|
3608
4147
|
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.8",
|
|
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.9",
|
|
48
48
|
"zod": "^4.6.5"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|