groundmail-cli 0.32.3 → 0.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cli.js +203 -51
- 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 senders list # saved senders; send with --sender billing instead of --from
|
|
78
79
|
SENDGRID_API_KEY=… groundmail migrate sendgrid --dry-run # import from SendGrid, Postmark, Mailgun, Resend or Amazon SES (beta)
|
|
79
80
|
```
|
|
80
81
|
|
package/dist/cli.js
CHANGED
|
@@ -533,6 +533,7 @@ function parseSendCommandArgs(args) {
|
|
|
533
533
|
const category = readFlag(args, "--category");
|
|
534
534
|
return {
|
|
535
535
|
from: readFlag(args, "--from"),
|
|
536
|
+
sender: readFlag(args, "--sender"),
|
|
536
537
|
to: readFlag(args, "--to"),
|
|
537
538
|
subject: readFlag(args, "--subject"),
|
|
538
539
|
html: readFlag(args, "--html"),
|
|
@@ -564,7 +565,7 @@ async function runSendCommand(args, deps) {
|
|
|
564
565
|
return 1;
|
|
565
566
|
}
|
|
566
567
|
if (options.fromDefaulted) {
|
|
567
|
-
stderr(`No --from: sending from the sandbox sender ${SANDBOX_FROM}, which only sends to your account email. Pass --from to send from your own domain.`);
|
|
568
|
+
stderr(`No --from: sending from the sandbox sender ${SANDBOX_FROM}, which only sends to your account email. Pass --from, or --sender with a saved sender, to send from your own domain.`);
|
|
568
569
|
}
|
|
569
570
|
const startedAt = now();
|
|
570
571
|
const recipientDomain2 = options.to.split(",")[0]?.split("@")[1]?.replace(/>$/, "").trim();
|
|
@@ -572,7 +573,8 @@ async function runSendCommand(args, deps) {
|
|
|
572
573
|
let result;
|
|
573
574
|
try {
|
|
574
575
|
result = await deps.sendEmail({
|
|
575
|
-
from: options.from,
|
|
576
|
+
...options.from ? { from: options.from } : {},
|
|
577
|
+
...options.sender ? { sender: options.sender } : {},
|
|
576
578
|
to: options.to,
|
|
577
579
|
subject: options.subject,
|
|
578
580
|
html: options.html,
|
|
@@ -637,9 +639,11 @@ function parseSendCommandOptions(args, env) {
|
|
|
637
639
|
if (!parsed.html && !parsed.text) {
|
|
638
640
|
return { error: "Provide --html or --text" };
|
|
639
641
|
}
|
|
642
|
+
const sender = parsed.sender?.trim() || void 0;
|
|
640
643
|
return {
|
|
641
|
-
from: parsed.from || SANDBOX_FROM,
|
|
642
|
-
|
|
644
|
+
from: parsed.from || (sender ? void 0 : SANDBOX_FROM),
|
|
645
|
+
...sender ? { sender } : {},
|
|
646
|
+
fromDefaulted: !parsed.from && !sender,
|
|
643
647
|
to: parsed.to,
|
|
644
648
|
subject: parsed.subject,
|
|
645
649
|
html: parsed.html,
|
|
@@ -2051,7 +2055,7 @@ function trimUrl(value) {
|
|
|
2051
2055
|
}
|
|
2052
2056
|
|
|
2053
2057
|
// package.json
|
|
2054
|
-
var version = "0.
|
|
2058
|
+
var version = "0.33.1";
|
|
2055
2059
|
|
|
2056
2060
|
// src/user-agent.ts
|
|
2057
2061
|
function buildUserAgent(own) {
|
|
@@ -2135,15 +2139,15 @@ function parseArgs(argv, spec) {
|
|
|
2135
2139
|
const booleanFlags = new Set(spec.booleans ?? []);
|
|
2136
2140
|
const values = {};
|
|
2137
2141
|
const booleans = {};
|
|
2138
|
-
const
|
|
2142
|
+
const positionals3 = [];
|
|
2139
2143
|
for (let i = 0; i < argv.length; i++) {
|
|
2140
2144
|
const arg = argv[i];
|
|
2141
2145
|
if (arg === "--") {
|
|
2142
|
-
|
|
2146
|
+
positionals3.push(...argv.slice(i + 1));
|
|
2143
2147
|
break;
|
|
2144
2148
|
}
|
|
2145
2149
|
if (!arg.startsWith("--") || arg === "-") {
|
|
2146
|
-
|
|
2150
|
+
positionals3.push(arg);
|
|
2147
2151
|
continue;
|
|
2148
2152
|
}
|
|
2149
2153
|
const eq = arg.indexOf("=");
|
|
@@ -2169,7 +2173,7 @@ function parseArgs(argv, spec) {
|
|
|
2169
2173
|
const known = [...valueFlags, ...booleanFlags].sort().join(", ");
|
|
2170
2174
|
return { ok: false, error: `Unknown flag ${name}.${known ? ` This command takes: ${known}.` : ""}` };
|
|
2171
2175
|
}
|
|
2172
|
-
return { ok: true, args: { values, booleans, positionals:
|
|
2176
|
+
return { ok: true, args: { values, booleans, positionals: positionals3 } };
|
|
2173
2177
|
}
|
|
2174
2178
|
function wantsJson(args, stdout = process.stdout) {
|
|
2175
2179
|
if (args.booleans["--json"] !== void 0) return args.booleans["--json"];
|
|
@@ -2477,10 +2481,10 @@ async function resolveAuth(flags, env = process.env, config = loadConfig(env)) {
|
|
|
2477
2481
|
return fromSaved(current.workspace);
|
|
2478
2482
|
}
|
|
2479
2483
|
async function applySavedSession(argv, env = process.env, config) {
|
|
2480
|
-
const
|
|
2484
|
+
const hasFlag3 = (flag) => argv.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
|
|
2481
2485
|
let workspace = null;
|
|
2482
2486
|
let notice;
|
|
2483
|
-
if (!
|
|
2487
|
+
if (!hasFlag3("--api-key") && !envApiKey(env)) {
|
|
2484
2488
|
const current = currentSavedWorkspace(config ?? loadConfig(env), env);
|
|
2485
2489
|
if (current.ok) {
|
|
2486
2490
|
workspace = current.workspace;
|
|
@@ -2491,7 +2495,7 @@ async function applySavedSession(argv, env = process.env, config) {
|
|
|
2491
2495
|
notice = current.error;
|
|
2492
2496
|
}
|
|
2493
2497
|
}
|
|
2494
|
-
if (
|
|
2498
|
+
if (hasFlag3("--base-url")) return { argv, notice };
|
|
2495
2499
|
const url = explicitApiUrl(env) || trimUrl(workspace?.apiUrl) || trimUrl(env.GROUNDMAIL_DEFAULT_API_URL);
|
|
2496
2500
|
return { argv: url ? [...argv, "--base-url", url] : argv, notice };
|
|
2497
2501
|
}
|
|
@@ -3483,6 +3487,7 @@ async function runCreateCommand3(args, env = process.env) {
|
|
|
3483
3487
|
const alias = readFlag20(args, "--alias");
|
|
3484
3488
|
const category = readFlag20(args, "--category");
|
|
3485
3489
|
const folder = readFlag20(args, "--folder");
|
|
3490
|
+
const sender = readFlag20(args, "--sender");
|
|
3486
3491
|
const unsubscribeEnabled = args.includes("--unsubscribe-enabled");
|
|
3487
3492
|
if (!name) {
|
|
3488
3493
|
return { exitCode: 1, error: 'Missing name. Pass --name "Welcome"' };
|
|
@@ -3503,7 +3508,8 @@ async function runCreateCommand3(args, env = process.env) {
|
|
|
3503
3508
|
...alias ? { alias } : {},
|
|
3504
3509
|
...category ? { category } : {},
|
|
3505
3510
|
...unsubscribeEnabled ? { unsubscribe_enabled: true } : {},
|
|
3506
|
-
...folder?.trim() ? { folder } : {}
|
|
3511
|
+
...folder?.trim() ? { folder } : {},
|
|
3512
|
+
...sender?.trim() ? { sender_id: sender.trim() } : {}
|
|
3507
3513
|
});
|
|
3508
3514
|
console.log(JSON.stringify(result));
|
|
3509
3515
|
return { exitCode: 0 };
|
|
@@ -3524,7 +3530,8 @@ var POSITIONAL_FLAGS11 = [
|
|
|
3524
3530
|
"--html",
|
|
3525
3531
|
"--text",
|
|
3526
3532
|
"--variable",
|
|
3527
|
-
"--folder"
|
|
3533
|
+
"--folder",
|
|
3534
|
+
"--sender"
|
|
3528
3535
|
];
|
|
3529
3536
|
async function runUpdateCommand4(args, env = process.env) {
|
|
3530
3537
|
const options = parseTemplatesCommonArgs(args);
|
|
@@ -3544,12 +3551,13 @@ async function runUpdateCommand4(args, env = process.env) {
|
|
|
3544
3551
|
const variables = readFlags3(args, "--variable");
|
|
3545
3552
|
const category = readFlag20(args, "--category");
|
|
3546
3553
|
const folder = readFlag20(args, "--folder");
|
|
3554
|
+
const sender = readFlag20(args, "--sender");
|
|
3547
3555
|
const unsubscribeEnabled = args.includes("--unsubscribe-enabled");
|
|
3548
3556
|
const unsubscribeDisabled = args.includes("--no-unsubscribe-enabled");
|
|
3549
|
-
if (!name && !subject && !html && !text && !alias && variables.length === 0 && !category && folder === void 0 && !unsubscribeEnabled && !unsubscribeDisabled) {
|
|
3557
|
+
if (!name && !subject && !html && !text && !alias && variables.length === 0 && !category && folder === void 0 && sender === void 0 && !unsubscribeEnabled && !unsubscribeDisabled) {
|
|
3550
3558
|
return {
|
|
3551
3559
|
exitCode: 1,
|
|
3552
|
-
error: "Nothing to update. Pass --name, --subject, --html, --text, --set-alias, --category, --variable, --folder, or unsubscribe flags."
|
|
3560
|
+
error: "Nothing to update. Pass --name, --subject, --html, --text, --set-alias, --category, --variable, --folder, --sender, or unsubscribe flags."
|
|
3553
3561
|
};
|
|
3554
3562
|
}
|
|
3555
3563
|
try {
|
|
@@ -3564,7 +3572,8 @@ async function runUpdateCommand4(args, env = process.env) {
|
|
|
3564
3572
|
...category ? { category } : {},
|
|
3565
3573
|
...unsubscribeEnabled ? { unsubscribe_enabled: true } : {},
|
|
3566
3574
|
...unsubscribeDisabled ? { unsubscribe_enabled: false } : {},
|
|
3567
|
-
...folder !== void 0 ? { folder } : {}
|
|
3575
|
+
...folder !== void 0 ? { folder } : {},
|
|
3576
|
+
...sender !== void 0 ? { sender_id: sender === "none" || !sender.trim() ? null : sender.trim() } : {}
|
|
3568
3577
|
});
|
|
3569
3578
|
console.log(JSON.stringify(result));
|
|
3570
3579
|
return { exitCode: 0 };
|
|
@@ -3641,7 +3650,7 @@ async function runListStartersCommand(args, env = process.env) {
|
|
|
3641
3650
|
}
|
|
3642
3651
|
|
|
3643
3652
|
// src/commands/templates/versions.ts
|
|
3644
|
-
var POSITIONAL_FLAGS13 = ["--api-key", "--base-url", "--id", "--alias", "--version", "--from", "--to"];
|
|
3653
|
+
var POSITIONAL_FLAGS13 = ["--api-key", "--base-url", "--id", "--alias", "--version", "--from", "--sender", "--to"];
|
|
3645
3654
|
function readFirstPositional14(args) {
|
|
3646
3655
|
for (let i = 0; i < args.length; i += 1) {
|
|
3647
3656
|
const arg = args[i];
|
|
@@ -3702,16 +3711,21 @@ async function runTestSendCommand(args, env = process.env) {
|
|
|
3702
3711
|
}
|
|
3703
3712
|
const idOrAlias = readFlag20(args, "--id") || readFlag20(args, "--alias") || readFirstPositional14(args);
|
|
3704
3713
|
const from = readFlag20(args, "--from");
|
|
3714
|
+
const sender = readFlag20(args, "--sender");
|
|
3705
3715
|
const to = readFlag20(args, "--to");
|
|
3706
|
-
if (!idOrAlias || !
|
|
3716
|
+
if (!idOrAlias || !to) {
|
|
3707
3717
|
return {
|
|
3708
3718
|
exitCode: 1,
|
|
3709
|
-
error: "Usage: groundmail templates test-send --id <id|alias> --
|
|
3719
|
+
error: "Usage: groundmail templates test-send --id <id|alias> --to <email> [--from <email> | --sender <handle>] (neither: the template's sender, else your default sender)"
|
|
3710
3720
|
};
|
|
3711
3721
|
}
|
|
3712
3722
|
try {
|
|
3713
3723
|
const client2 = await createTemplatesClient(apiKey, options.baseUrl);
|
|
3714
|
-
console.log(
|
|
3724
|
+
console.log(
|
|
3725
|
+
JSON.stringify(
|
|
3726
|
+
await client2.templates.testSend(idOrAlias, { to, ...from ? { from } : {}, ...sender ? { sender } : {} })
|
|
3727
|
+
)
|
|
3728
|
+
);
|
|
3715
3729
|
return { exitCode: 0 };
|
|
3716
3730
|
} catch (error) {
|
|
3717
3731
|
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
@@ -3925,6 +3939,131 @@ async function runDomainsCommand(args, env = process.env) {
|
|
|
3925
3939
|
};
|
|
3926
3940
|
}
|
|
3927
3941
|
|
|
3942
|
+
// src/commands/senders/shared.ts
|
|
3943
|
+
var SENDER_VALUE_FLAGS = ["--api-key", "--base-url", "--id", "--name", "--address", "--reply-to", "--handle"];
|
|
3944
|
+
function readFlag22(args, flag) {
|
|
3945
|
+
const index = args.indexOf(flag);
|
|
3946
|
+
if (index === -1) return void 0;
|
|
3947
|
+
return args[index + 1];
|
|
3948
|
+
}
|
|
3949
|
+
function hasFlag2(args, flag) {
|
|
3950
|
+
return args.includes(flag);
|
|
3951
|
+
}
|
|
3952
|
+
function positionals2(args, valueFlags = SENDER_VALUE_FLAGS) {
|
|
3953
|
+
const out = [];
|
|
3954
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
3955
|
+
const arg = args[i];
|
|
3956
|
+
if (arg.startsWith("--")) {
|
|
3957
|
+
if (valueFlags.includes(arg)) i += 1;
|
|
3958
|
+
continue;
|
|
3959
|
+
}
|
|
3960
|
+
out.push(arg);
|
|
3961
|
+
}
|
|
3962
|
+
return out;
|
|
3963
|
+
}
|
|
3964
|
+
function parseSendersCommonArgs(args) {
|
|
3965
|
+
return {
|
|
3966
|
+
apiKey: readFlag22(args, "--api-key"),
|
|
3967
|
+
baseUrl: readFlag22(args, "--base-url")
|
|
3968
|
+
};
|
|
3969
|
+
}
|
|
3970
|
+
function resolveApiKey10(options, env) {
|
|
3971
|
+
return options.apiKey || env.GROUNDMAIL_API_KEY || null;
|
|
3972
|
+
}
|
|
3973
|
+
async function createSendersClient(apiKey, baseUrl) {
|
|
3974
|
+
const { Groundmail } = await import("groundmail");
|
|
3975
|
+
return new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
3976
|
+
}
|
|
3977
|
+
var MISSING_KEY2 = "Missing API key. Set GROUNDMAIL_API_KEY or pass --api-key.";
|
|
3978
|
+
function failure2(error) {
|
|
3979
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3980
|
+
const hint = error?.hint;
|
|
3981
|
+
return { exitCode: 1, error: typeof hint === "string" && hint ? `${message}
|
|
3982
|
+
${hint}` : message };
|
|
3983
|
+
}
|
|
3984
|
+
function senderRef(args) {
|
|
3985
|
+
return readFlag22(args, "--id") || positionals2(args)[0];
|
|
3986
|
+
}
|
|
3987
|
+
function parseSenderFields(args) {
|
|
3988
|
+
const name = readFlag22(args, "--name");
|
|
3989
|
+
const address = readFlag22(args, "--address");
|
|
3990
|
+
const replyTo = readFlag22(args, "--reply-to");
|
|
3991
|
+
const handle = readFlag22(args, "--handle");
|
|
3992
|
+
const makeDefault = hasFlag2(args, "--default");
|
|
3993
|
+
const notDefault = hasFlag2(args, "--no-default");
|
|
3994
|
+
if (makeDefault && notDefault) return { error: "Pass --default or --no-default, not both" };
|
|
3995
|
+
return {
|
|
3996
|
+
value: {
|
|
3997
|
+
...name !== void 0 ? { name } : {},
|
|
3998
|
+
...address !== void 0 ? { address } : {},
|
|
3999
|
+
...replyTo !== void 0 ? { reply_to: replyTo === "none" || replyTo === "" ? null : replyTo } : {},
|
|
4000
|
+
...handle !== void 0 ? { handle } : {},
|
|
4001
|
+
...makeDefault ? { is_default: true } : notDefault ? { is_default: false } : {}
|
|
4002
|
+
}
|
|
4003
|
+
};
|
|
4004
|
+
}
|
|
4005
|
+
|
|
4006
|
+
// src/commands/senders/commands.ts
|
|
4007
|
+
async function withClient2(args, env, run) {
|
|
4008
|
+
const options = parseSendersCommonArgs(args);
|
|
4009
|
+
const apiKey = resolveApiKey10(options, env);
|
|
4010
|
+
if (!apiKey) return { exitCode: 1, error: MISSING_KEY2 };
|
|
4011
|
+
try {
|
|
4012
|
+
const client2 = await createSendersClient(apiKey, options.baseUrl);
|
|
4013
|
+
console.log(JSON.stringify(await run(client2)));
|
|
4014
|
+
return { exitCode: 0 };
|
|
4015
|
+
} catch (error) {
|
|
4016
|
+
return failure2(error);
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
function missingSender(usage) {
|
|
4020
|
+
return { exitCode: 1, error: `Missing sender (id or handle). Usage: ${usage}` };
|
|
4021
|
+
}
|
|
4022
|
+
async function runListCommand9(args, env = process.env) {
|
|
4023
|
+
return withClient2(args, env, (client2) => client2.senders.list());
|
|
4024
|
+
}
|
|
4025
|
+
async function runCreateCommand5(args, env = process.env) {
|
|
4026
|
+
const fields = parseSenderFields(args);
|
|
4027
|
+
if ("error" in fields) return { exitCode: 1, error: fields.error };
|
|
4028
|
+
const { name, address } = fields.value;
|
|
4029
|
+
if (!name || !address) {
|
|
4030
|
+
return {
|
|
4031
|
+
exitCode: 1,
|
|
4032
|
+
error: 'Required: --name and --address. Usage: groundmail senders create --name "Acme Billing" --address billing@mail.acme.com [--reply-to \u2026] [--handle billing] [--default]'
|
|
4033
|
+
};
|
|
4034
|
+
}
|
|
4035
|
+
return withClient2(args, env, (client2) => client2.senders.create({ ...fields.value, name, address }));
|
|
4036
|
+
}
|
|
4037
|
+
async function runGetCommand8(args, env = process.env) {
|
|
4038
|
+
const sender = senderRef(args);
|
|
4039
|
+
if (!sender) return missingSender("groundmail senders get <id|handle>");
|
|
4040
|
+
return withClient2(args, env, (client2) => client2.senders.get(sender));
|
|
4041
|
+
}
|
|
4042
|
+
async function runUpdateCommand6(args, env = process.env) {
|
|
4043
|
+
const sender = senderRef(args);
|
|
4044
|
+
if (!sender) return missingSender("groundmail senders update <id|handle> [--name \u2026] [--address \u2026] [--reply-to \u2026|none] [--handle \u2026] [--default]");
|
|
4045
|
+
const fields = parseSenderFields(args);
|
|
4046
|
+
if ("error" in fields) return { exitCode: 1, error: fields.error };
|
|
4047
|
+
if (Object.keys(fields.value).length === 0) {
|
|
4048
|
+
return {
|
|
4049
|
+
exitCode: 1,
|
|
4050
|
+
error: "Nothing to update. Pass --name, --address, --reply-to, --handle, --default or --no-default."
|
|
4051
|
+
};
|
|
4052
|
+
}
|
|
4053
|
+
return withClient2(args, env, (client2) => client2.senders.update(sender, fields.value));
|
|
4054
|
+
}
|
|
4055
|
+
async function runDeleteCommand6(args, env = process.env) {
|
|
4056
|
+
const sender = senderRef(args);
|
|
4057
|
+
if (!sender) return missingSender("groundmail senders delete <id|handle> --yes");
|
|
4058
|
+
if (!hasFlag2(args, "--yes")) {
|
|
4059
|
+
return {
|
|
4060
|
+
exitCode: 1,
|
|
4061
|
+
error: `This deletes sender ${sender}. Sends that name it will fail. Run again with --yes to confirm.`
|
|
4062
|
+
};
|
|
4063
|
+
}
|
|
4064
|
+
return withClient2(args, env, (client2) => client2.senders.remove(sender));
|
|
4065
|
+
}
|
|
4066
|
+
|
|
3928
4067
|
// src/commands/auth.ts
|
|
3929
4068
|
async function runAuthCommand(subcommand, argv, deps = {}) {
|
|
3930
4069
|
const env = deps.env ?? process.env;
|
|
@@ -4088,7 +4227,7 @@ function sesParams(args, env) {
|
|
|
4088
4227
|
"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."
|
|
4089
4228
|
].join("\n");
|
|
4090
4229
|
}
|
|
4091
|
-
const region =
|
|
4230
|
+
const region = readFlag23(args, "--region") || env.AWS_REGION || env.AWS_DEFAULT_REGION;
|
|
4092
4231
|
if (!region) return "Set the SES region with --region (for example --region us-east-1) or AWS_REGION. One region per import.";
|
|
4093
4232
|
if (!AWS_REGION.test(region)) return `--region must be an AWS region such as us-east-1, not ${region}`;
|
|
4094
4233
|
return {
|
|
@@ -4100,7 +4239,7 @@ function sesParams(args, env) {
|
|
|
4100
4239
|
};
|
|
4101
4240
|
}
|
|
4102
4241
|
var VALUE_FLAGS3 = /* @__PURE__ */ new Set(["--api-key", "--base-url"]);
|
|
4103
|
-
function
|
|
4242
|
+
function readFlag23(args, flag) {
|
|
4104
4243
|
const index = args.indexOf(flag);
|
|
4105
4244
|
if (index === -1) return void 0;
|
|
4106
4245
|
return args[index + 1];
|
|
@@ -4175,10 +4314,10 @@ function formatMigrationSummary(m) {
|
|
|
4175
4314
|
return lines.join("\n");
|
|
4176
4315
|
}
|
|
4177
4316
|
async function client(args, env) {
|
|
4178
|
-
const apiKey =
|
|
4317
|
+
const apiKey = readFlag23(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
4179
4318
|
if (!apiKey) return null;
|
|
4180
4319
|
const { Groundmail } = await import("groundmail");
|
|
4181
|
-
const baseUrl =
|
|
4320
|
+
const baseUrl = readFlag23(args, "--base-url");
|
|
4182
4321
|
return new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
4183
4322
|
}
|
|
4184
4323
|
async function runStoredCommand(sub, args, env) {
|
|
@@ -4215,7 +4354,7 @@ async function runMigrateCommand(provider, args, env = process.env) {
|
|
|
4215
4354
|
};
|
|
4216
4355
|
}
|
|
4217
4356
|
const id = provider;
|
|
4218
|
-
const apiKey =
|
|
4357
|
+
const apiKey = readFlag23(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
4219
4358
|
if (!apiKey) {
|
|
4220
4359
|
return { exitCode: 1, error: "Not signed in. Run `groundmail login`, or set GROUNDMAIL_API_KEY." };
|
|
4221
4360
|
}
|
|
@@ -4225,7 +4364,7 @@ async function runMigrateCommand(provider, args, env = process.env) {
|
|
|
4225
4364
|
if (typeof ses === "string") return { exitCode: 1, error: ses };
|
|
4226
4365
|
params = ses;
|
|
4227
4366
|
} else {
|
|
4228
|
-
const providerKey =
|
|
4367
|
+
const providerKey = readFlag23(args, "--key") || env[KEY_ENV[id]];
|
|
4229
4368
|
if (!providerKey) {
|
|
4230
4369
|
const resend = id === "resend" ? " Use a key with Full access (Resend \u2192 API Keys); a Sending access key cannot read your account." : "";
|
|
4231
4370
|
return {
|
|
@@ -4235,15 +4374,15 @@ async function runMigrateCommand(provider, args, env = process.env) {
|
|
|
4235
4374
|
}
|
|
4236
4375
|
params = { provider: id, api_key: providerKey };
|
|
4237
4376
|
if (id === "postmark") {
|
|
4238
|
-
const accountToken =
|
|
4377
|
+
const accountToken = readFlag23(args, "--account-token") || env.POSTMARK_ACCOUNT_TOKEN;
|
|
4239
4378
|
if (accountToken) params.account_api_key = accountToken;
|
|
4240
4379
|
} else if (id === "sendgrid" || id === "mailgun") {
|
|
4241
|
-
const region =
|
|
4380
|
+
const region = readFlag23(args, "--region");
|
|
4242
4381
|
if (region === "us" || region === "eu") params.region = region;
|
|
4243
4382
|
else if (region) return { exitCode: 1, error: "--region must be us or eu" };
|
|
4244
4383
|
}
|
|
4245
4384
|
}
|
|
4246
|
-
const only =
|
|
4385
|
+
const only = readFlag23(args, "--only");
|
|
4247
4386
|
if (only) {
|
|
4248
4387
|
const list = only.split(",").map((s) => s.trim()).filter(Boolean);
|
|
4249
4388
|
const bad = list.filter((r) => !RESOURCES.includes(r));
|
|
@@ -4255,7 +4394,7 @@ async function runMigrateCommand(provider, args, env = process.env) {
|
|
|
4255
4394
|
if (args.includes("--dry-run")) params.dry_run = true;
|
|
4256
4395
|
try {
|
|
4257
4396
|
const { Groundmail } = await import("groundmail");
|
|
4258
|
-
const baseUrl =
|
|
4397
|
+
const baseUrl = readFlag23(args, "--base-url");
|
|
4259
4398
|
const client2 = new Groundmail(apiKey, baseUrl ? { baseUrl } : void 0);
|
|
4260
4399
|
console.error(`${params.dry_run ? "Previewing" : "Importing"} from ${LABELS[id]}${params.provider === "ses" ? ` (${params.aws_region})` : ""} (Beta)\u2026`);
|
|
4261
4400
|
const report = await client2.migrations.run(params, {
|
|
@@ -4299,7 +4438,7 @@ async function runValidateCommand(args, env = process.env) {
|
|
|
4299
4438
|
|
|
4300
4439
|
// src/commands/dmarc/dmarc.ts
|
|
4301
4440
|
var DMARC_APPLY_PROVIDERS = ["cloudflare", "vercel", "godaddy", "netlify", "digitalocean", "dnsimple"];
|
|
4302
|
-
function
|
|
4441
|
+
function readFlag24(args, flag) {
|
|
4303
4442
|
const index = args.indexOf(flag);
|
|
4304
4443
|
if (index === -1) return void 0;
|
|
4305
4444
|
return args[index + 1];
|
|
@@ -4356,23 +4495,23 @@ function formatDmarcStatus(dmarc) {
|
|
|
4356
4495
|
return lines.join("\n");
|
|
4357
4496
|
}
|
|
4358
4497
|
async function runDmarcCommand(subcommand, args, env = process.env) {
|
|
4359
|
-
const apiKey =
|
|
4498
|
+
const apiKey = readFlag24(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
4360
4499
|
if (!apiKey) return { exitCode: 1, error: "Missing API key. Run groundmail login, set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
4361
|
-
const domain =
|
|
4500
|
+
const domain = readFlag24(args, "--domain") || readFirstPositional15(args);
|
|
4362
4501
|
if (!subcommand || !["status", "enable", "disable", "digest"].includes(subcommand)) {
|
|
4363
4502
|
return { exitCode: 1, error: "Usage: groundmail dmarc status|enable|disable|digest <domain>" };
|
|
4364
4503
|
}
|
|
4365
4504
|
if (!domain) return { exitCode: 1, error: `Missing domain. Usage: groundmail dmarc ${subcommand} example.com` };
|
|
4366
4505
|
const json = args.includes("--json");
|
|
4367
4506
|
try {
|
|
4368
|
-
const client2 = await createDmarcClient(apiKey,
|
|
4507
|
+
const client2 = await createDmarcClient(apiKey, readFlag24(args, "--base-url"));
|
|
4369
4508
|
if (subcommand === "status") {
|
|
4370
4509
|
const result2 = await client2.dmarc.get(domain);
|
|
4371
4510
|
console.log(json ? JSON.stringify(result2) : formatDmarcStatus(result2.dmarc));
|
|
4372
4511
|
return { exitCode: 0 };
|
|
4373
4512
|
}
|
|
4374
4513
|
if (subcommand === "enable") {
|
|
4375
|
-
const apply =
|
|
4514
|
+
const apply = readFlag24(args, "--apply");
|
|
4376
4515
|
if (apply && !DMARC_APPLY_PROVIDERS.includes(apply)) {
|
|
4377
4516
|
return { exitCode: 1, error: `--apply must be one of: ${DMARC_APPLY_PROVIDERS.join(", ")}` };
|
|
4378
4517
|
}
|
|
@@ -4402,7 +4541,7 @@ Updated the DMARC record with ${result2.applied.provider}.`);
|
|
|
4402
4541
|
}
|
|
4403
4542
|
|
|
4404
4543
|
// src/commands/test-inbox/test-inbox.ts
|
|
4405
|
-
function
|
|
4544
|
+
function readFlag25(args, flag) {
|
|
4406
4545
|
const index = args.indexOf(flag);
|
|
4407
4546
|
if (index === -1) return void 0;
|
|
4408
4547
|
return args[index + 1];
|
|
@@ -4430,20 +4569,20 @@ function formatTestEmail(email) {
|
|
|
4430
4569
|
var USAGE = "Usage: groundmail test-inbox address [--prefix signup] | wait --to <address> [--timeout 30] [--since <iso>] [--json] | list [--to <address>] [--json]";
|
|
4431
4570
|
async function runTestInboxCommand(subcommand, args, env = process.env) {
|
|
4432
4571
|
if (!subcommand || !["address", "wait", "list"].includes(subcommand)) return { exitCode: 1, error: USAGE };
|
|
4433
|
-
const apiKey =
|
|
4572
|
+
const apiKey = readFlag25(args, "--api-key") || env.GROUNDMAIL_API_KEY;
|
|
4434
4573
|
if (!apiKey) return { exitCode: 1, error: "Missing API key. Run groundmail login, set GROUNDMAIL_API_KEY or pass --api-key." };
|
|
4435
4574
|
const json = args.includes("--json");
|
|
4436
4575
|
try {
|
|
4437
|
-
const client2 = await createTestInboxClient(apiKey,
|
|
4576
|
+
const client2 = await createTestInboxClient(apiKey, readFlag25(args, "--base-url"));
|
|
4438
4577
|
if (subcommand === "address") {
|
|
4439
|
-
console.log(await client2.testInbox.address({ prefix:
|
|
4578
|
+
console.log(await client2.testInbox.address({ prefix: readFlag25(args, "--prefix") }));
|
|
4440
4579
|
return { exitCode: 0 };
|
|
4441
4580
|
}
|
|
4442
4581
|
if (subcommand === "list") {
|
|
4443
4582
|
const result = await client2.testInbox.list({
|
|
4444
|
-
to:
|
|
4445
|
-
since:
|
|
4446
|
-
limit:
|
|
4583
|
+
to: readFlag25(args, "--to"),
|
|
4584
|
+
since: readFlag25(args, "--since"),
|
|
4585
|
+
limit: readFlag25(args, "--limit") ? Number(readFlag25(args, "--limit")) : void 0
|
|
4447
4586
|
});
|
|
4448
4587
|
if (json) console.log(JSON.stringify(result));
|
|
4449
4588
|
else if (!result.emails.length) console.log("No test emails yet.");
|
|
@@ -4454,13 +4593,13 @@ async function runTestInboxCommand(subcommand, args, env = process.env) {
|
|
|
4454
4593
|
}
|
|
4455
4594
|
return { exitCode: 0 };
|
|
4456
4595
|
}
|
|
4457
|
-
const to =
|
|
4596
|
+
const to = readFlag25(args, "--to");
|
|
4458
4597
|
if (!to) return { exitCode: 1, error: "Missing --to. Usage: groundmail test-inbox wait --to signup-k3m9x2pq@acme.test.groundmail.dev" };
|
|
4459
|
-
const timeoutRaw =
|
|
4598
|
+
const timeoutRaw = readFlag25(args, "--timeout");
|
|
4460
4599
|
const seconds = timeoutRaw === void 0 ? 30 : Number(timeoutRaw);
|
|
4461
4600
|
if (!Number.isFinite(seconds) || seconds < 0) return { exitCode: 1, error: "--timeout must be a number of seconds" };
|
|
4462
4601
|
try {
|
|
4463
|
-
const email = await client2.testInbox.waitFor({ to, timeout: seconds * 1e3, since:
|
|
4602
|
+
const email = await client2.testInbox.waitFor({ to, timeout: seconds * 1e3, since: readFlag25(args, "--since") });
|
|
4464
4603
|
console.log(json ? JSON.stringify({ email }) : formatTestEmail(email));
|
|
4465
4604
|
return { exitCode: 0 };
|
|
4466
4605
|
} catch (error) {
|
|
@@ -4501,7 +4640,7 @@ var USAGE_LINES = [
|
|
|
4501
4640
|
" groundmail test-inbox address [--prefix signup] a fresh address on your team's test domain (mail there is caught, never delivered)",
|
|
4502
4641
|
" groundmail test-inbox wait --to <address> [--timeout 30] [--since <iso>] [--json] wait for an email; prints its code and links (exit 2 on timeout)",
|
|
4503
4642
|
" groundmail test-inbox list [--to <address>] [--since <iso>] [--json]",
|
|
4504
|
-
|
|
4643
|
+
` groundmail emails send [--from you@domain.com | --sender billing] --to user@example.com --subject "Hello" --html "<p>Hi</p>" [--category transactional|product|newsletter] [--unsubscribe] [--tenant <tenant>] [--watch [--timeout 60] [--json]] --sender: a saved sender's handle; no --from or --sender: the sandbox sender (to your account email only)`,
|
|
4505
4644
|
' groundmail emails send --to you@example.com --subject "Hello" --text "Hi" --watch watch it land: the receiving server, its reply and the time',
|
|
4506
4645
|
" groundmail emails batch --file ./emails.json [--tenant <tenant>]",
|
|
4507
4646
|
" groundmail emails list [--limit 25] [--cursor \u2026] [--test | --live] [--tenant <tenant>] [--stream transactional|product|newsletter]",
|
|
@@ -4534,14 +4673,14 @@ var USAGE_LINES = [
|
|
|
4534
4673
|
" groundmail templates list [--status draft|published]",
|
|
4535
4674
|
" groundmail templates starters",
|
|
4536
4675
|
" groundmail templates from-starter password-reset",
|
|
4537
|
-
' groundmail templates create --name Welcome --subject "Hi" --html "<p>Hi</p>" [--category transactional|product] [--unsubscribe-enabled] [--folder Onboarding]',
|
|
4676
|
+
' groundmail templates create --name Welcome --subject "Hi" --html "<p>Hi</p>" [--category transactional|product] [--unsubscribe-enabled] [--folder Onboarding] [--sender billing]',
|
|
4538
4677
|
" groundmail templates get --id tpl_\u2026",
|
|
4539
|
-
" groundmail templates update --id tpl_\u2026 [--name \u2026] [--subject \u2026] [--html \u2026] [--text \u2026] [--set-alias \u2026] [--category transactional|product] [--unsubscribe-enabled|--no-unsubscribe-enabled] [--folder \u2026]",
|
|
4678
|
+
" groundmail templates update --id tpl_\u2026 [--name \u2026] [--subject \u2026] [--html \u2026] [--text \u2026] [--set-alias \u2026] [--category transactional|product] [--unsubscribe-enabled|--no-unsubscribe-enabled] [--folder \u2026] [--sender billing|none]",
|
|
4540
4679
|
" groundmail templates publish --id tpl_\u2026",
|
|
4541
4680
|
` groundmail templates push ./emails/welcome.tsx --alias welcome [--subject "Welcome"] [--props '{"name":"Ada"}'] [--publish]`,
|
|
4542
4681
|
" groundmail templates versions --id tpl_\u2026",
|
|
4543
4682
|
" groundmail templates restore --id tpl_\u2026 --version 2",
|
|
4544
|
-
" groundmail templates test-send --id tpl_\u2026 --
|
|
4683
|
+
" 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",
|
|
4545
4684
|
' groundmail templates duplicate --id tpl_\u2026 [--name "Copy"]',
|
|
4546
4685
|
" groundmail templates delete --id tpl_\u2026",
|
|
4547
4686
|
" groundmail tenants list [--status active|paused] [--limit 25] [--cursor \u2026] your customers, each with its own domains, suppressions and limits",
|
|
@@ -4555,6 +4694,11 @@ var USAGE_LINES = [
|
|
|
4555
4694
|
" groundmail tenants domains list <tenant>",
|
|
4556
4695
|
" groundmail tenants domains add <tenant> example.com only this tenant can then send from it",
|
|
4557
4696
|
" groundmail tenants domains remove <tenant> example.com gives the domain back to the account",
|
|
4697
|
+
" groundmail senders list the names and addresses your email goes out as",
|
|
4698
|
+
' 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
|
+
" groundmail senders get <id|handle>",
|
|
4700
|
+
" groundmail senders update <id|handle> [--name \u2026] [--address \u2026] [--reply-to \u2026|none] [--handle \u2026] [--default|--no-default]",
|
|
4701
|
+
" groundmail senders delete <id|handle> --yes refused while an automation or template uses it",
|
|
4558
4702
|
" groundmail keys list",
|
|
4559
4703
|
" groundmail keys create [--name Default] [--scopes emails:send,domains:read] [--allowed-domains example.com] [--test] [--tenant <tenant>] --test: a gm_test_ key, sends are simulated; --tenant: a key limited to one tenant",
|
|
4560
4704
|
" groundmail keys delete --id <api-key-id>",
|
|
@@ -4597,7 +4741,8 @@ var ALIASES = {
|
|
|
4597
4741
|
automations: { add: "create", remove: "delete" },
|
|
4598
4742
|
suppressions: { create: "add", delete: "remove" },
|
|
4599
4743
|
templates: { add: "create", remove: "delete" },
|
|
4600
|
-
tenants: { add: "create", remove: "delete" }
|
|
4744
|
+
tenants: { add: "create", remove: "delete" },
|
|
4745
|
+
senders: { add: "create", remove: "delete" }
|
|
4601
4746
|
};
|
|
4602
4747
|
function words(line) {
|
|
4603
4748
|
return line.trim().split(/\s+/);
|
|
@@ -4935,6 +5080,13 @@ async function main() {
|
|
|
4935
5080
|
if (subcommand === "stats") await finish(await runStatsCommand(rest));
|
|
4936
5081
|
if (subcommand === "domains") await finish(await runDomainsCommand(rest));
|
|
4937
5082
|
}
|
|
5083
|
+
if (command === "senders") {
|
|
5084
|
+
if (subcommand === "list") await finish(await runListCommand9(rest));
|
|
5085
|
+
if (subcommand === "create" || subcommand === "add") await finish(await runCreateCommand5(rest));
|
|
5086
|
+
if (subcommand === "get") await finish(await runGetCommand8(rest));
|
|
5087
|
+
if (subcommand === "update") await finish(await runUpdateCommand6(rest));
|
|
5088
|
+
if (subcommand === "delete" || subcommand === "remove") await finish(await runDeleteCommand6(rest));
|
|
5089
|
+
}
|
|
4938
5090
|
if (command === "keys") {
|
|
4939
5091
|
if (subcommand === "list") {
|
|
4940
5092
|
const result = await runListKeysCommandWithClient(rest);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groundmail-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.1",
|
|
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.
|
|
41
|
+
"groundmail": "0.35.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"tsup": "^8.5.0",
|