groundmail-cli 0.33.1 → 0.35.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 +1 -0
  2. package/dist/cli.js +105 -9
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -75,6 +75,7 @@ groundmail domains list
75
75
  groundmail domains verify example.com
76
76
  groundmail webhooks list
77
77
  groundmail emails list
78
+ groundmail brand set --logo-url https://cdn.acme.com/logo.png --color '#2f6feb' --company "Acme Inc" # logo, color and footer every template follows
78
79
  groundmail senders list # saved senders; send with --sender billing instead of --from
79
80
  SENDGRID_API_KEY=… groundmail migrate sendgrid --dry-run # import from SendGrid, Postmark, Mailgun, Resend or Amazon SES (beta)
80
81
  ```
package/dist/cli.js CHANGED
@@ -2055,7 +2055,7 @@ function trimUrl(value) {
2055
2055
  }
2056
2056
 
2057
2057
  // package.json
2058
- var version = "0.33.1";
2058
+ var version = "0.35.0";
2059
2059
 
2060
2060
  // src/user-agent.ts
2061
2061
  function buildUserAgent(own) {
@@ -3283,8 +3283,8 @@ function extensionOf(filePath) {
3283
3283
  return match ? match[0].toLowerCase() : "";
3284
3284
  }
3285
3285
  async function defaultReadFile(filePath) {
3286
- const { readFile: readFile3 } = await import("fs/promises");
3287
- return readFile3(filePath, "utf8");
3286
+ const { readFile: readFile4 } = await import("fs/promises");
3287
+ return readFile4(filePath, "utf8");
3288
3288
  }
3289
3289
  async function defaultImportModule(filePath) {
3290
3290
  const { pathToFileURL } = await import("url");
@@ -3335,14 +3335,14 @@ async function toElement(mod, props) {
3335
3335
  return exported;
3336
3336
  }
3337
3337
  async function loadTemplateSource(filePath, options = {}, deps = {}) {
3338
- const readFile3 = deps.readFile ?? defaultReadFile;
3338
+ const readFile4 = deps.readFile ?? defaultReadFile;
3339
3339
  const importModule = deps.importModule ?? defaultImportModule;
3340
3340
  const extension = extensionOf(filePath);
3341
3341
  if (HTML_EXTENSIONS.includes(extension)) {
3342
- return { html: await readFile3(filePath) };
3342
+ return { html: await readFile4(filePath) };
3343
3343
  }
3344
3344
  if (TEXT_EXTENSIONS.includes(extension)) {
3345
- return { text: await readFile3(filePath) };
3345
+ return { text: await readFile4(filePath) };
3346
3346
  }
3347
3347
  if (!MODULE_EXTENSIONS.includes(extension)) {
3348
3348
  throw new Error(
@@ -4064,6 +4064,95 @@ async function runDeleteCommand6(args, env = process.env) {
4064
4064
  return withClient2(args, env, (client2) => client2.senders.remove(sender));
4065
4065
  }
4066
4066
 
4067
+ // src/commands/brand/commands.ts
4068
+ import { readFile as readFile3 } from "fs/promises";
4069
+ async function createBrandClient(apiKey, baseUrl) {
4070
+ const { Groundmail } = await import("groundmail");
4071
+ return new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
4072
+ }
4073
+ var HEX = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
4074
+ function readableText(hex) {
4075
+ const full = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
4076
+ const lin = (i) => {
4077
+ const c = parseInt(full.slice(i, i + 2), 16) / 255;
4078
+ return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
4079
+ };
4080
+ const luminance = 0.2126 * lin(1) + 0.7152 * lin(3) + 0.0722 * lin(5);
4081
+ return 1.05 / (luminance + 0.05) >= (luminance + 0.05) / (0.0103 + 0.05) ? "#ffffff" : "#1c1917";
4082
+ }
4083
+ async function withClient3(args, env, run) {
4084
+ const options = parseSendersCommonArgs(args);
4085
+ const apiKey = resolveApiKey10(options, env);
4086
+ if (!apiKey) return { exitCode: 1, error: MISSING_KEY2 };
4087
+ try {
4088
+ const client2 = await createBrandClient(apiKey, options.baseUrl);
4089
+ console.log(JSON.stringify(await run(client2)));
4090
+ return { exitCode: 0 };
4091
+ } catch (error) {
4092
+ return failure2(error);
4093
+ }
4094
+ }
4095
+ async function runGetCommand9(args, env = process.env) {
4096
+ return withClient3(args, env, (client2) => client2.brand.get());
4097
+ }
4098
+ function clearable(value) {
4099
+ if (value === void 0) return void 0;
4100
+ return value === "none" || value === "" ? null : value;
4101
+ }
4102
+ async function runSetCommand(args, env = process.env) {
4103
+ const logoUrl = clearable(readFlag22(args, "--logo-url"));
4104
+ const logoAlign = readFlag22(args, "--logo-align");
4105
+ const logoWidth = readFlag22(args, "--logo-width");
4106
+ const color = readFlag22(args, "--color");
4107
+ const settingsFile = readFlag22(args, "--settings-file");
4108
+ if (logoAlign !== void 0 && logoAlign !== "left" && logoAlign !== "center") {
4109
+ return { exitCode: 1, error: "--logo-align must be left or center" };
4110
+ }
4111
+ if ((logoAlign !== void 0 || logoWidth !== void 0) && !logoUrl) {
4112
+ return { exitCode: 1, error: "--logo-align and --logo-width go with --logo-url" };
4113
+ }
4114
+ if (logoWidth !== void 0 && !/^\d+$/.test(logoWidth)) return { exitCode: 1, error: "--logo-width must be a whole number of px" };
4115
+ if (color !== void 0 && !HEX.test(color)) return { exitCode: 1, error: "--color must be a hex colour such as #2f6feb" };
4116
+ let settingsFromFile;
4117
+ if (settingsFile) {
4118
+ try {
4119
+ settingsFromFile = JSON.parse(await readFile3(settingsFile, "utf8"));
4120
+ } catch {
4121
+ return { exitCode: 1, error: `Could not read JSON from ${settingsFile}` };
4122
+ }
4123
+ }
4124
+ const params = {};
4125
+ if (logoUrl !== void 0) {
4126
+ params.logo = logoUrl === null ? null : { url: logoUrl, align: logoAlign ?? "left", ...logoWidth ? { width: Number(logoWidth) } : {} };
4127
+ }
4128
+ const company = clearable(readFlag22(args, "--company"));
4129
+ const address = clearable(readFlag22(args, "--address"));
4130
+ const website = clearable(readFlag22(args, "--website"));
4131
+ if (company !== void 0) params.company_name = company;
4132
+ if (address !== void 0) params.address = address;
4133
+ if (website !== void 0) params.website = website;
4134
+ if (settingsFromFile) params.settings = settingsFromFile;
4135
+ if (!Object.keys(params).length && !color) {
4136
+ return {
4137
+ exitCode: 1,
4138
+ error: "Nothing to set. Usage: groundmail brand set [--logo-url \u2026] [--color #2f6feb] [--company \u2026] [--address \u2026] [--website \u2026] [--settings-file brand.json]"
4139
+ };
4140
+ }
4141
+ return withClient3(args, env, async (client2) => {
4142
+ if (color) {
4143
+ const base = params.settings ?? (await client2.brand.get()).brand.settings ?? {};
4144
+ const theme = base.theme ?? {};
4145
+ params.accent_color = color;
4146
+ params.settings = {
4147
+ ...base,
4148
+ linkColor: color,
4149
+ theme: { ...theme, buttonBackground: color, buttonTextColor: readableText(color) }
4150
+ };
4151
+ }
4152
+ return client2.brand.update(params);
4153
+ });
4154
+ }
4155
+
4067
4156
  // src/commands/auth.ts
4068
4157
  async function runAuthCommand(subcommand, argv, deps = {}) {
4069
4158
  const env = deps.env ?? process.env;
@@ -4651,7 +4740,7 @@ var USAGE_LINES = [
4651
4740
  ` groundmail events send --name user.created --email user@example.com --data '{"name":"Ada"}'`,
4652
4741
  " groundmail automations list",
4653
4742
  " groundmail automations create --file ./welcome.json",
4654
- ` groundmail automations create --name Welcome --trigger user.created --steps '[{"type":"send_email","email":{...}}]'`,
4743
+ ` groundmail automations create --name Welcome --trigger user.created --steps '[{"type":"send_email","email":{"from":"you@yourdomain.com","to":"{{event.email}}","template":{"id":"welcome","variables":{"name":"{{event.data.name}}"}}}}]' each email step sends a published template`,
4655
4744
  " groundmail automations create --file ./otp-sms.json",
4656
4745
  " groundmail automations get --id <automation-id>",
4657
4746
  " groundmail automations update --id <automation-id> --file ./welcome.json",
@@ -4682,7 +4771,7 @@ var USAGE_LINES = [
4682
4771
  " groundmail templates restore --id tpl_\u2026 --version 2",
4683
4772
  " groundmail templates test-send --id tpl_\u2026 --to you@domain.com [--from you@domain.com | --sender billing] neither: the template's sender, else your default sender",
4684
4773
  ' groundmail templates duplicate --id tpl_\u2026 [--name "Copy"]',
4685
- " groundmail templates delete --id tpl_\u2026",
4774
+ " groundmail templates delete --id tpl_\u2026 refused while an automation step sends it (the error names them)",
4686
4775
  " groundmail tenants list [--status active|paused] [--limit 25] [--cursor \u2026] your customers, each with its own domains, suppressions and limits",
4687
4776
  ` groundmail tenants create <external-id> [--name \u2026] [--default-from-domain \u2026] [--stream-policy all|transactional_only] [--daily-limit N] [--metadata '{"plan":"pro"}']`,
4688
4777
  " groundmail tenants get <tenant>",
@@ -4694,6 +4783,8 @@ var USAGE_LINES = [
4694
4783
  " groundmail tenants domains list <tenant>",
4695
4784
  " groundmail tenants domains add <tenant> example.com only this tenant can then send from it",
4696
4785
  " groundmail tenants domains remove <tenant> example.com gives the domain back to the account",
4786
+ " groundmail brand get your logo, colors and footer, which templates follow",
4787
+ ' groundmail brand set [--logo-url <https url>|none] [--logo-align left|center] [--logo-width 120] [--color #2f6feb] [--company "Acme Inc"|none] [--address "\u2026"|none] [--website acme.com|none] [--settings-file brand.json] --color: buttons, links and the line across the top',
4697
4788
  " groundmail senders list the names and addresses your email goes out as",
4698
4789
  ' groundmail senders create --name "Acme Billing" --address billing@mail.acme.com [--reply-to billing@acme.com] [--handle billing] [--default] address on a verified domain',
4699
4790
  " groundmail senders get <id|handle>",
@@ -4742,7 +4833,8 @@ var ALIASES = {
4742
4833
  suppressions: { create: "add", delete: "remove" },
4743
4834
  templates: { add: "create", remove: "delete" },
4744
4835
  tenants: { add: "create", remove: "delete" },
4745
- senders: { add: "create", remove: "delete" }
4836
+ senders: { add: "create", remove: "delete" },
4837
+ brand: { update: "set" }
4746
4838
  };
4747
4839
  function words(line) {
4748
4840
  return line.trim().split(/\s+/);
@@ -5080,6 +5172,10 @@ async function main() {
5080
5172
  if (subcommand === "stats") await finish(await runStatsCommand(rest));
5081
5173
  if (subcommand === "domains") await finish(await runDomainsCommand(rest));
5082
5174
  }
5175
+ if (command === "brand") {
5176
+ if (subcommand === "get") await finish(await runGetCommand9(rest));
5177
+ if (subcommand === "set" || subcommand === "update") await finish(await runSetCommand(rest));
5178
+ }
5083
5179
  if (command === "senders") {
5084
5180
  if (subcommand === "list") await finish(await runListCommand9(rest));
5085
5181
  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.33.1",
3
+ "version": "0.35.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.35.0"
41
+ "groundmail": "0.37.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "tsup": "^8.5.0",