shipmail-cli 0.1.9 → 0.2.0

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 +16 -2
  2. package/dist/cli.js +169 -10
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -14,7 +14,7 @@ Most commands require an API key. Create one in the Shipmail dashboard and expor
14
14
  export SHIPMAIL_API_KEY="sm_live_..."
15
15
  ```
16
16
 
17
- You can also pass `--api-key` to any command. Use `SHIPMAIL_BASE_URL` or `--base-url` when targeting a non-production API.
17
+ You can also pass `--api-key` to any command. Use `SHIPMAIL_BASE_URL` or `--base-url` when targeting a non-production API. Approved partners can set `SHIPMAIL_ORGANIZATION_ID` or pass `--organization-id` for delegated infrastructure calls.
18
18
 
19
19
  ## Common Commands
20
20
 
@@ -34,6 +34,9 @@ shipmail mailboxes resume mbx_123
34
34
  shipmail mailboxes forwarding-add mbx_123 --destination owner@example.net
35
35
  shipmail mailboxes forwarding mbx_123
36
36
  shipmail mailboxes forwarding-remove mbx_123 fwd_123
37
+ shipmail mailboxes app-passwords list mbx_123
38
+ shipmail mailboxes app-passwords create mbx_123 --name "Desktop mail" --expires-at 2026-10-01T00:00:00Z
39
+ shipmail mailboxes app-passwords revoke mbx_123 appwd_123
37
40
 
38
41
  # Send email
39
42
  shipmail messages send \
@@ -79,6 +82,14 @@ shipmail webhooks create \
79
82
  --events message.received,message.bounced
80
83
  shipmail webhooks deliveries whk_123 --status failed
81
84
  shipmail webhooks replay-delivery whk_123 dlv_123 --idempotency-key replay-123
85
+
86
+ # Partner beta lifecycle and delegated infrastructure
87
+ shipmail partner organizations create --name "Operator" --external-reference operator_123 --owner-email owner@example.com --mailbox-limit 3 --data-classification internal_test
88
+ shipmail partner usage
89
+ shipmail partner credential-grants list
90
+ shipmail partner credential-grants consume grant_123 --name "Embedded webmail"
91
+ shipmail domains list --organization-id 00000000-0000-4000-8000-000000000123
92
+ shipmail mailboxes create --organization-id 00000000-0000-4000-8000-000000000123 --domain-id dom_123 --address support --generate-password
82
93
  ```
83
94
 
84
95
  `newsletter-assets list` includes current newsletter media storage usage in the JSON response.
@@ -104,7 +115,7 @@ Use repeatable `--header "Name: value"` flags for request headers. Use `--output
104
115
  - `status`
105
116
  - `api <METHOD> <PATH>`
106
117
  - `domains list|search|create|get|verify|delete`
107
- - `mailboxes list|create|get|suspend|resume|forwarding|forwarding-add|forwarding-remove|folders|identities|rules`
118
+ - `mailboxes list|create|get|suspend|resume|forwarding|forwarding-add|forwarding-remove|app-passwords|folders|identities|rules`
108
119
  - `inbox list|thread|update|move`
109
120
  - `messages send|list|get|reply` (test keys also support `--sandbox-outcome`)
110
121
  - `mailboxes sandbox-inbound`
@@ -113,5 +124,8 @@ Use repeatable `--header "Name: value"` flags for request headers. Use `--output
113
124
  - `newsletter-domains list`
114
125
  - `newsletters create|update|preview|preflight|test-send|schedule|cancel|resume`
115
126
  - `newsletter-assets list|upload|register-url`
127
+ - `partner organizations list|create|get|update|invite|suspend|resume|offboard`
128
+ - `partner credential-grants list|consume`
129
+ - `partner usage`
116
130
 
117
131
  Run `shipmail --help` for flags and examples.
package/dist/cli.js CHANGED
@@ -6,13 +6,14 @@ import { readFile, writeFile } from "fs/promises";
6
6
  import { env as processEnv, exit, stderr, stdout } from "process";
7
7
  import { fileURLToPath } from "url";
8
8
  import {
9
+ CONFERENCING_PROVIDER_IDS,
9
10
  ShipMailClient,
10
11
  ShipMailError,
11
12
  WEBHOOK_EVENT_TYPES
12
13
  } from "shipmail";
