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.
Files changed (3) hide show
  1. package/README.md +45 -2
  2. package/dist/cli.js +555 -16
  3. 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 subscribers add|list|get|by-email|update|unsubscribe|resubscribe|remove`
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.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
- if (!text && !html) throw new CliUsageError("Provide --text or --html.");
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 SUPPRESSION_SCOPES = ["all", "marketing"];
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", SUPPRESSION_SCOPES);
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.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.7",
47
+ "shipmail": "0.6.9",
48
48
  "zod": "^4.6.5"
49
49
  },
50
50
  "devDependencies": {