groundmail-cli 0.35.1 → 0.37.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 +4 -0
  2. package/dist/cli.js +140 -30
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -74,7 +74,11 @@ groundmail workspaces use <name>
74
74
  groundmail domains list
75
75
  groundmail domains verify example.com
76
76
  groundmail webhooks list
77
+ groundmail webhooks deliveries list --webhook <webhook-id> --status failed
78
+ groundmail webhooks deliveries resend --webhook <webhook-id> --id <delivery-id>
77
79
  groundmail emails list
80
+ groundmail batches get batch_… # status counts for an emails batch
81
+ groundmail batches cancel batch_… # cancel the batch's emails that are still scheduled
78
82
  groundmail brand set --logo-url https://cdn.acme.com/logo.png --color '#2f6feb' --company "Acme Inc" # logo, color and footer every template follows
79
83
  groundmail senders list # saved senders; send with --sender billing instead of --from
80
84
  SENDGRID_API_KEY=… groundmail migrate sendgrid --dry-run # import from SendGrid, Postmark, Mailgun, Resend or Amazon SES (beta)
package/dist/cli.js CHANGED
@@ -2001,6 +2001,57 @@ function parseEnabledFlag(value) {
2001
2001
  return { error: "--enabled must be true or false" };
2002
2002
  }
2003
2003
 
2004
+ // src/commands/webhooks/deliveries.ts
2005
+ var VALUE_FILTERS = {
2006
+ "--status": "status",
2007
+ "--event": "event",
2008
+ "--email-id": "email_id",
2009
+ "--q": "q",
2010
+ "--cursor": "cursor"
2011
+ };
2012
+ function listParams(args) {
2013
+ const params = {};
2014
+ for (const [flag, key] of Object.entries(VALUE_FILTERS)) {
2015
+ const value = readFlag13(args, flag);
2016
+ if (value) params[key] = value;
2017
+ }
2018
+ const limit = readFlag13(args, "--limit");
2019
+ if (limit) {
2020
+ const parsed = Number(limit);
2021
+ if (!Number.isInteger(parsed) || parsed < 1) return { error: "--limit must be a positive whole number." };
2022
+ params.limit = parsed;
2023
+ }
2024
+ const test = readFlag13(args, "--test");
2025
+ if (test) {
2026
+ if (test !== "true" && test !== "false") return { error: "--test must be true or false." };
2027
+ params.test = test === "true";
2028
+ }
2029
+ return params;
2030
+ }
2031
+ async function runDeliveriesCommand(args, env = process.env) {
2032
+ const [action, ...rest] = args;
2033
+ if (action !== "list" && action !== "get" && action !== "resend") {
2034
+ return { exitCode: 1, error: "Usage: groundmail webhooks deliveries list|get|resend --webhook <webhook-id> [--id <delivery-id>]" };
2035
+ }
2036
+ const options = parseWebhooksCommonArgs(rest);
2037
+ const apiKey = resolveApiKey6(options, env);
2038
+ if (!apiKey) return { exitCode: 1, error: "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key." };
2039
+ const webhookId = readFlag13(rest, "--webhook");
2040
+ if (!webhookId) return { exitCode: 1, error: "Missing webhook id. Pass --webhook <webhook-id>." };
2041
+ const deliveryId = readFlag13(rest, "--id");
2042
+ if (action !== "list" && !deliveryId) return { exitCode: 1, error: "Missing delivery id. Pass --id <delivery-id>." };
2043
+ const params = action === "list" ? listParams(rest) : null;
2044
+ if (params && "error" in params) return { exitCode: 1, error: params.error };
2045
+ try {
2046
+ const { webhooks } = await createWebhooksClient(apiKey, options.baseUrl);
2047
+ const result = action === "list" ? await webhooks.deliveries.list(webhookId, params ?? {}) : action === "get" ? await webhooks.deliveries.get(webhookId, deliveryId) : await webhooks.deliveries.resend(webhookId, deliveryId);
2048
+ console.log(JSON.stringify(result));
2049
+ return { exitCode: 0 };
2050
+ } catch (error) {
2051
+ return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
2052
+ }
2053
+ }
2054
+
2004
2055
  // src/commands/keys/list.ts
