emailr-cli 1.0.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.
- package/README.md +247 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1303 -0
- package/package.json +57 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1303 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command9 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/send.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import { Emailr } from "@emailr/sdk";
|
|
9
|
+
|
|
10
|
+
// src/config.ts
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import os from "os";
|
|
13
|
+
import path from "path";
|
|
14
|
+
var CONFIG_PATHS = [
|
|
15
|
+
path.join(os.homedir(), ".emailrrc"),
|
|
16
|
+
path.join(os.homedir(), ".config", "emailr", "config.json")
|
|
17
|
+
];
|
|
18
|
+
function loadConfig() {
|
|
19
|
+
if (process.env.EMAILR_API_KEY) {
|
|
20
|
+
return {
|
|
21
|
+
apiKey: process.env.EMAILR_API_KEY,
|
|
22
|
+
baseUrl: process.env.EMAILR_BASE_URL,
|
|
23
|
+
format: process.env.EMAILR_FORMAT || "table"
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
for (const configPath of CONFIG_PATHS) {
|
|
27
|
+
if (fs.existsSync(configPath)) {
|
|
28
|
+
try {
|
|
29
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
30
|
+
const config = JSON.parse(content);
|
|
31
|
+
if (config.apiKey) {
|
|
32
|
+
return {
|
|
33
|
+
apiKey: config.apiKey,
|
|
34
|
+
baseUrl: config.baseUrl,
|
|
35
|
+
format: config.format || "table"
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
throw new Error(
|
|
43
|
+
'No API key configured.\n\nSet the EMAILR_API_KEY environment variable:\n export EMAILR_API_KEY=your-api-key\n\nOr create a config file at ~/.emailrrc:\n { "apiKey": "your-api-key" }\n\nOr run: emailr config set api-key <your-api-key>'
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
function getConfigPath() {
|
|
47
|
+
const configDir = path.join(os.homedir(), ".config", "emailr");
|
|
48
|
+
return path.join(configDir, "config.json");
|
|
49
|
+
}
|
|
50
|
+
function saveConfig(config) {
|
|
51
|
+
const configPath = getConfigPath();
|
|
52
|
+
const configDir = path.dirname(configPath);
|
|
53
|
+
if (!fs.existsSync(configDir)) {
|
|
54
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
let existingConfig = {};
|
|
57
|
+
if (fs.existsSync(configPath)) {
|
|
58
|
+
try {
|
|
59
|
+
existingConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const newConfig = { ...existingConfig, ...config };
|
|
64
|
+
fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2) + "\n");
|
|
65
|
+
}
|
|
66
|
+
function getConfigValue(key) {
|
|
67
|
+
try {
|
|
68
|
+
const config = loadConfig();
|
|
69
|
+
return config[key]?.toString();
|
|
70
|
+
} catch {
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/output.ts
|
|
76
|
+
import Table from "cli-table3";
|
|
77
|
+
import chalk from "chalk";
|
|
78
|
+
function output(data, format = "table") {
|
|
79
|
+
if (format === "json") {
|
|
80
|
+
console.log(JSON.stringify(data, null, 2));
|
|
81
|
+
} else {
|
|
82
|
+
printTable(data);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function printTable(data) {
|
|
86
|
+
if (Array.isArray(data)) {
|
|
87
|
+
printArrayTable(data);
|
|
88
|
+
} else if (typeof data === "object" && data !== null) {
|
|
89
|
+
printObjectTable(data);
|
|
90
|
+
} else {
|
|
91
|
+
console.log(data);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function printArrayTable(data) {
|
|
95
|
+
if (data.length === 0) {
|
|
96
|
+
console.log(chalk.gray("No results"));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const firstItem = data[0];
|
|
100
|
+
if (typeof firstItem !== "object" || firstItem === null) {
|
|
101
|
+
data.forEach((item) => console.log(item));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const keys = Object.keys(firstItem);
|
|
105
|
+
const table = new Table({
|
|
106
|
+
head: keys.map((k) => chalk.cyan(k)),
|
|
107
|
+
style: { head: [], border: [] }
|
|
108
|
+
});
|
|
109
|
+
for (const item of data) {
|
|
110
|
+
const row = keys.map((key) => {
|
|
111
|
+
const value = item[key];
|
|
112
|
+
return formatValue(value);
|
|
113
|
+
});
|
|
114
|
+
table.push(row);
|
|
115
|
+
}
|
|
116
|
+
console.log(table.toString());
|
|
117
|
+
}
|
|
118
|
+
function printObjectTable(data) {
|
|
119
|
+
const table = new Table({
|
|
120
|
+
style: { head: [], border: [] }
|
|
121
|
+
});
|
|
122
|
+
for (const [key, value] of Object.entries(data)) {
|
|
123
|
+
table.push([chalk.cyan(key), formatValue(value)]);
|
|
124
|
+
}
|
|
125
|
+
console.log(table.toString());
|
|
126
|
+
}
|
|
127
|
+
function formatValue(value) {
|
|
128
|
+
if (value === null || value === void 0) {
|
|
129
|
+
return chalk.gray("-");
|
|
130
|
+
}
|
|
131
|
+
if (typeof value === "boolean") {
|
|
132
|
+
return value ? chalk.green("\u2713") : chalk.red("\u2717");
|
|
133
|
+
}
|
|
134
|
+
if (typeof value === "object") {
|
|
135
|
+
return JSON.stringify(value);
|
|
136
|
+
}
|
|
137
|
+
return String(value);
|
|
138
|
+
}
|
|
139
|
+
function success(message) {
|
|
140
|
+
console.log(chalk.green("\u2713"), message);
|
|
141
|
+
}
|
|
142
|
+
function error(message) {
|
|
143
|
+
console.error(chalk.red("\u2717"), message);
|
|
144
|
+
}
|
|
145
|
+
function info(message) {
|
|
146
|
+
console.log(chalk.blue("\u2139"), message);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/commands/send.ts
|
|
150
|
+
function createSendCommand() {
|
|
151
|
+
const cmd = new Command("send").description("Send an email").requiredOption("--to <email>", "Recipient email address (comma-separated for multiple)").option("--from <email>", "Sender email address").option("--subject <subject>", "Email subject").option("--html <html>", "HTML content").option("--text <text>", "Plain text content").option("--template <id>", "Template ID to use").option("--template-data <json>", "Template data as JSON").option("--cc <emails>", "CC recipients (comma-separated)").option("--bcc <emails>", "BCC recipients (comma-separated)").option("--reply-to <email>", "Reply-to email address").option("--schedule <datetime>", "Schedule send time (ISO 8601)").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
152
|
+
try {
|
|
153
|
+
const config = loadConfig();
|
|
154
|
+
const client = new Emailr({
|
|
155
|
+
apiKey: config.apiKey,
|
|
156
|
+
baseUrl: config.baseUrl
|
|
157
|
+
});
|
|
158
|
+
const to = options.to.split(",").map((e) => e.trim());
|
|
159
|
+
const request = {
|
|
160
|
+
to: to.length === 1 ? to[0] : to
|
|
161
|
+
};
|
|
162
|
+
if (options.from) request.from = options.from;
|
|
163
|
+
if (options.subject) request.subject = options.subject;
|
|
164
|
+
if (options.html) request.html = options.html;
|
|
165
|
+
if (options.text) request.text = options.text;
|
|
166
|
+
if (options.template) request.template_id = options.template;
|
|
167
|
+
if (options.templateData) {
|
|
168
|
+
try {
|
|
169
|
+
request.template_data = JSON.parse(options.templateData);
|
|
170
|
+
} catch {
|
|
171
|
+
error("Invalid JSON for --template-data");
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (options.cc) {
|
|
176
|
+
const cc = options.cc.split(",").map((e) => e.trim());
|
|
177
|
+
request.cc = cc.length === 1 ? cc[0] : cc;
|
|
178
|
+
}
|
|
179
|
+
if (options.bcc) {
|
|
180
|
+
const bcc = options.bcc.split(",").map((e) => e.trim());
|
|
181
|
+
request.bcc = bcc.length === 1 ? bcc[0] : bcc;
|
|
182
|
+
}
|
|
183
|
+
if (options.replyTo) request.reply_to_email = options.replyTo;
|
|
184
|
+
if (options.schedule) request.scheduled_at = options.schedule;
|
|
185
|
+
const result = await client.emails.send(request);
|
|
186
|
+
if (options.format === "json") {
|
|
187
|
+
output(result, "json");
|
|
188
|
+
} else {
|
|
189
|
+
success(`Email sent successfully!`);
|
|
190
|
+
output({
|
|
191
|
+
"Message ID": result.message_id,
|
|
192
|
+
Recipients: result.recipients,
|
|
193
|
+
Status: result.status,
|
|
194
|
+
...result.scheduled_at && { "Scheduled At": result.scheduled_at }
|
|
195
|
+
}, "table");
|
|
196
|
+
}
|
|
197
|
+
} catch (err) {
|
|
198
|
+
error(err instanceof Error ? err.message : "Failed to send email");
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
return cmd;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/commands/contacts.ts
|
|
206
|
+
import { Command as Command2 } from "commander";
|
|
207
|
+
import { Emailr as Emailr2 } from "@emailr/sdk";
|
|
208
|
+
function createContactsCommand() {
|
|
209
|
+
const cmd = new Command2("contacts").description("Manage contacts");
|
|
210
|
+
cmd.command("list").description("List all contacts").option("--limit <number>", "Number of contacts to return", "20").option("--offset <number>", "Offset for pagination", "0").option("--subscribed", "Only show subscribed contacts").option("--unsubscribed", "Only show unsubscribed contacts").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
211
|
+
try {
|
|
212
|
+
const config = loadConfig();
|
|
213
|
+
const client = new Emailr2({
|
|
214
|
+
apiKey: config.apiKey,
|
|
215
|
+
baseUrl: config.baseUrl
|
|
216
|
+
});
|
|
217
|
+
const params = {
|
|
218
|
+
limit: parseInt(options.limit, 10),
|
|
219
|
+
offset: parseInt(options.offset, 10)
|
|
220
|
+
};
|
|
221
|
+
if (options.subscribed) params.subscribed = true;
|
|
222
|
+
if (options.unsubscribed) params.subscribed = false;
|
|
223
|
+
const result = await client.contacts.list(params);
|
|
224
|
+
if (options.format === "json") {
|
|
225
|
+
output(result, "json");
|
|
226
|
+
} else {
|
|
227
|
+
const contacts = result.contacts.map((c) => ({
|
|
228
|
+
ID: c.id,
|
|
229
|
+
Email: c.email,
|
|
230
|
+
Name: [c.first_name, c.last_name].filter(Boolean).join(" ") || "-",
|
|
231
|
+
Subscribed: c.subscribed,
|
|
232
|
+
Created: c.created_at
|
|
233
|
+
}));
|
|
234
|
+
output(contacts, "table");
|
|
235
|
+
console.log(`
|
|
236
|
+
Total: ${result.total}`);
|
|
237
|
+
}
|
|
238
|
+
} catch (err) {
|
|
239
|
+
error(err instanceof Error ? err.message : "Failed to list contacts");
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
cmd.command("get <id>").description("Get a contact by ID").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
244
|
+
try {
|
|
245
|
+
const config = loadConfig();
|
|
246
|
+
const client = new Emailr2({
|
|
247
|
+
apiKey: config.apiKey,
|
|
248
|
+
baseUrl: config.baseUrl
|
|
249
|
+
});
|
|
250
|
+
const contact = await client.contacts.get(id);
|
|
251
|
+
output(contact, options.format);
|
|
252
|
+
} catch (err) {
|
|
253
|
+
error(err instanceof Error ? err.message : "Failed to get contact");
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
cmd.command("create").description("Create a new contact").requiredOption("--email <email>", "Contact email address").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--subscribed", "Mark as subscribed (default: true)").option("--metadata <json>", "Metadata as JSON").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
258
|
+
try {
|
|
259
|
+
const config = loadConfig();
|
|
260
|
+
const client = new Emailr2({
|
|
261
|
+
apiKey: config.apiKey,
|
|
262
|
+
baseUrl: config.baseUrl
|
|
263
|
+
});
|
|
264
|
+
const request = {
|
|
265
|
+
email: options.email
|
|
266
|
+
};
|
|
267
|
+
if (options.firstName) request.first_name = options.firstName;
|
|
268
|
+
if (options.lastName) request.last_name = options.lastName;
|
|
269
|
+
if (options.subscribed !== void 0) request.subscribed = options.subscribed;
|
|
270
|
+
if (options.metadata) {
|
|
271
|
+
try {
|
|
272
|
+
request.metadata = JSON.parse(options.metadata);
|
|
273
|
+
} catch {
|
|
274
|
+
error("Invalid JSON for --metadata");
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const contact = await client.contacts.create(request);
|
|
279
|
+
if (options.format === "json") {
|
|
280
|
+
output(contact, "json");
|
|
281
|
+
} else {
|
|
282
|
+
success(`Contact created: ${contact.id}`);
|
|
283
|
+
output(contact, "table");
|
|
284
|
+
}
|
|
285
|
+
} catch (err) {
|
|
286
|
+
error(err instanceof Error ? err.message : "Failed to create contact");
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
cmd.command("update <id>").description("Update a contact").option("--email <email>", "New email address").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--subscribed", "Mark as subscribed").option("--unsubscribed", "Mark as unsubscribed").option("--metadata <json>", "Metadata as JSON").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
291
|
+
try {
|
|
292
|
+
const config = loadConfig();
|
|
293
|
+
const client = new Emailr2({
|
|
294
|
+
apiKey: config.apiKey,
|
|
295
|
+
baseUrl: config.baseUrl
|
|
296
|
+
});
|
|
297
|
+
const request = {};
|
|
298
|
+
if (options.email) request.email = options.email;
|
|
299
|
+
if (options.firstName) request.first_name = options.firstName;
|
|
300
|
+
if (options.lastName) request.last_name = options.lastName;
|
|
301
|
+
if (options.subscribed) request.subscribed = true;
|
|
302
|
+
if (options.unsubscribed) request.subscribed = false;
|
|
303
|
+
if (options.metadata) {
|
|
304
|
+
try {
|
|
305
|
+
request.metadata = JSON.parse(options.metadata);
|
|
306
|
+
} catch {
|
|
307
|
+
error("Invalid JSON for --metadata");
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const contact = await client.contacts.update(id, request);
|
|
312
|
+
if (options.format === "json") {
|
|
313
|
+
output(contact, "json");
|
|
314
|
+
} else {
|
|
315
|
+
success(`Contact updated: ${contact.id}`);
|
|
316
|
+
output(contact, "table");
|
|
317
|
+
}
|
|
318
|
+
} catch (err) {
|
|
319
|
+
error(err instanceof Error ? err.message : "Failed to update contact");
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
cmd.command("delete <id>").description("Delete a contact").action(async (id) => {
|
|
324
|
+
try {
|
|
325
|
+
const config = loadConfig();
|
|
326
|
+
const client = new Emailr2({
|
|
327
|
+
apiKey: config.apiKey,
|
|
328
|
+
baseUrl: config.baseUrl
|
|
329
|
+
});
|
|
330
|
+
await client.contacts.delete(id);
|
|
331
|
+
success(`Contact deleted: ${id}`);
|
|
332
|
+
} catch (err) {
|
|
333
|
+
error(err instanceof Error ? err.message : "Failed to delete contact");
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
return cmd;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/commands/templates.ts
|
|
341
|
+
import { Command as Command3 } from "commander";
|
|
342
|
+
import { Emailr as Emailr3 } from "@emailr/sdk";
|
|
343
|
+
function createTemplatesCommand() {
|
|
344
|
+
const cmd = new Command3("templates").description("Manage email templates");
|
|
345
|
+
cmd.command("list").description("List all templates").option("--limit <number>", "Number of templates to return", "20").option("--page <number>", "Page number", "1").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
346
|
+
try {
|
|
347
|
+
const config = loadConfig();
|
|
348
|
+
const client = new Emailr3({
|
|
349
|
+
apiKey: config.apiKey,
|
|
350
|
+
baseUrl: config.baseUrl
|
|
351
|
+
});
|
|
352
|
+
const result = await client.templates.list({
|
|
353
|
+
limit: parseInt(options.limit, 10),
|
|
354
|
+
page: parseInt(options.page, 10)
|
|
355
|
+
});
|
|
356
|
+
if (options.format === "json") {
|
|
357
|
+
output(result, "json");
|
|
358
|
+
} else {
|
|
359
|
+
const templates = result.map((t) => ({
|
|
360
|
+
ID: t.id,
|
|
361
|
+
Name: t.name,
|
|
362
|
+
Subject: t.subject,
|
|
363
|
+
Variables: t.variables?.join(", ") || "-",
|
|
364
|
+
Created: t.created_at
|
|
365
|
+
}));
|
|
366
|
+
output(templates, "table");
|
|
367
|
+
console.log(`
|
|
368
|
+
Total: ${result.length}`);
|
|
369
|
+
}
|
|
370
|
+
} catch (err) {
|
|
371
|
+
error(err instanceof Error ? err.message : "Failed to list templates");
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
cmd.command("get <id>").description("Get a template by ID").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
376
|
+
try {
|
|
377
|
+
const config = loadConfig();
|
|
378
|
+
const client = new Emailr3({
|
|
379
|
+
apiKey: config.apiKey,
|
|
380
|
+
baseUrl: config.baseUrl
|
|
381
|
+
});
|
|
382
|
+
const template = await client.templates.get(id);
|
|
383
|
+
output(template, options.format);
|
|
384
|
+
} catch (err) {
|
|
385
|
+
error(err instanceof Error ? err.message : "Failed to get template");
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
cmd.command("create").description("Create a new template").requiredOption("--name <name>", "Template name").requiredOption("--subject <subject>", "Email subject").option("--html <html>", "HTML content").option("--text <text>", "Plain text content").option("--html-file <path>", "Read HTML content from file").option("--text-file <path>", "Read text content from file").option("--from <email>", "Default from email").option("--reply-to <email>", "Default reply-to email").option("--preview-text <text>", "Preview text").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
390
|
+
try {
|
|
391
|
+
const config = loadConfig();
|
|
392
|
+
const client = new Emailr3({
|
|
393
|
+
apiKey: config.apiKey,
|
|
394
|
+
baseUrl: config.baseUrl
|
|
395
|
+
});
|
|
396
|
+
const request = {
|
|
397
|
+
name: options.name,
|
|
398
|
+
subject: options.subject
|
|
399
|
+
};
|
|
400
|
+
if (options.htmlFile) {
|
|
401
|
+
const fs2 = await import("fs");
|
|
402
|
+
request.html_content = fs2.readFileSync(options.htmlFile, "utf-8");
|
|
403
|
+
} else if (options.html) {
|
|
404
|
+
request.html_content = options.html;
|
|
405
|
+
}
|
|
406
|
+
if (options.textFile) {
|
|
407
|
+
const fs2 = await import("fs");
|
|
408
|
+
request.text_content = fs2.readFileSync(options.textFile, "utf-8");
|
|
409
|
+
} else if (options.text) {
|
|
410
|
+
request.text_content = options.text;
|
|
411
|
+
}
|
|
412
|
+
if (options.from) request.from_email = options.from;
|
|
413
|
+
if (options.replyTo) request.reply_to = options.replyTo;
|
|
414
|
+
if (options.previewText) request.preview_text = options.previewText;
|
|
415
|
+
const template = await client.templates.create(request);
|
|
416
|
+
if (options.format === "json") {
|
|
417
|
+
output(template, "json");
|
|
418
|
+
} else {
|
|
419
|
+
success(`Template created: ${template.id}`);
|
|
420
|
+
output({
|
|
421
|
+
ID: template.id,
|
|
422
|
+
Name: template.name,
|
|
423
|
+
Subject: template.subject,
|
|
424
|
+
Variables: template.variables?.join(", ") || "-"
|
|
425
|
+
}, "table");
|
|
426
|
+
}
|
|
427
|
+
} catch (err) {
|
|
428
|
+
error(err instanceof Error ? err.message : "Failed to create template");
|
|
429
|
+
process.exit(1);
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
cmd.command("update <id>").description("Update a template").option("--name <name>", "Template name").option("--subject <subject>", "Email subject").option("--html <html>", "HTML content").option("--text <text>", "Plain text content").option("--html-file <path>", "Read HTML content from file").option("--text-file <path>", "Read text content from file").option("--from <email>", "Default from email").option("--reply-to <email>", "Default reply-to email").option("--preview-text <text>", "Preview text").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
433
|
+
try {
|
|
434
|
+
const config = loadConfig();
|
|
435
|
+
const client = new Emailr3({
|
|
436
|
+
apiKey: config.apiKey,
|
|
437
|
+
baseUrl: config.baseUrl
|
|
438
|
+
});
|
|
439
|
+
const request = {};
|
|
440
|
+
if (options.name) request.name = options.name;
|
|
441
|
+
if (options.subject) request.subject = options.subject;
|
|
442
|
+
if (options.htmlFile) {
|
|
443
|
+
const fs2 = await import("fs");
|
|
444
|
+
request.html_content = fs2.readFileSync(options.htmlFile, "utf-8");
|
|
445
|
+
} else if (options.html) {
|
|
446
|
+
request.html_content = options.html;
|
|
447
|
+
}
|
|
448
|
+
if (options.textFile) {
|
|
449
|
+
const fs2 = await import("fs");
|
|
450
|
+
request.text_content = fs2.readFileSync(options.textFile, "utf-8");
|
|
451
|
+
} else if (options.text) {
|
|
452
|
+
request.text_content = options.text;
|
|
453
|
+
}
|
|
454
|
+
if (options.from) request.from_email = options.from;
|
|
455
|
+
if (options.replyTo) request.reply_to = options.replyTo;
|
|
456
|
+
if (options.previewText) request.preview_text = options.previewText;
|
|
457
|
+
const template = await client.templates.update(id, request);
|
|
458
|
+
if (options.format === "json") {
|
|
459
|
+
output(template, "json");
|
|
460
|
+
} else {
|
|
461
|
+
success(`Template updated: ${template.id}`);
|
|
462
|
+
output(template, "table");
|
|
463
|
+
}
|
|
464
|
+
} catch (err) {
|
|
465
|
+
error(err instanceof Error ? err.message : "Failed to update template");
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
cmd.command("delete <id>").description("Delete a template").action(async (id) => {
|
|
470
|
+
try {
|
|
471
|
+
const config = loadConfig();
|
|
472
|
+
const client = new Emailr3({
|
|
473
|
+
apiKey: config.apiKey,
|
|
474
|
+
baseUrl: config.baseUrl
|
|
475
|
+
});
|
|
476
|
+
await client.templates.delete(id);
|
|
477
|
+
success(`Template deleted: ${id}`);
|
|
478
|
+
} catch (err) {
|
|
479
|
+
error(err instanceof Error ? err.message : "Failed to delete template");
|
|
480
|
+
process.exit(1);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
return cmd;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/commands/domains.ts
|
|
487
|
+
import { Command as Command4 } from "commander";
|
|
488
|
+
import { Emailr as Emailr4 } from "@emailr/sdk";
|
|
489
|
+
function createDomainsCommand() {
|
|
490
|
+
const cmd = new Command4("domains").description("Manage sending domains");
|
|
491
|
+
cmd.command("list").description("List all domains").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
492
|
+
try {
|
|
493
|
+
const config = loadConfig();
|
|
494
|
+
const client = new Emailr4({
|
|
495
|
+
apiKey: config.apiKey,
|
|
496
|
+
baseUrl: config.baseUrl
|
|
497
|
+
});
|
|
498
|
+
const domains = await client.domains.list();
|
|
499
|
+
if (options.format === "json") {
|
|
500
|
+
output(domains, "json");
|
|
501
|
+
} else {
|
|
502
|
+
const formatted = domains.map((d) => ({
|
|
503
|
+
ID: d.id,
|
|
504
|
+
Domain: d.domain,
|
|
505
|
+
Status: d.status,
|
|
506
|
+
DKIM: d.dkim_verified,
|
|
507
|
+
SPF: d.spf_verified,
|
|
508
|
+
DMARC: d.dmarc_verified,
|
|
509
|
+
Created: d.created_at
|
|
510
|
+
}));
|
|
511
|
+
output(formatted, "table");
|
|
512
|
+
}
|
|
513
|
+
} catch (err) {
|
|
514
|
+
error(err instanceof Error ? err.message : "Failed to list domains");
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
cmd.command("get <id>").description("Get a domain by ID").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
519
|
+
try {
|
|
520
|
+
const config = loadConfig();
|
|
521
|
+
const client = new Emailr4({
|
|
522
|
+
apiKey: config.apiKey,
|
|
523
|
+
baseUrl: config.baseUrl
|
|
524
|
+
});
|
|
525
|
+
const domain = await client.domains.get(id);
|
|
526
|
+
output(domain, options.format);
|
|
527
|
+
} catch (err) {
|
|
528
|
+
error(err instanceof Error ? err.message : "Failed to get domain");
|
|
529
|
+
process.exit(1);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
cmd.command("add <domain>").description("Add a new domain").option("--receiving-subdomain <subdomain>", "Subdomain for receiving emails").option("--format <format>", "Output format (json|table)", "table").action(async (domainName, options) => {
|
|
533
|
+
try {
|
|
534
|
+
const config = loadConfig();
|
|
535
|
+
const client = new Emailr4({
|
|
536
|
+
apiKey: config.apiKey,
|
|
537
|
+
baseUrl: config.baseUrl
|
|
538
|
+
});
|
|
539
|
+
const request = {
|
|
540
|
+
domain: domainName
|
|
541
|
+
};
|
|
542
|
+
if (options.receivingSubdomain) {
|
|
543
|
+
request.receivingSubdomain = options.receivingSubdomain;
|
|
544
|
+
}
|
|
545
|
+
const domain = await client.domains.add(request);
|
|
546
|
+
if (options.format === "json") {
|
|
547
|
+
output(domain, "json");
|
|
548
|
+
} else {
|
|
549
|
+
success(`Domain added: ${domain.domain}`);
|
|
550
|
+
info("Add the following DNS records to verify your domain:");
|
|
551
|
+
console.log("");
|
|
552
|
+
if (domain.dns_records) {
|
|
553
|
+
const records = [
|
|
554
|
+
{ Type: "DKIM", ...formatDnsRecord(domain.dns_records.dkim) },
|
|
555
|
+
{ Type: "SPF", ...formatDnsRecord(domain.dns_records.spf) },
|
|
556
|
+
{ Type: "DMARC", ...formatDnsRecord(domain.dns_records.dmarc) }
|
|
557
|
+
];
|
|
558
|
+
output(records, "table");
|
|
559
|
+
}
|
|
560
|
+
console.log("");
|
|
561
|
+
info(`Run 'emailr domains verify ${domain.id}' after adding DNS records`);
|
|
562
|
+
}
|
|
563
|
+
} catch (err) {
|
|
564
|
+
error(err instanceof Error ? err.message : "Failed to add domain");
|
|
565
|
+
process.exit(1);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
cmd.command("verify <id>").description("Verify a domain").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
569
|
+
try {
|
|
570
|
+
const config = loadConfig();
|
|
571
|
+
const client = new Emailr4({
|
|
572
|
+
apiKey: config.apiKey,
|
|
573
|
+
baseUrl: config.baseUrl
|
|
574
|
+
});
|
|
575
|
+
const result = await client.domains.verify(id);
|
|
576
|
+
if (options.format === "json") {
|
|
577
|
+
output(result, "json");
|
|
578
|
+
} else {
|
|
579
|
+
if (result.verified) {
|
|
580
|
+
success("Domain verified successfully!");
|
|
581
|
+
} else {
|
|
582
|
+
info(`Domain status: ${result.status}`);
|
|
583
|
+
if (result.dkim_status) {
|
|
584
|
+
info(`DKIM status: ${result.dkim_status}`);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
} catch (err) {
|
|
589
|
+
error(err instanceof Error ? err.message : "Failed to verify domain");
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
cmd.command("check-dns <id>").description("Check DNS records for a domain").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
594
|
+
try {
|
|
595
|
+
const config = loadConfig();
|
|
596
|
+
const client = new Emailr4({
|
|
597
|
+
apiKey: config.apiKey,
|
|
598
|
+
baseUrl: config.baseUrl
|
|
599
|
+
});
|
|
600
|
+
const result = await client.domains.checkDns(id);
|
|
601
|
+
if (options.format === "json") {
|
|
602
|
+
output(result, "json");
|
|
603
|
+
} else {
|
|
604
|
+
const records = Object.entries(result).map(([name, status]) => ({
|
|
605
|
+
Record: name,
|
|
606
|
+
Verified: status.verified,
|
|
607
|
+
Expected: status.expected || "-",
|
|
608
|
+
Found: status.found?.join(", ") || "-"
|
|
609
|
+
}));
|
|
610
|
+
output(records, "table");
|
|
611
|
+
}
|
|
612
|
+
} catch (err) {
|
|
613
|
+
error(err instanceof Error ? err.message : "Failed to check DNS");
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
cmd.command("delete <id>").description("Delete a domain").action(async (id) => {
|
|
618
|
+
try {
|
|
619
|
+
const config = loadConfig();
|
|
620
|
+
const client = new Emailr4({
|
|
621
|
+
apiKey: config.apiKey,
|
|
622
|
+
baseUrl: config.baseUrl
|
|
623
|
+
});
|
|
624
|
+
await client.domains.delete(id);
|
|
625
|
+
success(`Domain deleted: ${id}`);
|
|
626
|
+
} catch (err) {
|
|
627
|
+
error(err instanceof Error ? err.message : "Failed to delete domain");
|
|
628
|
+
process.exit(1);
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
return cmd;
|
|
632
|
+
}
|
|
633
|
+
function formatDnsRecord(record) {
|
|
634
|
+
return {
|
|
635
|
+
"Record Type": record.type,
|
|
636
|
+
Name: record.name,
|
|
637
|
+
Value: record.value.length > 50 ? record.value.substring(0, 47) + "..." : record.value,
|
|
638
|
+
...record.priority !== void 0 && { Priority: record.priority }
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// src/commands/config.ts
|
|
643
|
+
import { Command as Command5 } from "commander";
|
|
644
|
+
function createConfigCommand() {
|
|
645
|
+
const cmd = new Command5("config").description("Manage CLI configuration");
|
|
646
|
+
cmd.command("set <key> <value>").description("Set a configuration value").action(async (key, value) => {
|
|
647
|
+
try {
|
|
648
|
+
const validKeys = ["api-key", "base-url", "format"];
|
|
649
|
+
const normalizedKey = key.toLowerCase();
|
|
650
|
+
if (!validKeys.includes(normalizedKey)) {
|
|
651
|
+
error(`Invalid config key: ${key}`);
|
|
652
|
+
info(`Valid keys: ${validKeys.join(", ")}`);
|
|
653
|
+
process.exit(1);
|
|
654
|
+
}
|
|
655
|
+
const keyMap = {
|
|
656
|
+
"api-key": "apiKey",
|
|
657
|
+
"base-url": "baseUrl",
|
|
658
|
+
"format": "format"
|
|
659
|
+
};
|
|
660
|
+
const configKey = keyMap[normalizedKey];
|
|
661
|
+
if (normalizedKey === "format" && !["json", "table"].includes(value)) {
|
|
662
|
+
error(`Invalid format value: ${value}`);
|
|
663
|
+
info("Valid formats: json, table");
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
saveConfig({ [configKey]: value });
|
|
667
|
+
success(`Configuration saved: ${key} = ${normalizedKey === "api-key" ? "***" : value}`);
|
|
668
|
+
info(`Config file: ${getConfigPath()}`);
|
|
669
|
+
} catch (err) {
|
|
670
|
+
error(err instanceof Error ? err.message : "Failed to save configuration");
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
cmd.command("get <key>").description("Get a configuration value").action(async (key) => {
|
|
675
|
+
try {
|
|
676
|
+
const validKeys = ["api-key", "base-url", "format"];
|
|
677
|
+
const normalizedKey = key.toLowerCase();
|
|
678
|
+
if (!validKeys.includes(normalizedKey)) {
|
|
679
|
+
error(`Invalid config key: ${key}`);
|
|
680
|
+
info(`Valid keys: ${validKeys.join(", ")}`);
|
|
681
|
+
process.exit(1);
|
|
682
|
+
}
|
|
683
|
+
const keyMap = {
|
|
684
|
+
"api-key": "apiKey",
|
|
685
|
+
"base-url": "baseUrl",
|
|
686
|
+
"format": "format"
|
|
687
|
+
};
|
|
688
|
+
const configKey = keyMap[normalizedKey];
|
|
689
|
+
const value = getConfigValue(configKey);
|
|
690
|
+
if (value) {
|
|
691
|
+
if (normalizedKey === "api-key") {
|
|
692
|
+
console.log(value.substring(0, 8) + "..." + value.substring(value.length - 4));
|
|
693
|
+
} else {
|
|
694
|
+
console.log(value);
|
|
695
|
+
}
|
|
696
|
+
} else {
|
|
697
|
+
info(`${key} is not set`);
|
|
698
|
+
}
|
|
699
|
+
} catch (err) {
|
|
700
|
+
error(err instanceof Error ? err.message : "Failed to get configuration");
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
cmd.command("list").description("List all configuration values").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
705
|
+
try {
|
|
706
|
+
const config = loadConfig();
|
|
707
|
+
const configData = {
|
|
708
|
+
"api-key": config.apiKey ? config.apiKey.substring(0, 8) + "..." + config.apiKey.substring(config.apiKey.length - 4) : "(not set)",
|
|
709
|
+
"base-url": config.baseUrl || "(default)",
|
|
710
|
+
"format": config.format || "table"
|
|
711
|
+
};
|
|
712
|
+
if (options.format === "json") {
|
|
713
|
+
output(configData, "json");
|
|
714
|
+
} else {
|
|
715
|
+
const rows = Object.entries(configData).map(([key, value]) => ({
|
|
716
|
+
Key: key,
|
|
717
|
+
Value: value
|
|
718
|
+
}));
|
|
719
|
+
output(rows, "table");
|
|
720
|
+
}
|
|
721
|
+
console.log("");
|
|
722
|
+
info(`Config file: ${getConfigPath()}`);
|
|
723
|
+
} catch (err) {
|
|
724
|
+
if (err instanceof Error && err.message.includes("No API key configured")) {
|
|
725
|
+
info("No configuration found.");
|
|
726
|
+
info(`Run 'emailr config set api-key <your-api-key>' to get started.`);
|
|
727
|
+
} else {
|
|
728
|
+
error(err instanceof Error ? err.message : "Failed to list configuration");
|
|
729
|
+
process.exit(1);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
cmd.command("path").description("Show the configuration file path").action(() => {
|
|
734
|
+
console.log(getConfigPath());
|
|
735
|
+
});
|
|
736
|
+
cmd.command("init").description("Initialize configuration interactively").option("--api-key <key>", "API key to use").option("--base-url <url>", "Base URL for API").action(async (options) => {
|
|
737
|
+
try {
|
|
738
|
+
if (options.apiKey) {
|
|
739
|
+
saveConfig({
|
|
740
|
+
apiKey: options.apiKey,
|
|
741
|
+
baseUrl: options.baseUrl
|
|
742
|
+
});
|
|
743
|
+
success("Configuration initialized!");
|
|
744
|
+
info(`Config file: ${getConfigPath()}`);
|
|
745
|
+
} else {
|
|
746
|
+
info("Initialize your Emailr CLI configuration:");
|
|
747
|
+
console.log("");
|
|
748
|
+
info("Run with --api-key flag:");
|
|
749
|
+
console.log(" emailr config init --api-key <your-api-key>");
|
|
750
|
+
console.log("");
|
|
751
|
+
info("Or set environment variable:");
|
|
752
|
+
console.log(" export EMAILR_API_KEY=<your-api-key>");
|
|
753
|
+
}
|
|
754
|
+
} catch (err) {
|
|
755
|
+
error(err instanceof Error ? err.message : "Failed to initialize configuration");
|
|
756
|
+
process.exit(1);
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
return cmd;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/commands/broadcasts.ts
|
|
763
|
+
import { Command as Command6 } from "commander";
|
|
764
|
+
import { Emailr as Emailr5 } from "@emailr/sdk";
|
|
765
|
+
function createBroadcastsCommand() {
|
|
766
|
+
const cmd = new Command6("broadcasts").description("Manage broadcast campaigns");
|
|
767
|
+
cmd.command("list").description("List all broadcasts").option("--status <status>", "Filter by status (draft, scheduled, sending, sent)").option("--limit <number>", "Number of broadcasts to return", "20").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
768
|
+
try {
|
|
769
|
+
const config = loadConfig();
|
|
770
|
+
const client = new Emailr5({
|
|
771
|
+
apiKey: config.apiKey,
|
|
772
|
+
baseUrl: config.baseUrl
|
|
773
|
+
});
|
|
774
|
+
const broadcasts = await client.broadcasts.list({
|
|
775
|
+
status: options.status,
|
|
776
|
+
limit: parseInt(options.limit)
|
|
777
|
+
});
|
|
778
|
+
if (options.format === "json") {
|
|
779
|
+
output(broadcasts, "json");
|
|
780
|
+
} else {
|
|
781
|
+
if (broadcasts.length === 0) {
|
|
782
|
+
console.log("No broadcasts found.");
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const tableData = broadcasts.map((b) => ({
|
|
786
|
+
ID: b.id,
|
|
787
|
+
Name: b.name,
|
|
788
|
+
Subject: b.subject.substring(0, 30) + (b.subject.length > 30 ? "..." : ""),
|
|
789
|
+
Status: b.status,
|
|
790
|
+
Recipients: b.total_recipients || 0,
|
|
791
|
+
"Sent": b.sent_count || 0,
|
|
792
|
+
"Created": new Date(b.created_at).toLocaleDateString()
|
|
793
|
+
}));
|
|
794
|
+
output(tableData, "table");
|
|
795
|
+
}
|
|
796
|
+
} catch (err) {
|
|
797
|
+
error(err instanceof Error ? err.message : "Failed to list broadcasts");
|
|
798
|
+
process.exit(1);
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
cmd.command("get <id>").description("Get broadcast details").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
802
|
+
try {
|
|
803
|
+
const config = loadConfig();
|
|
804
|
+
const client = new Emailr5({
|
|
805
|
+
apiKey: config.apiKey,
|
|
806
|
+
baseUrl: config.baseUrl
|
|
807
|
+
});
|
|
808
|
+
const broadcast = await client.broadcasts.get(id);
|
|
809
|
+
if (options.format === "json") {
|
|
810
|
+
output(broadcast, "json");
|
|
811
|
+
} else {
|
|
812
|
+
output({
|
|
813
|
+
ID: broadcast.id,
|
|
814
|
+
Name: broadcast.name,
|
|
815
|
+
Subject: broadcast.subject,
|
|
816
|
+
"From Email": broadcast.from_email,
|
|
817
|
+
Status: broadcast.status,
|
|
818
|
+
"Total Recipients": broadcast.total_recipients || 0,
|
|
819
|
+
"Sent Count": broadcast.sent_count || 0,
|
|
820
|
+
"Delivered": broadcast.delivered_count || 0,
|
|
821
|
+
"Opened": broadcast.opened_count || 0,
|
|
822
|
+
"Clicked": broadcast.clicked_count || 0,
|
|
823
|
+
"Bounced": broadcast.bounced_count || 0,
|
|
824
|
+
"Scheduled At": broadcast.scheduled_at || "N/A",
|
|
825
|
+
"Started At": broadcast.started_at || "N/A",
|
|
826
|
+
"Completed At": broadcast.completed_at || "N/A",
|
|
827
|
+
"Created At": broadcast.created_at
|
|
828
|
+
}, "table");
|
|
829
|
+
}
|
|
830
|
+
} catch (err) {
|
|
831
|
+
error(err instanceof Error ? err.message : "Failed to get broadcast");
|
|
832
|
+
process.exit(1);
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
cmd.command("create").description("Create a new broadcast").requiredOption("--name <name>", "Broadcast name").requiredOption("--subject <subject>", "Email subject").requiredOption("--from <email>", "Sender email address").option("--template <id>", "Template ID to use").option("--segment <id>", "Segment ID to target").option("--html <html>", "HTML content").option("--text <text>", "Plain text content").option("--schedule <datetime>", "Schedule time (ISO 8601)").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
836
|
+
try {
|
|
837
|
+
const config = loadConfig();
|
|
838
|
+
const client = new Emailr5({
|
|
839
|
+
apiKey: config.apiKey,
|
|
840
|
+
baseUrl: config.baseUrl
|
|
841
|
+
});
|
|
842
|
+
const broadcast = await client.broadcasts.create({
|
|
843
|
+
name: options.name,
|
|
844
|
+
subject: options.subject,
|
|
845
|
+
from_email: options.from,
|
|
846
|
+
template_id: options.template,
|
|
847
|
+
segment_id: options.segment,
|
|
848
|
+
html_content: options.html,
|
|
849
|
+
text_content: options.text,
|
|
850
|
+
scheduled_at: options.schedule
|
|
851
|
+
});
|
|
852
|
+
if (options.format === "json") {
|
|
853
|
+
output(broadcast, "json");
|
|
854
|
+
} else {
|
|
855
|
+
success("Broadcast created successfully!");
|
|
856
|
+
output({
|
|
857
|
+
ID: broadcast.id,
|
|
858
|
+
Name: broadcast.name,
|
|
859
|
+
Status: broadcast.status,
|
|
860
|
+
"Scheduled At": broadcast.scheduled_at || "Not scheduled"
|
|
861
|
+
}, "table");
|
|
862
|
+
}
|
|
863
|
+
} catch (err) {
|
|
864
|
+
error(err instanceof Error ? err.message : "Failed to create broadcast");
|
|
865
|
+
process.exit(1);
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
cmd.command("send <id>").description("Send a broadcast immediately").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
869
|
+
try {
|
|
870
|
+
const config = loadConfig();
|
|
871
|
+
const client = new Emailr5({
|
|
872
|
+
apiKey: config.apiKey,
|
|
873
|
+
baseUrl: config.baseUrl
|
|
874
|
+
});
|
|
875
|
+
const result = await client.broadcasts.send(id);
|
|
876
|
+
if (options.format === "json") {
|
|
877
|
+
output(result, "json");
|
|
878
|
+
} else {
|
|
879
|
+
success("Broadcast sent successfully!");
|
|
880
|
+
output({
|
|
881
|
+
Success: result.success,
|
|
882
|
+
Sent: result.sent,
|
|
883
|
+
Total: result.total
|
|
884
|
+
}, "table");
|
|
885
|
+
}
|
|
886
|
+
} catch (err) {
|
|
887
|
+
error(err instanceof Error ? err.message : "Failed to send broadcast");
|
|
888
|
+
process.exit(1);
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
cmd.command("schedule <id>").description("Schedule a broadcast").requiredOption("--at <datetime>", "Schedule time (ISO 8601)").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
892
|
+
try {
|
|
893
|
+
const config = loadConfig();
|
|
894
|
+
const client = new Emailr5({
|
|
895
|
+
apiKey: config.apiKey,
|
|
896
|
+
baseUrl: config.baseUrl
|
|
897
|
+
});
|
|
898
|
+
const broadcast = await client.broadcasts.schedule(id, options.at);
|
|
899
|
+
if (options.format === "json") {
|
|
900
|
+
output(broadcast, "json");
|
|
901
|
+
} else {
|
|
902
|
+
success("Broadcast scheduled successfully!");
|
|
903
|
+
output({
|
|
904
|
+
ID: broadcast.id,
|
|
905
|
+
Name: broadcast.name,
|
|
906
|
+
Status: broadcast.status,
|
|
907
|
+
"Scheduled At": broadcast.scheduled_at
|
|
908
|
+
}, "table");
|
|
909
|
+
}
|
|
910
|
+
} catch (err) {
|
|
911
|
+
error(err instanceof Error ? err.message : "Failed to schedule broadcast");
|
|
912
|
+
process.exit(1);
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
cmd.command("cancel <id>").description("Cancel a scheduled broadcast").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
916
|
+
try {
|
|
917
|
+
const config = loadConfig();
|
|
918
|
+
const client = new Emailr5({
|
|
919
|
+
apiKey: config.apiKey,
|
|
920
|
+
baseUrl: config.baseUrl
|
|
921
|
+
});
|
|
922
|
+
const result = await client.broadcasts.cancel(id);
|
|
923
|
+
if (options.format === "json") {
|
|
924
|
+
output(result, "json");
|
|
925
|
+
} else {
|
|
926
|
+
success("Broadcast cancelled successfully!");
|
|
927
|
+
}
|
|
928
|
+
} catch (err) {
|
|
929
|
+
error(err instanceof Error ? err.message : "Failed to cancel broadcast");
|
|
930
|
+
process.exit(1);
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
cmd.command("delete <id>").description("Delete a broadcast").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
934
|
+
try {
|
|
935
|
+
const config = loadConfig();
|
|
936
|
+
const client = new Emailr5({
|
|
937
|
+
apiKey: config.apiKey,
|
|
938
|
+
baseUrl: config.baseUrl
|
|
939
|
+
});
|
|
940
|
+
const result = await client.broadcasts.delete(id);
|
|
941
|
+
if (options.format === "json") {
|
|
942
|
+
output(result, "json");
|
|
943
|
+
} else {
|
|
944
|
+
success("Broadcast deleted successfully!");
|
|
945
|
+
}
|
|
946
|
+
} catch (err) {
|
|
947
|
+
error(err instanceof Error ? err.message : "Failed to delete broadcast");
|
|
948
|
+
process.exit(1);
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
return cmd;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/commands/webhooks.ts
|
|
955
|
+
import { Command as Command7 } from "commander";
|
|
956
|
+
import { Emailr as Emailr6 } from "@emailr/sdk";
|
|
957
|
+
function createWebhooksCommand() {
|
|
958
|
+
const cmd = new Command7("webhooks").description("Manage webhooks");
|
|
959
|
+
cmd.command("list").description("List all webhooks").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
960
|
+
try {
|
|
961
|
+
const config = loadConfig();
|
|
962
|
+
const client = new Emailr6({
|
|
963
|
+
apiKey: config.apiKey,
|
|
964
|
+
baseUrl: config.baseUrl
|
|
965
|
+
});
|
|
966
|
+
const webhooks = await client.webhooks.list();
|
|
967
|
+
if (options.format === "json") {
|
|
968
|
+
output(webhooks, "json");
|
|
969
|
+
} else {
|
|
970
|
+
if (webhooks.length === 0) {
|
|
971
|
+
console.log("No webhooks found.");
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
const tableData = webhooks.map((w) => ({
|
|
975
|
+
ID: w.id,
|
|
976
|
+
Name: w.name,
|
|
977
|
+
URL: w.url.substring(0, 40) + (w.url.length > 40 ? "..." : ""),
|
|
978
|
+
Events: w.events.join(", "),
|
|
979
|
+
Active: w.active ? "Yes" : "No"
|
|
980
|
+
}));
|
|
981
|
+
output(tableData, "table");
|
|
982
|
+
}
|
|
983
|
+
} catch (err) {
|
|
984
|
+
error(err instanceof Error ? err.message : "Failed to list webhooks");
|
|
985
|
+
process.exit(1);
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
cmd.command("get <id>").description("Get webhook details").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
989
|
+
try {
|
|
990
|
+
const config = loadConfig();
|
|
991
|
+
const client = new Emailr6({
|
|
992
|
+
apiKey: config.apiKey,
|
|
993
|
+
baseUrl: config.baseUrl
|
|
994
|
+
});
|
|
995
|
+
const webhook = await client.webhooks.get(id);
|
|
996
|
+
if (options.format === "json") {
|
|
997
|
+
output(webhook, "json");
|
|
998
|
+
} else {
|
|
999
|
+
output({
|
|
1000
|
+
ID: webhook.id,
|
|
1001
|
+
Name: webhook.name,
|
|
1002
|
+
URL: webhook.url,
|
|
1003
|
+
Events: webhook.events.join(", "),
|
|
1004
|
+
Active: webhook.active ? "Yes" : "No",
|
|
1005
|
+
Secret: webhook.secret,
|
|
1006
|
+
"Created At": webhook.created_at
|
|
1007
|
+
}, "table");
|
|
1008
|
+
}
|
|
1009
|
+
} catch (err) {
|
|
1010
|
+
error(err instanceof Error ? err.message : "Failed to get webhook");
|
|
1011
|
+
process.exit(1);
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
cmd.command("create").description("Create a new webhook").requiredOption("--name <name>", "Webhook name").requiredOption("--url <url>", "Webhook URL").requiredOption("--events <events>", "Events to subscribe to (comma-separated)").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
1015
|
+
try {
|
|
1016
|
+
const config = loadConfig();
|
|
1017
|
+
const client = new Emailr6({
|
|
1018
|
+
apiKey: config.apiKey,
|
|
1019
|
+
baseUrl: config.baseUrl
|
|
1020
|
+
});
|
|
1021
|
+
const events = options.events.split(",").map((e) => e.trim());
|
|
1022
|
+
const webhook = await client.webhooks.create({
|
|
1023
|
+
name: options.name,
|
|
1024
|
+
url: options.url,
|
|
1025
|
+
events
|
|
1026
|
+
});
|
|
1027
|
+
if (options.format === "json") {
|
|
1028
|
+
output(webhook, "json");
|
|
1029
|
+
} else {
|
|
1030
|
+
success("Webhook created successfully!");
|
|
1031
|
+
output({
|
|
1032
|
+
ID: webhook.id,
|
|
1033
|
+
Name: webhook.name,
|
|
1034
|
+
URL: webhook.url,
|
|
1035
|
+
Secret: webhook.secret
|
|
1036
|
+
}, "table");
|
|
1037
|
+
}
|
|
1038
|
+
} catch (err) {
|
|
1039
|
+
error(err instanceof Error ? err.message : "Failed to create webhook");
|
|
1040
|
+
process.exit(1);
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
cmd.command("update <id>").description("Update a webhook").option("--name <name>", "New webhook name").option("--url <url>", "New webhook URL").option("--events <events>", "New events (comma-separated)").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1044
|
+
try {
|
|
1045
|
+
const config = loadConfig();
|
|
1046
|
+
const client = new Emailr6({
|
|
1047
|
+
apiKey: config.apiKey,
|
|
1048
|
+
baseUrl: config.baseUrl
|
|
1049
|
+
});
|
|
1050
|
+
const updateData = {};
|
|
1051
|
+
if (options.name) updateData.name = options.name;
|
|
1052
|
+
if (options.url) updateData.url = options.url;
|
|
1053
|
+
if (options.events) {
|
|
1054
|
+
updateData.events = options.events.split(",").map((e) => e.trim());
|
|
1055
|
+
}
|
|
1056
|
+
const webhook = await client.webhooks.update(id, updateData);
|
|
1057
|
+
if (options.format === "json") {
|
|
1058
|
+
output(webhook, "json");
|
|
1059
|
+
} else {
|
|
1060
|
+
success("Webhook updated successfully!");
|
|
1061
|
+
output({
|
|
1062
|
+
ID: webhook.id,
|
|
1063
|
+
Name: webhook.name,
|
|
1064
|
+
URL: webhook.url
|
|
1065
|
+
}, "table");
|
|
1066
|
+
}
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
error(err instanceof Error ? err.message : "Failed to update webhook");
|
|
1069
|
+
process.exit(1);
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
cmd.command("enable <id>").description("Enable a webhook").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1073
|
+
try {
|
|
1074
|
+
const config = loadConfig();
|
|
1075
|
+
const client = new Emailr6({
|
|
1076
|
+
apiKey: config.apiKey,
|
|
1077
|
+
baseUrl: config.baseUrl
|
|
1078
|
+
});
|
|
1079
|
+
const result = await client.webhooks.enable(id);
|
|
1080
|
+
if (options.format === "json") {
|
|
1081
|
+
output(result, "json");
|
|
1082
|
+
} else {
|
|
1083
|
+
success("Webhook enabled successfully!");
|
|
1084
|
+
}
|
|
1085
|
+
} catch (err) {
|
|
1086
|
+
error(err instanceof Error ? err.message : "Failed to enable webhook");
|
|
1087
|
+
process.exit(1);
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
cmd.command("disable <id>").description("Disable a webhook").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1091
|
+
try {
|
|
1092
|
+
const config = loadConfig();
|
|
1093
|
+
const client = new Emailr6({
|
|
1094
|
+
apiKey: config.apiKey,
|
|
1095
|
+
baseUrl: config.baseUrl
|
|
1096
|
+
});
|
|
1097
|
+
const result = await client.webhooks.disable(id);
|
|
1098
|
+
if (options.format === "json") {
|
|
1099
|
+
output(result, "json");
|
|
1100
|
+
} else {
|
|
1101
|
+
success("Webhook disabled successfully!");
|
|
1102
|
+
}
|
|
1103
|
+
} catch (err) {
|
|
1104
|
+
error(err instanceof Error ? err.message : "Failed to disable webhook");
|
|
1105
|
+
process.exit(1);
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
cmd.command("delete <id>").description("Delete a webhook").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1109
|
+
try {
|
|
1110
|
+
const config = loadConfig();
|
|
1111
|
+
const client = new Emailr6({
|
|
1112
|
+
apiKey: config.apiKey,
|
|
1113
|
+
baseUrl: config.baseUrl
|
|
1114
|
+
});
|
|
1115
|
+
const result = await client.webhooks.delete(id);
|
|
1116
|
+
if (options.format === "json") {
|
|
1117
|
+
output(result, "json");
|
|
1118
|
+
} else {
|
|
1119
|
+
success("Webhook deleted successfully!");
|
|
1120
|
+
}
|
|
1121
|
+
} catch (err) {
|
|
1122
|
+
error(err instanceof Error ? err.message : "Failed to delete webhook");
|
|
1123
|
+
process.exit(1);
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
return cmd;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// src/commands/segments.ts
|
|
1130
|
+
import { Command as Command8 } from "commander";
|
|
1131
|
+
import { Emailr as Emailr7 } from "@emailr/sdk";
|
|
1132
|
+
function createSegmentsCommand() {
|
|
1133
|
+
const cmd = new Command8("segments").description("Manage contact segments");
|
|
1134
|
+
cmd.command("list").description("List all segments").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
1135
|
+
try {
|
|
1136
|
+
const config = loadConfig();
|
|
1137
|
+
const client = new Emailr7({
|
|
1138
|
+
apiKey: config.apiKey,
|
|
1139
|
+
baseUrl: config.baseUrl
|
|
1140
|
+
});
|
|
1141
|
+
const segments = await client.segments.list();
|
|
1142
|
+
if (options.format === "json") {
|
|
1143
|
+
output(segments, "json");
|
|
1144
|
+
} else {
|
|
1145
|
+
if (segments.length === 0) {
|
|
1146
|
+
console.log("No segments found.");
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
const tableData = segments.map((s) => ({
|
|
1150
|
+
ID: s.id,
|
|
1151
|
+
Name: s.name,
|
|
1152
|
+
Description: (s.description || "").substring(0, 30) + ((s.description?.length || 0) > 30 ? "..." : ""),
|
|
1153
|
+
"Created At": new Date(s.created_at).toLocaleDateString()
|
|
1154
|
+
}));
|
|
1155
|
+
output(tableData, "table");
|
|
1156
|
+
}
|
|
1157
|
+
} catch (err) {
|
|
1158
|
+
error(err instanceof Error ? err.message : "Failed to list segments");
|
|
1159
|
+
process.exit(1);
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1162
|
+
cmd.command("get <id>").description("Get segment details").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1163
|
+
try {
|
|
1164
|
+
const config = loadConfig();
|
|
1165
|
+
const client = new Emailr7({
|
|
1166
|
+
apiKey: config.apiKey,
|
|
1167
|
+
baseUrl: config.baseUrl
|
|
1168
|
+
});
|
|
1169
|
+
const segment = await client.segments.get(id);
|
|
1170
|
+
if (options.format === "json") {
|
|
1171
|
+
output(segment, "json");
|
|
1172
|
+
} else {
|
|
1173
|
+
output({
|
|
1174
|
+
ID: segment.id,
|
|
1175
|
+
Name: segment.name,
|
|
1176
|
+
Description: segment.description || "N/A",
|
|
1177
|
+
Conditions: JSON.stringify(segment.conditions),
|
|
1178
|
+
"Created At": segment.created_at,
|
|
1179
|
+
"Updated At": segment.updated_at
|
|
1180
|
+
}, "table");
|
|
1181
|
+
}
|
|
1182
|
+
} catch (err) {
|
|
1183
|
+
error(err instanceof Error ? err.message : "Failed to get segment");
|
|
1184
|
+
process.exit(1);
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
cmd.command("create").description("Create a new segment").requiredOption("--name <name>", "Segment name").requiredOption("--conditions <json>", "Segment conditions as JSON").option("--description <description>", "Segment description").option("--format <format>", "Output format (json|table)", "table").action(async (options) => {
|
|
1188
|
+
try {
|
|
1189
|
+
const config = loadConfig();
|
|
1190
|
+
const client = new Emailr7({
|
|
1191
|
+
apiKey: config.apiKey,
|
|
1192
|
+
baseUrl: config.baseUrl
|
|
1193
|
+
});
|
|
1194
|
+
let conditions;
|
|
1195
|
+
try {
|
|
1196
|
+
conditions = JSON.parse(options.conditions);
|
|
1197
|
+
} catch {
|
|
1198
|
+
error("Invalid JSON for --conditions");
|
|
1199
|
+
process.exit(1);
|
|
1200
|
+
}
|
|
1201
|
+
const segment = await client.segments.create({
|
|
1202
|
+
name: options.name,
|
|
1203
|
+
description: options.description,
|
|
1204
|
+
conditions
|
|
1205
|
+
});
|
|
1206
|
+
if (options.format === "json") {
|
|
1207
|
+
output(segment, "json");
|
|
1208
|
+
} else {
|
|
1209
|
+
success("Segment created successfully!");
|
|
1210
|
+
output({
|
|
1211
|
+
ID: segment.id,
|
|
1212
|
+
Name: segment.name
|
|
1213
|
+
}, "table");
|
|
1214
|
+
}
|
|
1215
|
+
} catch (err) {
|
|
1216
|
+
error(err instanceof Error ? err.message : "Failed to create segment");
|
|
1217
|
+
process.exit(1);
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
cmd.command("update <id>").description("Update a segment").option("--name <name>", "New segment name").option("--description <description>", "New description").option("--conditions <json>", "New conditions as JSON").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1221
|
+
try {
|
|
1222
|
+
const config = loadConfig();
|
|
1223
|
+
const client = new Emailr7({
|
|
1224
|
+
apiKey: config.apiKey,
|
|
1225
|
+
baseUrl: config.baseUrl
|
|
1226
|
+
});
|
|
1227
|
+
const updateData = {};
|
|
1228
|
+
if (options.name) updateData.name = options.name;
|
|
1229
|
+
if (options.description) updateData.description = options.description;
|
|
1230
|
+
if (options.conditions) {
|
|
1231
|
+
try {
|
|
1232
|
+
updateData.conditions = JSON.parse(options.conditions);
|
|
1233
|
+
} catch {
|
|
1234
|
+
error("Invalid JSON for --conditions");
|
|
1235
|
+
process.exit(1);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
const segment = await client.segments.update(id, updateData);
|
|
1239
|
+
if (options.format === "json") {
|
|
1240
|
+
output(segment, "json");
|
|
1241
|
+
} else {
|
|
1242
|
+
success("Segment updated successfully!");
|
|
1243
|
+
output({
|
|
1244
|
+
ID: segment.id,
|
|
1245
|
+
Name: segment.name
|
|
1246
|
+
}, "table");
|
|
1247
|
+
}
|
|
1248
|
+
} catch (err) {
|
|
1249
|
+
error(err instanceof Error ? err.message : "Failed to update segment");
|
|
1250
|
+
process.exit(1);
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
cmd.command("delete <id>").description("Delete a segment").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1254
|
+
try {
|
|
1255
|
+
const config = loadConfig();
|
|
1256
|
+
const client = new Emailr7({
|
|
1257
|
+
apiKey: config.apiKey,
|
|
1258
|
+
baseUrl: config.baseUrl
|
|
1259
|
+
});
|
|
1260
|
+
const result = await client.segments.delete(id);
|
|
1261
|
+
if (options.format === "json") {
|
|
1262
|
+
output(result, "json");
|
|
1263
|
+
} else {
|
|
1264
|
+
success("Segment deleted successfully!");
|
|
1265
|
+
}
|
|
1266
|
+
} catch (err) {
|
|
1267
|
+
error(err instanceof Error ? err.message : "Failed to delete segment");
|
|
1268
|
+
process.exit(1);
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
cmd.command("count <id>").description("Get the number of contacts in a segment").option("--format <format>", "Output format (json|table)", "table").action(async (id, options) => {
|
|
1272
|
+
try {
|
|
1273
|
+
const config = loadConfig();
|
|
1274
|
+
const client = new Emailr7({
|
|
1275
|
+
apiKey: config.apiKey,
|
|
1276
|
+
baseUrl: config.baseUrl
|
|
1277
|
+
});
|
|
1278
|
+
const result = await client.segments.getContactsCount(id);
|
|
1279
|
+
if (options.format === "json") {
|
|
1280
|
+
output(result, "json");
|
|
1281
|
+
} else {
|
|
1282
|
+
console.log(`Segment contains ${result.count} contacts.`);
|
|
1283
|
+
}
|
|
1284
|
+
} catch (err) {
|
|
1285
|
+
error(err instanceof Error ? err.message : "Failed to get segment count");
|
|
1286
|
+
process.exit(1);
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
return cmd;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// src/index.ts
|
|
1293
|
+
var program = new Command9();
|
|
1294
|
+
program.name("emailr").description("Emailr CLI - Send emails and manage your email infrastructure").version("1.0.0");
|
|
1295
|
+
program.addCommand(createSendCommand());
|
|
1296
|
+
program.addCommand(createContactsCommand());
|
|
1297
|
+
program.addCommand(createTemplatesCommand());
|
|
1298
|
+
program.addCommand(createDomainsCommand());
|
|
1299
|
+
program.addCommand(createBroadcastsCommand());
|
|
1300
|
+
program.addCommand(createWebhooksCommand());
|
|
1301
|
+
program.addCommand(createSegmentsCommand());
|
|
1302
|
+
program.addCommand(createConfigCommand());
|
|
1303
|
+
program.parse();
|