@dench.com/cli 3.6.6-staging.386 → 3.7.0-staging.387
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/agent-config.ts +17 -3
- package/app-sdk.d.ts +248 -10
- package/crm.ts +92 -24
- package/dench.ts +128 -19
- package/email-inbox.ts +169 -0
- package/email.ts +103 -21
- package/lib/api-schemas.ts +156 -14
- package/lib/app-sdk-catalog.generated.ts +9 -3
- package/lib/bundled-references.ts +17 -0
- package/lib/cli-args.ts +30 -3
- package/lib/command-args.ts +237 -0
- package/lib/command-help.ts +78 -0
- package/lib/command-registry.ts +75 -3
- package/lib/native-email-wait.ts +75 -0
- package/lib/public-error.ts +82 -0
- package/lib/references.generated.json +66 -0
- package/package.json +3 -1
- package/reference.ts +47 -0
package/email-inbox.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { ConvexHttpClient } from "convex/browser";
|
|
2
|
+
import { makeFunctionReference } from "convex/server";
|
|
3
|
+
import { assertNoUnknownFlags, getFlag, hasFlag } from "./lib/cli-args";
|
|
4
|
+
|
|
5
|
+
export const EMAIL_INBOX_HELP = `
|
|
6
|
+
dench email inbox search [--mailbox <id|email>] [--from <email>] [--to <email>] [--subject <text>] [--after <ISO-date|milliseconds>] [--before <ISO-date|milliseconds>] [--direction inbound|outbound] [--limit 1..50] [--cursor <cursor>] [--json]
|
|
7
|
+
dench email inbox thread <thread-handle> [--limit 1..50] [--cursor <cursor>] [--json]
|
|
8
|
+
dench email inbox message <message-handle> [--format text|html] [--max-bytes 1..100000] [--json]
|
|
9
|
+
|
|
10
|
+
Search reads synced headers only; it does not contact the provider or search message bodies.
|
|
11
|
+
--from/--to match exact addresses (to includes cc/bcc); --subject is a case-insensitive substring.
|
|
12
|
+
--after is inclusive, --before exclusive; date-only values mean midnight UTC. Older undated rows use createdAt.
|
|
13
|
+
isDone means the persisted scan ended, not that the provider's entire mailbox has been synced.
|
|
14
|
+
coverage.privacyLimited reports older rows omitted when complete record privacy cannot be established.
|
|
15
|
+
Keep the returned cursor through empty filtered pages. Thread/message handles come from these results.
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export type EmailInboxCliContext = {
|
|
19
|
+
convex: ConvexHttpClient;
|
|
20
|
+
args: string[];
|
|
21
|
+
jsonOutput: boolean;
|
|
22
|
+
sessionToken?: string;
|
|
23
|
+
};
|
|
24
|
+
function valueFlag(args: string[], name: string) {
|
|
25
|
+
const present = args.includes(name);
|
|
26
|
+
const value = getFlag(args, name);
|
|
27
|
+
if (present && (!value?.trim() || value.startsWith("--")))
|
|
28
|
+
throw new Error(`${name} requires a value`);
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
function integerFlag(args: string[], name: string, maximum: number) {
|
|
32
|
+
const raw = valueFlag(args, name);
|
|
33
|
+
if (raw === undefined) return undefined;
|
|
34
|
+
const value = Number(raw);
|
|
35
|
+
if (
|
|
36
|
+
!/^\d+$/.test(raw) ||
|
|
37
|
+
!Number.isInteger(value) ||
|
|
38
|
+
value < 1 ||
|
|
39
|
+
value > maximum
|
|
40
|
+
)
|
|
41
|
+
throw new Error(`${name} must be an integer from 1 to ${maximum}`);
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
function dateFlag(args: string[], name: string) {
|
|
45
|
+
const raw = valueFlag(args, name);
|
|
46
|
+
if (raw === undefined) return undefined;
|
|
47
|
+
if (/^-?\d+$/.test(raw)) {
|
|
48
|
+
const value = Number(raw);
|
|
49
|
+
if (Number.isSafeInteger(value)) return value;
|
|
50
|
+
}
|
|
51
|
+
const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(raw);
|
|
52
|
+
if (dateOnly || /^\d{4}-\d{2}-\d{2}T.*(?:Z|[+-]\d{2}:\d{2})$/.test(raw)) {
|
|
53
|
+
const value = Date.parse(dateOnly ? `${raw}T00:00:00.000Z` : raw);
|
|
54
|
+
const day = raw.slice(0, 10);
|
|
55
|
+
const calendar = Date.parse(`${day}T00:00:00.000Z`);
|
|
56
|
+
if (
|
|
57
|
+
Number.isFinite(value) &&
|
|
58
|
+
Number.isFinite(calendar) &&
|
|
59
|
+
new Date(calendar).toISOString().slice(0, 10) === day
|
|
60
|
+
)
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
throw new Error(
|
|
64
|
+
`${name} must be YYYY-MM-DD, an ISO timestamp with timezone, or Unix milliseconds`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
export async function runEmailInboxCommand(
|
|
68
|
+
ctx: EmailInboxCliContext,
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
const args = [...ctx.args];
|
|
71
|
+
if (
|
|
72
|
+
!args.length ||
|
|
73
|
+
args.includes("--help") ||
|
|
74
|
+
args.includes("-h") ||
|
|
75
|
+
args[0] === "help"
|
|
76
|
+
) {
|
|
77
|
+
console.log(EMAIL_INBOX_HELP.trim());
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const command = args.shift();
|
|
81
|
+
const jsonOutput = hasFlag(args, "--json") || ctx.jsonOutput;
|
|
82
|
+
const input: Record<string, unknown> = {};
|
|
83
|
+
const add = (key: string, value: unknown) => {
|
|
84
|
+
if (value !== undefined) input[key] = value;
|
|
85
|
+
};
|
|
86
|
+
if (command === "search") {
|
|
87
|
+
for (const flag of ["mailbox", "from", "to", "subject"])
|
|
88
|
+
add(flag, valueFlag(args, `--${flag}`));
|
|
89
|
+
add("after", dateFlag(args, "--after"));
|
|
90
|
+
add("before", dateFlag(args, "--before"));
|
|
91
|
+
const direction = valueFlag(args, "--direction");
|
|
92
|
+
if (
|
|
93
|
+
direction !== undefined &&
|
|
94
|
+
direction !== "inbound" &&
|
|
95
|
+
direction !== "outbound"
|
|
96
|
+
)
|
|
97
|
+
throw new Error("--direction must be inbound or outbound");
|
|
98
|
+
add("direction", direction);
|
|
99
|
+
if (
|
|
100
|
+
typeof input.after === "number" &&
|
|
101
|
+
typeof input.before === "number" &&
|
|
102
|
+
input.after >= input.before
|
|
103
|
+
)
|
|
104
|
+
throw new Error("--after must be earlier than --before");
|
|
105
|
+
} else if (command === "thread" || command === "message") {
|
|
106
|
+
const handle = args.shift();
|
|
107
|
+
if (
|
|
108
|
+
!handle?.startsWith(`email-${command}:`) ||
|
|
109
|
+
handle.length <= `email-${command}:`.length
|
|
110
|
+
)
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Use an exact ${command} handle returned by email inbox search`,
|
|
113
|
+
);
|
|
114
|
+
input[`${command}Handle`] = handle;
|
|
115
|
+
} else
|
|
116
|
+
throw new Error("Unknown inbox command. Run `dench email inbox --help`.");
|
|
117
|
+
if (command === "message") {
|
|
118
|
+
const format = valueFlag(args, "--format");
|
|
119
|
+
if (format !== undefined && format !== "text" && format !== "html")
|
|
120
|
+
throw new Error("--format must be text or html");
|
|
121
|
+
add("format", format);
|
|
122
|
+
add("maxBytes", integerFlag(args, "--max-bytes", 100_000));
|
|
123
|
+
} else {
|
|
124
|
+
add("limit", integerFlag(args, "--limit", 50));
|
|
125
|
+
add("cursor", valueFlag(args, "--cursor"));
|
|
126
|
+
}
|
|
127
|
+
assertNoUnknownFlags(args, "email inbox");
|
|
128
|
+
if (args.length)
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Unexpected inbox argument: ${args[0]}. Run \`dench email inbox --help\`.`,
|
|
131
|
+
);
|
|
132
|
+
const result = await ctx.convex.query(
|
|
133
|
+
makeFunctionReference<"query">(`functions/emailInbox:${command}`),
|
|
134
|
+
{
|
|
135
|
+
...input,
|
|
136
|
+
...(ctx.sessionToken ? { sessionToken: ctx.sessionToken } : {}),
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
if (jsonOutput) {
|
|
140
|
+
console.log(JSON.stringify(result, null, 2));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (command === "message") {
|
|
144
|
+
console.log(
|
|
145
|
+
`${result.header.subject}\nFrom: ${result.header.from.email}\n\n${result.body.content}`,
|
|
146
|
+
);
|
|
147
|
+
if (result.body.truncated)
|
|
148
|
+
console.log(
|
|
149
|
+
`\nBody truncated to the requested byte limit (${result.body.totalBytes} bytes stored).`,
|
|
150
|
+
);
|
|
151
|
+
} else {
|
|
152
|
+
for (const row of result.page)
|
|
153
|
+
console.log(
|
|
154
|
+
`${new Date(row.at).toISOString()} ${row.from.email} ${row.subject}\n ${row.messageHandle} ${row.threadHandle}`,
|
|
155
|
+
);
|
|
156
|
+
if (!result.page.length) console.log("No matching headers on this page.");
|
|
157
|
+
if (!result.isDone)
|
|
158
|
+
console.log(
|
|
159
|
+
`Continue with --cursor ${JSON.stringify(result.continueCursor)}`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (result.coverage.privacyLimited)
|
|
163
|
+
console.log(
|
|
164
|
+
"Some older messages were omitted because their full record privacy scope is unavailable.",
|
|
165
|
+
);
|
|
166
|
+
console.log(
|
|
167
|
+
`Synced mailbox: ${result.coverage.mailboxEmail} (${result.coverage.phase}); provider completeness is not established.`,
|
|
168
|
+
);
|
|
169
|
+
}
|
package/email.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { createHash, createHmac, randomUUID } from "node:crypto";
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, extname, join } from "node:path";
|
|
4
|
-
import
|
|
4
|
+
import { ConvexHttpClient } from "convex/browser";
|
|
5
5
|
import { makeFunctionReference } from "convex/server";
|
|
6
|
+
import { EMAIL_INBOX_HELP, runEmailInboxCommand } from "./email-inbox";
|
|
6
7
|
import {
|
|
7
8
|
assertNoUnknownFlags,
|
|
8
9
|
getFlag,
|
|
9
10
|
hasFlag,
|
|
11
|
+
isHelpRequest,
|
|
10
12
|
parseJson,
|
|
11
13
|
} from "./lib/cli-args";
|
|
12
14
|
import { denchConfigDirectory } from "./lib/config-path.mjs";
|
|
15
|
+
import { waitForNativeEmail } from "./lib/native-email-wait";
|
|
13
16
|
|
|
14
|
-
type EmailCliContext = {
|
|
17
|
+
export type EmailCliContext = {
|
|
15
18
|
convex: ConvexHttpClient;
|
|
16
19
|
args: string[];
|
|
17
20
|
jsonOutput: boolean;
|
|
@@ -19,9 +22,21 @@ type EmailCliContext = {
|
|
|
19
22
|
bearerToken?: string;
|
|
20
23
|
gatewayBaseUrl?: string;
|
|
21
24
|
gatewayHeaders?: Record<string, string>;
|
|
25
|
+
/** Injectable read transport; it must honor the supplied observation deadline. */
|
|
26
|
+
readNativeReceipt?: (
|
|
27
|
+
draftId: string,
|
|
28
|
+
signal: AbortSignal,
|
|
29
|
+
) => Promise<unknown>;
|
|
22
30
|
};
|
|
23
31
|
|
|
24
|
-
class EmailCliError extends Error {
|
|
32
|
+
class EmailCliError extends Error {
|
|
33
|
+
constructor(
|
|
34
|
+
message: string,
|
|
35
|
+
readonly code = "invalid_arguments",
|
|
36
|
+
) {
|
|
37
|
+
super(message);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
25
40
|
|
|
26
41
|
type JsonRecord = Record<string, unknown>;
|
|
27
42
|
|
|
@@ -48,6 +63,7 @@ function requireBearerToken(ctx: EmailCliContext): string {
|
|
|
48
63
|
if (!token) {
|
|
49
64
|
throw new EmailCliError(
|
|
50
65
|
"No Dench credential found. Set DENCH_API_KEY or run `dench signin`.",
|
|
66
|
+
"login_required",
|
|
51
67
|
);
|
|
52
68
|
}
|
|
53
69
|
return token;
|
|
@@ -59,6 +75,7 @@ async function callGateway(
|
|
|
59
75
|
path: string,
|
|
60
76
|
body?: JsonRecord,
|
|
61
77
|
) {
|
|
78
|
+
if (method === "POST") assertEmailArgs(ctx);
|
|
62
79
|
const baseUrl = resolveGatewayBaseUrl(ctx);
|
|
63
80
|
const response = await fetch(`${baseUrl}${path}`, {
|
|
64
81
|
method,
|
|
@@ -74,6 +91,7 @@ async function callGateway(
|
|
|
74
91
|
if (!response.ok) {
|
|
75
92
|
throw new EmailCliError(
|
|
76
93
|
`Email service returned ${response.status}: ${text.slice(0, 500)}`,
|
|
94
|
+
"upstream_failed",
|
|
77
95
|
);
|
|
78
96
|
}
|
|
79
97
|
const payload = text ? JSON.parse(text) : {};
|
|
@@ -85,6 +103,7 @@ async function callMutation(
|
|
|
85
103
|
name: string,
|
|
86
104
|
args: JsonRecord,
|
|
87
105
|
) {
|
|
106
|
+
assertEmailArgs(ctx);
|
|
88
107
|
const ref = makeFunctionReference<"mutation">(name);
|
|
89
108
|
return await ctx.convex.mutation(ref, {
|
|
90
109
|
...args,
|
|
@@ -105,6 +124,7 @@ async function callAction(
|
|
|
105
124
|
name: string,
|
|
106
125
|
args: JsonRecord,
|
|
107
126
|
) {
|
|
127
|
+
assertEmailArgs(ctx);
|
|
108
128
|
const ref = makeFunctionReference<"action">(name);
|
|
109
129
|
return await ctx.convex.action(ref, {
|
|
110
130
|
...args,
|
|
@@ -362,17 +382,20 @@ function toRecipients(rows: JsonRecord[]) {
|
|
|
362
382
|
});
|
|
363
383
|
}
|
|
364
384
|
|
|
365
|
-
export function
|
|
366
|
-
|
|
385
|
+
export function emailHelpText() {
|
|
386
|
+
return `dench email — outbound email from a connected inbox (or Dench Emailing Service)
|
|
367
387
|
|
|
368
388
|
Usage:
|
|
389
|
+
${EMAIL_INBOX_HELP}
|
|
369
390
|
dench email draft create --to <email> --subject <subject> --text <body> [--mailbox <id/email>] [--request-id <id>] [--json]
|
|
370
391
|
dench email draft show <id> [--json]
|
|
371
|
-
dench email draft update
|
|
392
|
+
dench email draft update <id> --revision <n> [--to <email>] [--subject <subject>] [--text <body>] [--mailbox <id>] [--json]
|
|
393
|
+
dench email draft send <id> --revision <n> [--request-id <id>] [--wait <seconds>|--no-wait] [--json]
|
|
394
|
+
dench email draft senders [--json] # available Gmail/Outlook mailboxes
|
|
372
395
|
dench email draft attach <id> --revision <n> --file <path> [--content-type <mime>] [--json] # up to 5 MB/file
|
|
373
396
|
dench email draft signature <id> [--json] # read signature HTML; insert visibly into the body
|
|
374
397
|
# Identical native create/send invocations reuse a saved request ID. Use --request-id <new-id> for another identical email.
|
|
375
|
-
dench email send --
|
|
398
|
+
dench email send --to <email> [--subject <subject>] (--text <body> | --html <body> | --text-file <path> | --html-file <path>) [--mailbox <id/email>] [--cc <emails>] [--bcc <emails>] [--request-id <id>] [--wait <seconds>|--no-wait] [--json] # native default; observe the same receipt for 10s by default (--wait 0..60)
|
|
376
399
|
dench email identity verify --from founder@example.com [--private] [--json]
|
|
377
400
|
dench email identity verify --domain example.com [--from founder@example.com] [--private] [--json]
|
|
378
401
|
dench email identity list [--domain example.com] [--json]
|
|
@@ -443,10 +466,15 @@ Email + LinkedIn multichannel sequences (see \`dench campaign\` — the fuller s
|
|
|
443
466
|
dench email multichannel enroll-from-object --sequence <id> --object lead --entries id1,id2 [--email-field Email] [--linkedin-field "POC LinkedIn URL"] [--relation-field Contact] [--reverse] [--allow-duplicates] [--json]
|
|
444
467
|
dench email multichannel launch --sequence <id> [--ack-linkedin-risk] [--json]
|
|
445
468
|
dench email multichannel pause|resume|archive --sequence <id> [--json]
|
|
446
|
-
(see \`dench campaign help\` for the same campaigns with schedule, view-based add, launch checks and the Now/Next feed)
|
|
469
|
+
(see \`dench campaign help\` for the same campaigns with schedule, view-based add, launch checks and the Now/Next feed)`;
|
|
447
470
|
}
|
|
448
471
|
|
|
449
472
|
export async function runEmailCommand(ctx: EmailCliContext): Promise<void> {
|
|
473
|
+
if (isHelpRequest(ctx.args)) {
|
|
474
|
+
const { commandHelp } = await import("./lib/command-help");
|
|
475
|
+
console.log(commandHelp(["email", ...ctx.args], emailHelpText()));
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
450
478
|
ctx.args = ctx.args.flatMap((arg) => {
|
|
451
479
|
const split = arg.startsWith("--") ? arg.indexOf("=") : -1;
|
|
452
480
|
return split > 2 ? [arg.slice(0, split), arg.slice(split + 1)] : [arg];
|
|
@@ -454,7 +482,11 @@ export async function runEmailCommand(ctx: EmailCliContext): Promise<void> {
|
|
|
454
482
|
ctx.jsonOutput = hasFlag(ctx.args, "--json") || ctx.jsonOutput;
|
|
455
483
|
const group = ctx.args.shift();
|
|
456
484
|
if (!group || group === "help" || group === "--help" || group === "-h") {
|
|
457
|
-
|
|
485
|
+
console.log(emailHelpText());
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if (group === "inbox") {
|
|
489
|
+
await runEmailInboxCommand(ctx);
|
|
458
490
|
return;
|
|
459
491
|
}
|
|
460
492
|
if (group === "draft") {
|
|
@@ -524,14 +556,20 @@ function parseAddressList(
|
|
|
524
556
|
}
|
|
525
557
|
|
|
526
558
|
async function runSend(ctx: EmailCliContext) {
|
|
527
|
-
|
|
559
|
+
const wait = waitFlag(ctx.args);
|
|
560
|
+
if (!ctx.args.includes("--from")) {
|
|
528
561
|
const content = {
|
|
529
562
|
...(await draftContent(ctx)),
|
|
530
|
-
mailbox:
|
|
563
|
+
mailbox: getFlag(ctx.args, "--mailbox"),
|
|
531
564
|
threadId: getFlag(ctx.args, "--thread") ?? process.env.DENCH_THREAD_ID,
|
|
532
565
|
mode: "send",
|
|
533
566
|
};
|
|
534
|
-
|
|
567
|
+
if (!Array.isArray(content.to) || content.to.length === 0)
|
|
568
|
+
throw new EmailCliError("Sending requires --to <email>");
|
|
569
|
+
const created = await createNativeEmail(ctx, content);
|
|
570
|
+
const payload = wait
|
|
571
|
+
? await observeNativeEmail(ctx, created, wait)
|
|
572
|
+
: created;
|
|
535
573
|
out(ctx, payload, JSON.stringify(payload, null, 2));
|
|
536
574
|
return;
|
|
537
575
|
}
|
|
@@ -575,6 +613,50 @@ async function runSend(ctx: EmailCliContext) {
|
|
|
575
613
|
);
|
|
576
614
|
}
|
|
577
615
|
|
|
616
|
+
function waitFlag(args: string[]): number {
|
|
617
|
+
const noWait = hasFlag(args, "--no-wait");
|
|
618
|
+
const raw = getFlag(args, "--wait");
|
|
619
|
+
if (raw !== undefined && noWait)
|
|
620
|
+
throw new EmailCliError("Use --wait or --no-wait, not both");
|
|
621
|
+
const seconds = raw === undefined ? 10 : Number(raw);
|
|
622
|
+
if (
|
|
623
|
+
raw?.trim() === "" ||
|
|
624
|
+
!Number.isFinite(seconds) ||
|
|
625
|
+
seconds < 0 ||
|
|
626
|
+
seconds > 60
|
|
627
|
+
)
|
|
628
|
+
throw new EmailCliError(
|
|
629
|
+
"--wait must be a finite number of seconds from 0 to 60",
|
|
630
|
+
);
|
|
631
|
+
return noWait ? 0 : seconds * 1000;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function observeNativeEmail(
|
|
635
|
+
ctx: EmailCliContext,
|
|
636
|
+
receipt: unknown,
|
|
637
|
+
waitMs: number,
|
|
638
|
+
) {
|
|
639
|
+
return waitForNativeEmail(
|
|
640
|
+
receipt,
|
|
641
|
+
ctx.readNativeReceipt ??
|
|
642
|
+
((draftId, signal) => {
|
|
643
|
+
const reader = new ConvexHttpClient(ctx.convex.url, {
|
|
644
|
+
logger: false,
|
|
645
|
+
fetch: new Proxy(fetch, {
|
|
646
|
+
apply(target, _receiver, [input, init]) {
|
|
647
|
+
return target(input, { ...init, signal });
|
|
648
|
+
},
|
|
649
|
+
}),
|
|
650
|
+
});
|
|
651
|
+
return reader.query(
|
|
652
|
+
makeFunctionReference<"query">("functions/emailDrafts:get"),
|
|
653
|
+
{ draftId, sessionToken: ctx.sessionToken },
|
|
654
|
+
);
|
|
655
|
+
}),
|
|
656
|
+
waitMs,
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
578
660
|
/** Persist before HTTP. Identical invocations recover the same request even
|
|
579
661
|
* after the CLI process dies; an explicit new --request-id starts another email. */
|
|
580
662
|
async function createNativeEmail(ctx: EmailCliContext, content: JsonRecord) {
|
|
@@ -615,7 +697,7 @@ async function createNativeEmail(ctx: EmailCliContext, content: JsonRecord) {
|
|
|
615
697
|
}
|
|
616
698
|
|
|
617
699
|
function assertEmailArgs(ctx: EmailCliContext) {
|
|
618
|
-
assertNoUnknownFlags(ctx.args, "
|
|
700
|
+
assertNoUnknownFlags(ctx.args, "email");
|
|
619
701
|
if (ctx.args.length)
|
|
620
702
|
throw new EmailCliError(`Unexpected argument: ${ctx.args[0]}`);
|
|
621
703
|
}
|
|
@@ -671,6 +753,7 @@ async function runDraft(ctx: EmailCliContext) {
|
|
|
671
753
|
draftId,
|
|
672
754
|
});
|
|
673
755
|
} else if (sub === "update" || sub === "send") {
|
|
756
|
+
const wait = sub === "send" ? waitFlag(ctx.args) : 0;
|
|
674
757
|
const draftId = draftIdArg(ctx);
|
|
675
758
|
const expectedRevision = revisionArg(ctx);
|
|
676
759
|
const args =
|
|
@@ -690,6 +773,7 @@ async function runDraft(ctx: EmailCliContext) {
|
|
|
690
773
|
};
|
|
691
774
|
assertEmailArgs(ctx);
|
|
692
775
|
result = await callMutation(ctx, `functions/emailDrafts:${sub}`, args);
|
|
776
|
+
if (wait) result = await observeNativeEmail(ctx, result, wait);
|
|
693
777
|
} else if (sub === "attach") {
|
|
694
778
|
const draftId = draftIdArg(ctx);
|
|
695
779
|
const expectedRevision = revisionArg(ctx);
|
|
@@ -1146,6 +1230,8 @@ async function runCampaign(ctx: EmailCliContext) {
|
|
|
1146
1230
|
const sub = ctx.args.shift();
|
|
1147
1231
|
if (sub === "create") {
|
|
1148
1232
|
const sender = await campaignSenderArgs(ctx);
|
|
1233
|
+
const scheduledAt = getFlag(ctx.args, "--scheduled-at");
|
|
1234
|
+
const maxRecipients = getFlag(ctx.args, "--max-recipients");
|
|
1149
1235
|
const payload = await callMutation(
|
|
1150
1236
|
ctx,
|
|
1151
1237
|
"functions/emailCampaigns:createCampaign",
|
|
@@ -1153,12 +1239,8 @@ async function runCampaign(ctx: EmailCliContext) {
|
|
|
1153
1239
|
name: requiredFlag(ctx.args, "--name"),
|
|
1154
1240
|
...sender,
|
|
1155
1241
|
templateId: requiredFlag(ctx.args, "--template"),
|
|
1156
|
-
scheduledAt:
|
|
1157
|
-
|
|
1158
|
-
: undefined,
|
|
1159
|
-
maxRecipients: getFlag(ctx.args, "--max-recipients")
|
|
1160
|
-
? Number(requiredFlag(ctx.args, "--max-recipients"))
|
|
1161
|
-
: undefined,
|
|
1242
|
+
scheduledAt: scheduledAt ? Date.parse(scheduledAt) : undefined,
|
|
1243
|
+
maxRecipients: maxRecipients ? Number(maxRecipients) : undefined,
|
|
1162
1244
|
},
|
|
1163
1245
|
);
|
|
1164
1246
|
out(
|
|
@@ -1377,7 +1459,7 @@ async function runSequence(ctx: EmailCliContext) {
|
|
|
1377
1459
|
...sender,
|
|
1378
1460
|
replyToMode: getFlag(ctx.args, "--reply-to-mode"),
|
|
1379
1461
|
stopOnReply,
|
|
1380
|
-
stopOnClick:
|
|
1462
|
+
stopOnClick: hasFlag(ctx.args, "--stop-on-click"),
|
|
1381
1463
|
},
|
|
1382
1464
|
);
|
|
1383
1465
|
out(
|
|
@@ -1426,7 +1508,7 @@ async function runSequence(ctx: EmailCliContext) {
|
|
|
1426
1508
|
const payload = await callQuery(
|
|
1427
1509
|
ctx,
|
|
1428
1510
|
"functions/emailSequences:listSequences",
|
|
1429
|
-
{ includeArchived:
|
|
1511
|
+
{ includeArchived: hasFlag(ctx.args, "--all") },
|
|
1430
1512
|
);
|
|
1431
1513
|
out(ctx, payload, JSON.stringify(payload, null, 2));
|
|
1432
1514
|
return;
|