2005
2056
  function readFlag16(args, flag) {
2006
2057
  const index = args.indexOf(flag);
@@ -2055,7 +2106,7 @@ function trimUrl(value) {
2055
2106
  }
2056
2107
 
2057
2108
  // package.json
2058
- var version = "0.35.1";
2109
+ var version = "0.37.0";
2059
2110
 
2060
2111
  // src/user-agent.ts
2061
2112
  function buildUserAgent(own) {
@@ -4159,6 +4210,51 @@ async function runSetCommand(args, env = process.env) {
4159
4210
  });
4160
4211
  }
4161
4212
 
4213
+ // src/commands/batches/commands.ts
4214
+ var VALUE_FLAGS3 = ["--api-key", "--base-url", "--id"];
4215
+ var MISSING_KEY3 = "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key.";
4216
+ function readFlag23(args, flag) {
4217
+ const index = args.indexOf(flag);
4218
+ if (index === -1) return void 0;
4219
+ return args[index + 1];
4220
+ }
4221
+ function batchRef(args) {
4222
+ const id = readFlag23(args, "--id");
4223
+ if (id) return id;
4224
+ for (let i = 0; i < args.length; i += 1) {
4225
+ const arg = args[i];
4226
+ if (arg.startsWith("--")) {
4227
+ if (VALUE_FLAGS3.includes(arg)) i += 1;
4228
+ continue;
4229
+ }
4230
+ return arg;
4231
+ }
4232
+ return void 0;
4233
+ }
4234
+ async function createBatchesClient(apiKey, baseUrl) {
4235
+ const { Groundmail } = await import("groundmail");
4236
+ return new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
4237
+ }
4238
+ async function withBatch(args, env, usage, run) {
4239
+ const batchId = batchRef(args);
4240
+ if (!batchId) return { exitCode: 1, error: `Missing batch id (batch_\u2026). Usage: ${usage}` };
4241
+ const apiKey = readFlag23(args, "--api-key") || env.GROUNDMAIL_API_KEY;
4242
+ if (!apiKey) return { exitCode: 1, error: MISSING_KEY3 };
4243
+ try {
4244
+ const client2 = await createBatchesClient(apiKey, readFlag23(args, "--base-url"));
4245
+ console.log(JSON.stringify(await run(client2, batchId)));
4246
+ return { exitCode: 0 };
4247
+ } catch (error) {
4248
+ return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
4249
+ }
4250
+ }
4251
+ async function runGetCommand10(args, env = process.env) {
4252
+ return withBatch(args, env, "groundmail batches get <batch_id>", (client2, id) => client2.batches.get(id));
4253
+ }
4254
+ async function runCancelCommand2(args, env = process.env) {
4255
+ return withBatch(args, env, "groundmail batches cancel <batch_id>", (client2, id) => client2.batches.cancel(id));
4256
+ }
4257
+
4162
4258
  // src/commands/auth.ts
4163
4259
  async function runAuthCommand(subcommand, argv, deps = {}) {
4164
4260
  const env = deps.env ?? process.env;
@@ -4322,7 +4418,7 @@ function sesParams(args, env) {
4322
4418
  "Use a key with the read-only import policy (https://docs.groundmail.dev/migrations#amazon-ses-iam-policy). It is used for this import only and never stored."
4323
4419
  ].join("\n");
4324
4420
  }
4325
- const region = readFlag23(args, "--region") || env.AWS_REGION || env.AWS_DEFAULT_REGION;
4421
+ const region = readFlag24(args, "--region") || env.AWS_REGION || env.AWS_DEFAULT_REGION;
4326
4422
  if (!region) return "Set the SES region with --region (for example --region us-east-1) or AWS_REGION. One region per import.";
4327
4423
  if (!AWS_REGION.test(region)) return `--region must be an AWS region such as us-east-1, not ${region}`;
4328
4424
  return {
@@ -4333,8 +4429,8 @@ function sesParams(args, env) {
4333
4429
  aws_region: region
4334
4430
  };
4335
4431
  }