13
14
 
14
15
  // src/version.ts
15
- var VERSION = "0.1.9";
16
+ var VERSION = "0.2.0";
16
17
 
17
18
  // src/cli.ts
18
19
  var DEFAULT_BASE_URL = "https://shipmail.to/api/v1";
@@ -123,6 +124,9 @@ function apiKey(parsed, env) {
123
124
  function baseUrl(parsed, env) {
124
125
  return normalizeBaseUrl(getString(parsed.options, "base-url") ?? env["SHIPMAIL_BASE_URL"]);
125
126
  }
127
+ function organizationId(parsed, env) {
128
+ return getString(parsed.options, "organization-id") ?? env["SHIPMAIL_ORGANIZATION_ID"];
129
+ }
126
130
  function requireApiKey(parsed, env) {
127
131
  const key = apiKey(parsed, env);
128
132
  if (!key) {
@@ -135,6 +139,7 @@ function createClient(parsed, options) {
135
139
  const config = {
136
140
  apiKey: requireApiKey(parsed, env),
137
141
  baseUrl: baseUrl(parsed, env),
142
+ organizationId: organizationId(parsed, env),
138
143
  ...options.fetch ? { fetch: options.fetch } : {}
139
144
  };
140
145
  return new ShipMailClient(config);
@@ -264,6 +269,10 @@ async function rawApiRequest(parsed, runOptions, method, path, body, authRequire
264
269
  const key = apiKey(parsed, env);
265
270
  if (key) headers["Authorization"] = `Bearer ${key}`;
266
271
  }
272
+ const targetOrganizationId = organizationId(parsed, env);
273
+ if (targetOrganizationId) {
274
+ headers["X-ShipMail-Organization-Id"] = targetOrganizationId;
275
+ }
267
276
  let requestBody;
268
277
  if (body !== void 0) {
269
278
  headers["Content-Type"] = "application/json";
@@ -315,12 +324,15 @@ Usage:
315
324
  shipmail domains verify <id>
316
325
  shipmail domains delete <id>
317
326
  shipmail mailboxes list [--domain-id dom] [--limit 25] [--cursor cur]
318
- shipmail mailboxes create --domain-id dom --address hello (--password value | --password-file path) [--display-name "Hello"]
327
+ shipmail mailboxes create --domain-id dom --address hello (--password value | --password-file path | --generate-password) [--display-name "Hello"]
319
328
  shipmail mailboxes get <id>
320
329
  shipmail mailboxes suspend <id>
321
330
  shipmail mailboxes resume <id>
322
331
  shipmail mailboxes export <id>
323
332
  shipmail mailboxes export-status <id> <export-id>
333
+ shipmail mailboxes app-passwords list <id>
334
+ shipmail mailboxes app-passwords create <id> --name "Desktop mail" [--expires-at 2026-10-01T00:00:00Z] [--allowed-cidr 203.0.113.0/24]
335
+ shipmail mailboxes app-passwords revoke <id> <app-password-id>
324
336
  shipmail mailboxes folders <id>
325
337
  shipmail mailboxes identities <id>
326
338
  shipmail mailboxes rules <id>
@@ -378,19 +390,36 @@ Usage:
378
390
  shipmail calendar events delete <event-id> --mailbox hello@example.com
379
391
  shipmail calendar availability --mailbox hello@example.com --from 2026-08-03T00:00:00Z --to 2026-08-04T00:00:00Z [--duration 30] [--days 1,2,3,4,5] [--window-start 540] [--window-end 1020]
380
392
  shipmail booking-pages list [--limit 25] [--cursor cur]
381
- shipmail booking-pages create --name "Intro" --mailbox hello@example.com --slug intro --duration-minutes 30 --availability-days 1,2,3,4,5 --window-start-minutes 540 --window-end-minutes 1020 --timezone America/New_York
393
+ shipmail booking-pages create --name "Intro" --mailbox hello@example.com --slug intro --duration-minutes 30 --availability-days 1,2,3,4,5 --window-start-minutes 540 --window-end-minutes 1020 --timezone America/New_York [--conferencing-provider zoom|google_meet]
382
394
  shipmail booking-pages get <id>
383
- shipmail booking-pages update <id> [--name "..."] [--active false]
395
+ shipmail booking-pages update <id> [--name "..."] [--conferencing-provider zoom|google_meet|none] [--active false]
384
396
  shipmail booking-pages delete <id>
397
+ shipmail partner organizations list
398
+ shipmail partner organizations create --name "Operator" --external-reference op_123 --owner-email owner@example.com [--mailbox-limit 3] [--data-classification internal_test|customer]
399
+ shipmail partner organizations get <id>
400
+ shipmail partner organizations update <id> [--name "Operator"] [--mailbox-limit 5]
401
+ shipmail partner organizations invite <id> [--owner-email owner@example.com]
402
+ shipmail partner organizations suspend <id>
403
+ shipmail partner organizations resume <id>
404
+ shipmail partner organizations offboard <id>
405
+ shipmail partner credential-grants list
406
+ shipmail partner credential-grants consume <grant-id> [--name "Marketstead webmail"] [--expires-at 2026-10-01T00:00:00Z] [--allowed-cidr 203.0.113.0/24]
407
+ shipmail partner usage
408
+
409
+ Booking page conferencing:
410
+ --conferencing-provider <zoom|google_meet> Create a meeting for every booking.
411
+ --conferencing-provider none Stop creating meeting links on update.
385
412
 
386
413
  Global options:
387
414
  --api-key <key> API key. Defaults to SHIPMAIL_API_KEY.
388
415
  --base-url <url> API base URL. Defaults to SHIPMAIL_BASE_URL or ${DEFAULT_BASE_URL}.
416
+ --organization-id Delegated child organization for supported infrastructure commands.
389
417
  --header "K: V" Extra header for the generic api command. Repeatable.
390
418
 
391
419
  Environment:
392
420
  SHIPMAIL_API_KEY API key for authenticated commands.
393
421
  SHIPMAIL_BASE_URL Optional API base URL.
422
+ SHIPMAIL_ORGANIZATION_ID Optional delegated child organization ID.
394
423
  `;
395
424
  }
396
425
  function requirePositional(positional, index, label) {
@@ -591,20 +620,25 @@ async function runMailboxes(client, parsed, out) {
591
620
  const displayName = getString(parsed.options, "display-name");
592
621
  const passwordValue = getString(parsed.options, "password");
593
622
  const passwordFile = getString(parsed.options, "password-file");
594
- if (passwordValue && passwordFile) {
595
- throw new CliUsageError("Pass either --password or --password-file, not both.");
623
+ const generatePassword = getBoolean(parsed.options, "generate-password") === true;
624
+ if ([Boolean(passwordValue), Boolean(passwordFile), generatePassword].filter(Boolean).length > 1) {
625
+ throw new CliUsageError(
626
+ "Pass only one of --password, --password-file, or --generate-password."
627
+ );
596
628
  }
597
- if (!passwordValue && !passwordFile) {
598
- throw new CliUsageError("Missing --password or --password-file.");
629
+ if (!passwordValue && !passwordFile && !generatePassword) {
630
+ throw new CliUsageError("Missing --password, --password-file, or --generate-password.");
599
631
  }
600
632
  const password = passwordFile ? (await readFile(passwordFile, "utf8")).replace(/\r?\n$/, "") : passwordValue;
601
- if (!password) throw new CliUsageError("Mailbox password must not be empty.");
633
+ if (!generatePassword && !password) {
634
+ throw new CliUsageError("Mailbox password must not be empty.");
635
+ }
602
636
  writeJson(
603
637
  out,
604
638
  await client.mailboxes.create({
605
639
  domain_id: getRequiredString(parsed.options, "domain-id"),
606
640
  address: getRequiredString(parsed.options, "address"),
607
- password,
641
+ ...generatePassword ? { generate_password: true } : { password },
608
642
  ...displayName ? { display_name: displayName } : {}
609
643
  })
610
644
  );
@@ -648,6 +682,32 @@ async function runMailboxes(client, parsed, out) {
648
682
  );
649
683
  return;
650
684
  }
685
+ if (subcommand === "app-passwords") {
686
+ const command = requirePositional(parsed.positional, 2, "app-password command");
687
+ const mailboxId = requirePositional(parsed.positional, 3, "mailbox id");
688
+ if (command === "list") {
689
+ writeJson(out, await client.mailboxes.listAppPasswords(mailboxId));
690
+ return;
691
+ }
692
+ if (command === "create") {
693
+ const expiresAt = getString(parsed.options, "expires-at");
694
+ const allowedCidrs = getStringList(parsed.options, "allowed-cidr");
695
+ const params = {
696
+ name: getRequiredString(parsed.options, "name"),
697
+ ...expiresAt ? { expires_at: expiresAt } : {},
698
+ ...allowedCidrs.length > 0 ? { allowed_cidrs: allowedCidrs } : {}
699
+ };
700
+ writeJson(out, await client.mailboxes.createAppPassword(mailboxId, params));
701
+ return;
702
+ }
703
+ if (command === "revoke") {
704
+ const appPasswordId = requirePositional(parsed.positional, 4, "app password id");
705
+ await client.mailboxes.revokeAppPassword(mailboxId, appPasswordId);
706
+ writeJson(out, { ok: true, id: appPasswordId });
707
+ return;
708
+ }
709
+ throw new CliUsageError("Unknown mailboxes app-passwords command.");
710
+ }
651
711
  if (subcommand === "folders") {
652
712
  writeJson(
653
713
  out,
@@ -1222,6 +1282,12 @@ function getEnum(options, key, allowed) {
1222
1282
  }
1223
1283
  return match;
1224
1284
  }
1285
+ function getConferencingProvider(options) {
1286
+ const value = getString(options, "conferencing-provider");
1287
+ if (value === void 0) return void 0;
1288
+ if (value === "none") return null;
1289
+ return getEnum(options, "conferencing-provider", CONFERENCING_PROVIDER_IDS);
1290
+ }
1225
1291
  function isReminderArray(value) {
1226
1292
  return Array.isArray(value) && value.every(
1227
1293
  (item) => typeof item === "object" && item !== null && !Array.isArray(item) && Number.isInteger(item.minutes_before)
@@ -1387,6 +1453,7 @@ async function runBookingPages(client, parsed, out) {
1387
1453
  buffer_minutes: getNumber(options, "buffer-minutes"),
1388
1454
  minimum_notice_minutes: getNumber(options, "minimum-notice-minutes"),
1389
1455
  max_advance_days: getNumber(options, "max-advance-days"),
1456
+ conferencing_provider: getConferencingProvider(options),
1390
1457
  active: getBoolean(options, "active")
1391
1458
  };
1392
1459
  writeJson(out, await client.bookingPages.create(params));
@@ -1415,6 +1482,7 @@ async function runBookingPages(client, parsed, out) {
1415
1482
  buffer_minutes: getNumber(options, "buffer-minutes"),
1416
1483
  minimum_notice_minutes: getNumber(options, "minimum-notice-minutes"),
1417
1484
  max_advance_days: getNumber(options, "max-advance-days"),
1485
+ conferencing_provider: getConferencingProvider(options),
1418
1486
  active: getBoolean(options, "active")
1419
1487
  };
1420
1488
  writeJson(out, await client.bookingPages.update(id, params));
@@ -1428,6 +1496,96 @@ async function runBookingPages(client, parsed, out) {
1428
1496
  }
1429
1497
  throw new CliUsageError("Unknown booking-pages command.");
1430
1498
  }
1499
+ async function runPartner(client, parsed, out) {
1500
+ const group = parsed.positional[1];
1501
+ if (group === "usage") {
1502
+ writeJson(out, await client.partner.usage());
1503
+ return;
1504
+ }
1505
+ if (group === "credential-grants") {
1506
+ const command2 = requirePositional(parsed.positional, 2, "credential grant command");
1507
+ if (command2 === "list") {
1508
+ writeJson(out, await client.partner.listMailboxCredentialGrants());
1509
+ return;
1510
+ }
1511
+ if (command2 !== "consume")
1512
+ throw new CliUsageError("Unknown partner credential-grants command.");
1513
+ const grantId = requirePositional(parsed.positional, 3, "credential grant id");
1514
+ const name = getString(parsed.options, "name");
1515
+ const expiresAt = getString(parsed.options, "expires-at");
1516
+ const allowedCidrs = getStringList(parsed.options, "allowed-cidr");
1517
+ writeJson(
1518
+ out,
1519
+ await client.partner.consumeMailboxCredentialGrant(grantId, {
1520
+ ...name ? { name } : {},
1521
+ ...expiresAt ? { expires_at: expiresAt } : {},
1522
+ ...allowedCidrs.length > 0 ? { allowed_cidrs: allowedCidrs } : {}
1523
+ })
1524
+ );
1525
+ return;
1526
+ }
1527
+ if (group !== "organizations") throw new CliUsageError("Unknown partner command.");
1528
+ const command = parsed.positional[2];
1529
+ if (command === "list") {
1530
+ writeJson(out, await client.partner.listOrganizations());
1531
+ return;
1532
+ }
1533
+ if (command === "create") {
1534
+ const classification = getString(parsed.options, "data-classification") ?? "customer";
1535
+ if (classification !== "internal_test" && classification !== "customer") {
1536
+ throw new CliUsageError("--data-classification must be internal_test or customer.");
1537
+ }
1538
+ const params = {
1539
+ name: getRequiredString(parsed.options, "name"),
1540
+ external_reference: getRequiredString(parsed.options, "external-reference"),
1541
+ owner_email: getRequiredString(parsed.options, "owner-email"),
1542
+ mailbox_limit: getNumber(parsed.options, "mailbox-limit") ?? 3,
1543
+ data_classification: classification
1544
+ };
1545
+ writeJson(out, await client.partner.createOrganization(params));
1546
+ return;
1547
+ }
1548
+ const id = requirePositional(parsed.positional, 3, "partner organization id");
1549
+ if (command === "get") {
1550
+ writeJson(out, await client.partner.getOrganization(id));
1551
+ return;
1552
+ }
1553
+ if (command === "update") {
1554
+ const name = getString(parsed.options, "name");
1555
+ const mailboxLimit = getNumber(parsed.options, "mailbox-limit");
1556
+ if (name === void 0 && mailboxLimit === void 0) {
1557
+ throw new CliUsageError("Provide --name or --mailbox-limit.");
1558
+ }
1559
+ const params = {
1560
+ ...name !== void 0 ? { name } : {},
1561
+ ...mailboxLimit !== void 0 ? { mailbox_limit: mailboxLimit } : {}
1562
+ };
1563
+ writeJson(out, await client.partner.updateOrganization(id, params));
1564
+ return;
1565
+ }
1566
+ if (command === "invite") {
1567
+ writeJson(
1568
+ out,
1569
+ await client.partner.resendOwnershipInvitation(id, {
1570
+ owner_email: getString(parsed.options, "owner-email")
1571
+ })
1572
+ );
1573
+ return;
1574
+ }
1575
+ if (command === "suspend") {
1576
+ writeJson(out, await client.partner.suspendOrganization(id));
1577
+ return;
1578
+ }
1579
+ if (command === "resume") {
1580
+ writeJson(out, await client.partner.resumeOrganization(id));
1581
+ return;
1582
+ }
1583
+ if (command === "offboard") {
1584
+ writeJson(out, await client.partner.offboardOrganization(id));
1585
+ return;
1586
+ }
1587
+ throw new CliUsageError("Unknown partner organizations command.");
1588
+ }
1431
1589
  function writeError(err, errOut) {
1432
1590
  if (err instanceof CliUsageError) {
1433
1591
  errOut.write(`Error: ${err.message}
@@ -1493,6 +1651,7 @@ async function runCli(argv, options = {}) {
1493
1651
  else if (command === "audiences") await runAudiences(client, parsed, out);
1494
1652
  else if (command === "calendar") await runCalendar(client, parsed, out);
1495
1653
  else if (command === "booking-pages") await runBookingPages(client, parsed, out);
1654
+ else if (command === "partner") await runPartner(client, parsed, out);
1496
1655
  else throw new CliUsageError(`Unknown command: ${command}.`);
1497
1656
  return 0;
1498
1657
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shipmail-cli",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "Official CLI for the ShipMail API",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,7 +41,7 @@
41
41
  "access": "public"
42
42
  },
43
43
  "dependencies": {
44
- "shipmail": "0.1.32"
44
+ "shipmail": "0.2.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "tsup": "^8.4.0",