4336
- var VALUE_FLAGS3 = /* @__PURE__ */ new Set(["--api-key", "--base-url"]);
4337
- function readFlag23(args, flag) {
4432
+ var VALUE_FLAGS4 = /* @__PURE__ */ new Set(["--api-key", "--base-url"]);
4433
+ function readFlag24(args, flag) {
4338
4434
  const index = args.indexOf(flag);
4339
4435
  if (index === -1) return void 0;
4340
4436
  return args[index + 1];
@@ -4409,10 +4505,10 @@ function formatMigrationSummary(m) {
4409
4505
  return lines.join("\n");
4410
4506
  }
4411
4507
  async function client(args, env) {
4412
- const apiKey = readFlag23(args, "--api-key") || env.GROUNDMAIL_API_KEY;
4508
+ const apiKey = readFlag24(args, "--api-key") || env.GROUNDMAIL_API_KEY;
4413
4509
  if (!apiKey) return null;
4414
4510
  const { Groundmail } = await import("groundmail");
4415
- const baseUrl = readFlag23(args, "--base-url");
4511
+ const baseUrl = readFlag24(args, "--base-url");
4416
4512
  return new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
4417
4513
  }
4418
4514
  async function runStoredCommand(sub, args, env) {
@@ -4426,7 +4522,7 @@ async function runStoredCommand(sub, args, env) {
4426
4522
  if (!data.length) console.error("No imports yet. Previews are not stored.");
4427
4523
  return { exitCode: 0 };
4428
4524
  }
4429
- const id = args.find((arg, i) => !arg.startsWith("--") && !VALUE_FLAGS3.has(args[i - 1] ?? ""));
4525
+ const id = args.find((arg, i) => !arg.startsWith("--") && !VALUE_FLAGS4.has(args[i - 1] ?? ""));
4430
4526
  if (!id) return { exitCode: 1, error: "Usage: groundmail migrate report <import id> [--csv]" };
4431
4527
  if (args.includes("--csv")) {
4432
4528
  process.stdout.write(await gm.migrations.reportCsv(id));
@@ -4449,7 +4545,7 @@ async function runMigrateCommand(provider, args, env = process.env) {
4449
4545
  };
4450
4546
  }
4451
4547
  const id = provider;
4452
- const apiKey = readFlag23(args, "--api-key") || env.GROUNDMAIL_API_KEY;
4548
+ const apiKey = readFlag24(args, "--api-key") || env.GROUNDMAIL_API_KEY;
4453
4549
  if (!apiKey) {
4454
4550
  return { exitCode: 1, error: "Not signed in. Run `groundmail login`, or set GROUNDMAIL_API_KEY." };
4455
4551
  }
@@ -4459,7 +4555,7 @@ async function runMigrateCommand(provider, args, env = process.env) {
4459
4555
  if (typeof ses === "string") return { exitCode: 1, error: ses };
4460
4556
  params = ses;
4461
4557
  } else {
4462
- const providerKey = readFlag23(args, "--key") || env[KEY_ENV[id]];
4558
+ const providerKey = readFlag24(args, "--key") || env[KEY_ENV[id]];
4463
4559
  if (!providerKey) {
4464
4560
  const resend = id === "resend" ? " Use a key with Full access (Resend \u2192 API Keys); a Sending access key cannot read your account." : "";
4465
4561
  return {
@@ -4469,15 +4565,15 @@ async function runMigrateCommand(provider, args, env = process.env) {
4469
4565
  }
4470
4566
  params = { provider: id, api_key: providerKey };
4471
4567
  if (id === "postmark") {
4472
- const accountToken = readFlag23(args, "--account-token") || env.POSTMARK_ACCOUNT_TOKEN;
4568
+ const accountToken = readFlag24(args, "--account-token") || env.POSTMARK_ACCOUNT_TOKEN;
4473
4569
  if (accountToken) params.account_api_key = accountToken;
4474
4570
  } else if (id === "sendgrid" || id === "mailgun") {
4475
- const region = readFlag23(args, "--region");
4571
+ const region = readFlag24(args, "--region");
4476
4572
  if (region === "us" || region === "eu") params.region = region;
4477
4573
  else if (region) return { exitCode: 1, error: "--region must be us or eu" };
4478
4574
  }
4479
4575
  }
4480
- const only = readFlag23(args, "--only");
4576
+ const only = readFlag24(args, "--only");
4481
4577
  if (only) {
4482
4578
  const list = only.split(",").map((s) => s.trim()).filter(Boolean);
4483
4579
  const bad = list.filter((r) => !RESOURCES.includes(r));
@@ -4489,7 +4585,7 @@ async function runMigrateCommand(provider, args, env = process.env) {
4489
4585
  if (args.includes("--dry-run")) params.dry_run = true;
4490
4586
  try {
4491
4587
  const { Groundmail } = await import("groundmail");
4492
- const baseUrl = readFlag23(args, "--base-url");
4588
+ const baseUrl = readFlag24(args, "--base-url");
4493
4589
  const client2 = new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
4494
4590
  console.error(`${params.dry_run ? "Previewing" : "Importing"} from ${LABELS[id]}${params.provider === "ses" ? ` (${params.aws_region})` : ""} (Beta)\u2026`);
4495
4591
  const report = await client2.migrations.run(params, {
@@ -4533,17 +4629,17 @@ async function runValidateCommand(args, env = process.env) {
4533
4629
 
4534
4630
  // src/commands/dmarc/dmarc.ts
4535
4631
  var DMARC_APPLY_PROVIDERS = ["cloudflare", "vercel", "godaddy", "netlify", "digitalocean", "dnsimple"];
4536
- function readFlag24(args, flag) {
4632
+ function readFlag25(args, flag) {
4537
4633
  const index = args.indexOf(flag);
4538
4634
  if (index === -1) return void 0;
4539
4635
  return args[index + 1];
4540
4636
  }
4541
- var VALUE_FLAGS4 = ["--api-key", "--base-url", "--domain", "--apply"];
4637
+ var VALUE_FLAGS5 = ["--api-key", "--base-url", "--domain", "--apply"];
4542
4638
  function readFirstPositional15(args) {
4543
4639
  for (let i = 0; i < args.length; i += 1) {
4544
4640
  const arg = args[i];
4545
4641
  if (arg.startsWith("--")) {
4546
- if (VALUE_FLAGS4.includes(arg)) i += 1;
4642
+ if (VALUE_FLAGS5.includes(arg)) i += 1;
4547
4643
  continue;
4548
4644
  }
4549
4645
  return arg;
@@ -4590,23 +4686,23 @@ function formatDmarcStatus(dmarc) {
4590
4686
  return lines.join("\n");
4591
4687
  }
4592
4688
  async function runDmarcCommand(subcommand, args, env = process.env) {
4593
- const apiKey = readFlag24(args, "--api-key") || env.GROUNDMAIL_API_KEY;
4689
+ const apiKey = readFlag25(args, "--api-key") || env.GROUNDMAIL_API_KEY;
4594
4690
  if (!apiKey) return { exitCode: 1, error: "Missing API key. Run groundmail login, set GROUNDMAIL_API_KEY or pass --api-key." };
4595
- const domain = readFlag24(args, "--domain") || readFirstPositional15(args);
4691
+ const domain = readFlag25(args, "--domain") || readFirstPositional15(args);
4596
4692
  if (!subcommand || !["status", "enable", "disable", "digest"].includes(subcommand)) {
4597
4693
  return { exitCode: 1, error: "Usage: groundmail dmarc status|enable|disable|digest <domain>" };
4598
4694
  }
4599
4695
  if (!domain) return { exitCode: 1, error: `Missing domain. Usage: groundmail dmarc ${subcommand} example.com` };
4600
4696
  const json = args.includes("--json");
4601
4697
  try {
4602
- const client2 = await createDmarcClient(apiKey, readFlag24(args, "--base-url"));
4698
+ const client2 = await createDmarcClient(apiKey, readFlag25(args, "--base-url"));
4603
4699
  if (subcommand === "status") {
4604
4700
  const result2 = await client2.dmarc.get(domain);
4605
4701
  console.log(json ? JSON.stringify(result2) : formatDmarcStatus(result2.dmarc));
4606
4702
  return { exitCode: 0 };
4607
4703
  }
4608
4704
  if (subcommand === "enable") {
4609
- const apply = readFlag24(args, "--apply");
4705
+ const apply = readFlag25(args, "--apply");
4610
4706
  if (apply && !DMARC_APPLY_PROVIDERS.includes(apply)) {
4611
4707
  return { exitCode: 1, error: `--apply must be one of: ${DMARC_APPLY_PROVIDERS.join(", ")}` };
4612
4708
  }
@@ -4636,7 +4732,7 @@ Updated the DMARC record with ${result2.applied.provider}.`);
4636
4732
  }
4637
4733
 
4638
4734
  // src/commands/test-inbox/test-inbox.ts
4639
- function readFlag25(args, flag) {
4735
+ function readFlag26(args, flag) {
4640
4736
  const index = args.indexOf(flag);
4641
4737
  if (index === -1) return void 0;
4642
4738
  return args[index + 1];
@@ -4664,20 +4760,20 @@ function formatTestEmail(email) {
4664
4760
  var USAGE = "Usage: groundmail test-inbox address [--prefix signup] | wait --to <address> [--timeout 30] [--since <iso>] [--json] | list [--to <address>] [--json]";
4665
4761
  async function runTestInboxCommand(subcommand, args, env = process.env) {
4666
4762
  if (!subcommand || !["address", "wait", "list"].includes(subcommand)) return { exitCode: 1, error: USAGE };
4667
- const apiKey = readFlag25(args, "--api-key") || env.GROUNDMAIL_API_KEY;
4763
+ const apiKey = readFlag26(args, "--api-key") || env.GROUNDMAIL_API_KEY;
4668
4764
  if (!apiKey) return { exitCode: 1, error: "Missing API key. Run groundmail login, set GROUNDMAIL_API_KEY or pass --api-key." };
4669
4765
  const json = args.includes("--json");
4670
4766
  try {
4671
- const client2 = await createTestInboxClient(apiKey, readFlag25(args, "--base-url"));
4767
+ const client2 = await createTestInboxClient(apiKey, readFlag26(args, "--base-url"));
4672
4768
  if (subcommand === "address") {
4673
- console.log(await client2.testInbox.address({ prefix: readFlag25(args, "--prefix") }));
4769
+ console.log(await client2.testInbox.address({ prefix: readFlag26(args, "--prefix") }));
4674
4770
  return { exitCode: 0 };
4675
4771
  }
4676
4772
  if (subcommand === "list") {
4677
4773
  const result = await client2.testInbox.list({
4678
- to: readFlag25(args, "--to"),
4679
- since: readFlag25(args, "--since"),
4680
- limit: readFlag25(args, "--limit") ? Number(readFlag25(args, "--limit")) : void 0
4774
+ to: readFlag26(args, "--to"),
4775
+ since: readFlag26(args, "--since"),
4776
+ limit: readFlag26(args, "--limit") ? Number(readFlag26(args, "--limit")) : void 0
4681
4777
  });
4682
4778
  if (json) console.log(JSON.stringify(result));
4683
4779
  else if (!result.emails.length) console.log("No test emails yet.");
@@ -4688,13 +4784,13 @@ async function runTestInboxCommand(subcommand, args, env = process.env) {
4688
4784
  }
4689
4785
  return { exitCode: 0 };
4690
4786
  }
4691
- const to = readFlag25(args, "--to");
4787
+ const to = readFlag26(args, "--to");
4692
4788
  if (!to) return { exitCode: 1, error: "Missing --to. Usage: groundmail test-inbox wait --to signup-k3m9x2pq@acme.test.groundmail.dev" };
4693
- const timeoutRaw = readFlag25(args, "--timeout");
4789
+ const timeoutRaw = readFlag26(args, "--timeout");
4694
4790
  const seconds = timeoutRaw === void 0 ? 30 : Number(timeoutRaw);
4695
4791
  if (!Number.isFinite(seconds) || seconds < 0) return { exitCode: 1, error: "--timeout must be a number of seconds" };
4696
4792
  try {
4697
- const email = await client2.testInbox.waitFor({ to, timeout: seconds * 1e3, since: readFlag25(args, "--since") });
4793
+ const email = await client2.testInbox.waitFor({ to, timeout: seconds * 1e3, since: readFlag26(args, "--since") });
4698
4794
  console.log(json ? JSON.stringify({ email }) : formatTestEmail(email));
4699
4795
  return { exitCode: 0 };
4700
4796
  } catch (error) {
@@ -4742,6 +4838,8 @@ var USAGE_LINES = [
4742
4838
  " groundmail emails insights [--window 7d|30d]",
4743
4839
  " groundmail emails get --id msg_\u2026 [--watch [--timeout 60] [--json]] --watch: follow an email you already sent until it lands",
4744
4840
  " groundmail emails cancel --id msg_\u2026",
4841
+ " groundmail batches get <batch_id> an emails batch's status counts (the batch_id `emails batch` returned)",
4842
+ " groundmail batches cancel <batch_id> cancel the batch's emails that are still scheduled; safe to repeat",
4745
4843
  " groundmail emails resend --id msg_\u2026",
4746
4844
  ` groundmail events send --name user.created --email user@example.com --data '{"name":"Ada"}'`,
4747
4845
  " groundmail automations list",
@@ -4759,6 +4857,9 @@ var USAGE_LINES = [
4759
4857
  " groundmail webhooks get --id wh_\u2026",
4760
4858
  " groundmail webhooks update --id wh_\u2026 [--url \u2026] [--event email.sent] [--enabled true|false] [--tenants a,b|all]",
4761
4859
  " groundmail webhooks delete --id <webhook-id>",
4860
+ " groundmail webhooks deliveries list --webhook <webhook-id> [--status failed] [--event email.delivered] [--email-id msg_\u2026] [--q \u2026] [--test true|false] [--limit 25] [--cursor \u2026]",
4861
+ " groundmail webhooks deliveries get --webhook <webhook-id> --id <delivery-id>",
4862
+ " groundmail webhooks deliveries resend --webhook <webhook-id> --id <delivery-id>",
4762
4863
  " groundmail validate user@example.com Groundmail validation: verdict, reasons, typo suggestion (exit 2 when invalid)",
4763
4864
  " groundmail suppressions list [--tenant <tenant>]",
4764
4865
  " groundmail suppressions add --email user@example.com [--reason \u2026] [--tenant <tenant>]",
@@ -5036,6 +5137,11 @@ async function main() {
5036
5137
  }
5037
5138
  }
5038
5139
  if (command === "webhooks") {
5140
+ if (subcommand === "deliveries") {
5141
+ const result = await runDeliveriesCommand(rest);
5142
+ if (result.error) console.error(result.error);
5143
+ process.exit(result.exitCode);
5144
+ }
5039
5145
  if (subcommand === "list") {
5040
5146
  const result = await runListCommand5(rest);
5041
5147
  if (result.error) console.error(result.error);
@@ -5182,6 +5288,10 @@ async function main() {
5182
5288
  if (subcommand === "get") await finish(await runGetCommand9(rest));
5183
5289
  if (subcommand === "set" || subcommand === "update") await finish(await runSetCommand(rest));
5184
5290
  }
5291
+ if (command === "batches") {
5292
+ if (subcommand === "get") await finish(await runGetCommand10(rest));
5293
+ if (subcommand === "cancel") await finish(await runCancelCommand2(rest));
5294
+ }
5185
5295
  if (command === "senders") {
5186
5296
  if (subcommand === "list") await finish(await runListCommand9(rest));
5187
5297
  if (subcommand === "create" || subcommand === "add") await finish(await runCreateCommand5(rest));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groundmail-cli",
3
- "version": "0.35.1",
3
+ "version": "0.37.0",
4
4
  "description": "Groundmail CLI — send transactional email from the terminal",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -38,7 +38,7 @@
38
38
  "prepublishOnly": "npm run build"
39
39
  },
40
40
  "dependencies": {
41
- "groundmail": "0.37.0"
41
+ "groundmail": "0.39.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "tsup": "^8.5.0",