@sesender/cli 1.0.0 → 1.0.3

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.
@@ -1,66 +1,123 @@
1
1
  import { readFileSync } from "fs";
2
2
  import { apiRequest } from "../api.mjs";
3
- import { formatTable, outputResult, success } from "../output.mjs";
3
+ import { formatTable, outputResult, success, info } from "../output.mjs";
4
4
 
5
5
  export async function smsSend(args, jsonMode) {
6
6
  const to = args["--to"];
7
- const body = args["--body"];
8
- const from = args["--from"];
7
+ const body = args["--body"] || args["--message"];
8
+ const from = args["--from"] || args["--sender"];
9
+ const provider = args["--provider"];
10
+ const route = args["--route"];
11
+ const schedule = args["--schedule"];
9
12
 
10
13
  if (!to || !body) {
11
- console.error("Usage: ses-cli sms send --to PHONE --body MESSAGE [--from SENDER_ID]");
14
+ console.error(`
15
+ Usage: ses-cli sms send --to PHONE --body MESSAGE [options]
16
+
17
+ Options:
18
+ --to Recipient phone number (E.164 format, e.g., +1234567890)
19
+ --body SMS message text (aliases: --message)
20
+ --from Sender ID or phone number (aliases: --sender)
21
+ --provider Provider ID to use (see: ses-cli providers list)
22
+ --route Route name for the provider (e.g., DEFAULT, PREMIUM)
23
+ --schedule Schedule send time (ISO 8601, e.g., 2026-06-15T09:00:00Z)
24
+
25
+ Examples:
26
+ ses-cli sms send --to +1234567890 --body "Hello!"
27
+ ses-cli sms send --to +1234567890 --body "VIP offer" --provider 5 --route PREMIUM
28
+ ses-cli sms send --to +1234567890 --body "Reminder" --schedule "2026-06-15T09:00:00Z"
29
+ `);
12
30
  process.exit(1);
13
31
  }
14
32
 
15
- const payload = { to, body };
16
- if (from) payload.from = from;
33
+ const payload = { to, message: body };
34
+ if (from) payload.senderId = from;
35
+ if (provider) payload.providerId = parseInt(provider);
36
+ if (route) payload.routeName = route;
37
+ if (schedule) payload.scheduledAt = schedule;
17
38
 
18
39
  const result = await apiRequest("POST", "/sms/send", payload);
19
40
 
20
41
  if (jsonMode) {
21
42
  outputResult(result, true);
22
43
  } else {
23
- success(`SMS sent to ${to}`);
24
- if (result.messageId) console.log(` Message ID: ${result.messageId}`);
25
- if (result.cost) console.log(` Cost: $${result.cost}`);
44
+ success(`SMS ${schedule ? "scheduled" : "sent"} to ${to}`);
45
+ if (result.data?.messageId) console.log(` Message ID: ${result.data.messageId}`);
46
+ if (result.data?.provider) console.log(` Provider: ${result.data.provider}`);
47
+ if (schedule) console.log(` Scheduled: ${schedule}`);
26
48
  }
27
49
  }
28
50
 
29
51
  export async function smsBulk(args, jsonMode) {
30
52
  const file = args["--file"];
31
- const body = args["--body"];
32
- const from = args["--from"];
33
-
34
- if (!file) {
35
- console.error("Usage: ses-cli sms bulk --file contacts.csv --body MESSAGE [--from SENDER_ID]");
36
- console.error("\nCSV format: phone (one number per line, or column header 'phone')");
53
+ const body = args["--body"] || args["--message"];
54
+ const from = args["--from"] || args["--sender"];
55
+ const provider = args["--provider"];
56
+ const route = args["--route"];
57
+ const schedule = args["--schedule"];
58
+ const delay = parseInt(args["--delay"] || "0");
59
+
60
+ if (!file || !body) {
61
+ console.error(`
62
+ Usage: ses-cli sms bulk --file FILE --body MESSAGE [options]
63
+
64
+ Options:
65
+ --file CSV file with phone numbers (column: phone or first column)
66
+ --body SMS message text. Use [[ColumnName]] for personalization
67
+ --from Sender ID or phone number
68
+ --provider Provider ID to use
69
+ --route Route name (e.g., DEFAULT, PREMIUM)
70
+ --schedule Schedule send time (ISO 8601)
71
+ --delay Delay between messages in ms (default: 0)
72
+
73
+ Personalization:
74
+ Use [[First]], [[Last]], [[Custom1]], etc. in your message body.
75
+ Column names are matched from your CSV headers.
76
+
77
+ Examples:
78
+ ses-cli sms bulk --file contacts.csv --body "Hi [[First]], check out our sale!"
79
+ ses-cli sms bulk --file vip.csv --body "VIP: 50% off" --provider 3 --route PREMIUM
80
+ `);
37
81
  process.exit(1);
38
82
  }
39
83
 
40
84
  const csvContent = readFileSync(file, "utf-8");
41
85
  const lines = csvContent.trim().split("\n");
42
- const hasHeader = lines[0].toLowerCase().includes("phone");
43
- const numbers = (hasHeader ? lines.slice(1) : lines)
44
- .map((l) => l.trim().split(",")[0].trim())
45
- .filter(Boolean);
86
+ const headers = lines[0].toLowerCase().split(",").map(h => h.trim().replace(/"/g, ""));
87
+ const phoneIdx = headers.indexOf("phone") !== -1 ? headers.indexOf("phone") : 0;
88
+ const dataLines = lines.slice(1);
46
89
 
47
- if (numbers.length === 0) {
48
- console.error("Error: No phone numbers found in CSV file");
90
+ if (dataLines.length === 0) {
91
+ console.error("Error: No data rows found in CSV file");
49
92
  process.exit(1);
50
93
  }
51
94
 
52
- console.log(`Sending to ${numbers.length} recipients...`);
95
+ info(`Sending to ${dataLines.length} recipients...`);
53
96
 
54
97
  let sent = 0;
55
98
  let failed = 0;
56
99
  const errors = [];
57
100
 
58
- for (const phone of numbers) {
101
+ for (const line of dataLines) {
102
+ const cols = line.split(",").map(c => c.trim().replace(/"/g, ""));
103
+ const phone = cols[phoneIdx];
104
+ if (!phone) continue;
105
+
106
+ // Replace personalization tokens
107
+ let personalizedBody = body;
108
+ headers.forEach((header, idx) => {
109
+ const regex = new RegExp(`\\[\\[${header}\\]\\]`, "gi");
110
+ personalizedBody = personalizedBody.replace(regex, cols[idx] || "");
111
+ });
112
+
59
113
  try {
60
- const payload = { to: phone, body };
61
- if (from) payload.from = from;
114
+ const payload = { to: phone, message: personalizedBody };
115
+ if (from) payload.senderId = from;
116
+ if (provider) payload.providerId = parseInt(provider);
117
+ if (route) payload.routeName = route;
62
118
  await apiRequest("POST", "/sms/send", payload);
63
119
  sent++;
120
+ if (delay > 0) await new Promise(r => setTimeout(r, delay));
64
121
  } catch {
65
122
  failed++;
66
123
  errors.push(phone);
@@ -68,11 +125,11 @@ export async function smsBulk(args, jsonMode) {
68
125
  }
69
126
 
70
127
  if (jsonMode) {
71
- outputResult({ sent, failed, total: numbers.length, errors }, true);
128
+ outputResult({ sent, failed, total: dataLines.length, errors }, true);
72
129
  } else {
73
- success(`Bulk SMS complete: ${sent} sent, ${failed} failed out of ${numbers.length}`);
130
+ success(`Bulk SMS complete: ${sent} sent, ${failed} failed out of ${dataLines.length}`);
74
131
  if (errors.length > 0) {
75
- console.log(` Failed numbers: ${errors.slice(0, 5).join(", ")}${errors.length > 5 ? "..." : ""}`);
132
+ console.log(` Failed: ${errors.slice(0, 5).join(", ")}${errors.length > 5 ? ` +${errors.length - 5} more` : ""}`);
76
133
  }
77
134
  }
78
135
  }
@@ -80,24 +137,29 @@ export async function smsBulk(args, jsonMode) {
80
137
  export async function smsHistory(args, jsonMode) {
81
138
  const limit = args["--limit"] || "20";
82
139
  const page = args["--page"] || "1";
140
+ const status = args["--status"];
141
+
142
+ const params = { limit, page };
143
+ if (status) params.status = status;
83
144
 
84
- const result = await apiRequest("GET", "/sms/history", null, { limit, page });
145
+ const result = await apiRequest("GET", "/sms/messages", null, params);
85
146
 
86
147
  if (jsonMode) {
87
148
  outputResult(result, true);
88
149
  } else {
89
- const messages = result.messages || result.data || [];
150
+ const messages = result.data?.messages || result.messages || result.data || [];
90
151
  if (messages.length === 0) {
91
152
  console.log("No SMS messages found.");
92
153
  return;
93
154
  }
94
155
  formatTable(
95
- ["To", "Status", "Date", "Body"],
156
+ ["To", "Status", "Provider", "Date", "Body"],
96
157
  messages.map((m) => [
97
158
  m.to || m.recipient,
98
159
  m.status || "sent",
160
+ m.provider || "-",
99
161
  new Date(m.createdAt || m.sentAt).toLocaleString(),
100
- (m.body || m.message || "").slice(0, 40),
162
+ (m.body || m.message || "").slice(0, 30),
101
163
  ])
102
164
  );
103
165
  console.log(`\nShowing ${messages.length} messages (page ${page})`);
@@ -0,0 +1,57 @@
1
+ import { apiRequest } from "../api.mjs";
2
+ import { formatTable, outputResult, success } from "../output.mjs";
3
+
4
+ export async function templatesList(args, jsonMode) {
5
+ const type = args["--type"]; // sms or email
6
+
7
+ const params = {};
8
+ if (type) params.type = type;
9
+
10
+ const result = await apiRequest("GET", "/templates", null, params);
11
+
12
+ if (jsonMode) {
13
+ outputResult(result, true);
14
+ } else {
15
+ const templates = result.data?.templates || result.templates || result.data || [];
16
+ if (templates.length === 0) {
17
+ console.log("No templates found. Create templates in your SESender dashboard.");
18
+ return;
19
+ }
20
+ formatTable(
21
+ ["ID", "Name", "Type", "Subject/Preview", "Created"],
22
+ templates.map((t) => [
23
+ t.id,
24
+ (t.name || "Untitled").slice(0, 20),
25
+ t.type || "email",
26
+ (t.subject || t.body || "").slice(0, 25),
27
+ t.createdAt ? new Date(t.createdAt).toLocaleDateString() : "-",
28
+ ])
29
+ );
30
+ console.log(`\n${templates.length} template(s) found`);
31
+ }
32
+ }
33
+
34
+ export async function templatesGet(args, jsonMode) {
35
+ const id = args["--id"];
36
+
37
+ if (!id) {
38
+ console.error("Usage: ses-cli templates get --id TEMPLATE_ID");
39
+ process.exit(1);
40
+ }
41
+
42
+ const result = await apiRequest("GET", `/templates/${id}`);
43
+
44
+ if (jsonMode) {
45
+ outputResult(result, true);
46
+ } else {
47
+ const t = result.data || result;
48
+ console.log(`Template: ${t.name || "Untitled"}`);
49
+ console.log(` ID: ${t.id}`);
50
+ console.log(` Type: ${t.type || "email"}`);
51
+ if (t.subject) console.log(` Subject: ${t.subject}`);
52
+ if (t.body) {
53
+ console.log(` Body:`);
54
+ console.log(` ${t.body.slice(0, 200)}${t.body.length > 200 ? "..." : ""}`);
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,61 @@
1
+ import { apiRequest } from "../api.mjs";
2
+ import { formatTable, outputResult, success } from "../output.mjs";
3
+
4
+ export async function trackingDomainsList(args, jsonMode) {
5
+ const result = await apiRequest("GET", "/tracking-domains");
6
+
7
+ if (jsonMode) {
8
+ outputResult(result, true);
9
+ } else {
10
+ const domains = result.data?.domains || result.domains || result.data || [];
11
+ if (domains.length === 0) {
12
+ console.log("No tracking domains configured. Add them in your SESender dashboard.");
13
+ return;
14
+ }
15
+ formatTable(
16
+ ["ID", "Domain", "Status", "SSL", "Clicks (24h)"],
17
+ domains.map((d) => [
18
+ d.id,
19
+ d.domain || d.name,
20
+ d.status || (d.isVerified ? "Verified" : "Pending"),
21
+ d.sslEnabled ? "Yes" : "No",
22
+ d.clicksToday || d.clicks24h || 0,
23
+ ])
24
+ );
25
+ console.log(`\n${domains.length} tracking domain(s)`);
26
+ }
27
+ }
28
+
29
+ export async function trackingStats(args, jsonMode) {
30
+ const domain = args["--domain"];
31
+ const period = args["--period"] || "7d";
32
+
33
+ const params = { period };
34
+ if (domain) params.domain = domain;
35
+
36
+ const result = await apiRequest("GET", "/tracking-domains/stats", null, params);
37
+
38
+ if (jsonMode) {
39
+ outputResult(result, true);
40
+ } else {
41
+ const data = result.data || result;
42
+ console.log(`\n Tracking Statistics (${period})`);
43
+ console.log(` ─────────────────────────`);
44
+ console.log(` Total Clicks: ${data.totalClicks || 0}`);
45
+ console.log(` Unique Clicks: ${data.uniqueClicks || 0}`);
46
+ console.log(` Total Opens: ${data.totalOpens || 0}`);
47
+ console.log(` Unique Opens: ${data.uniqueOpens || 0}`);
48
+ if (data.topLinks && data.topLinks.length > 0) {
49
+ console.log(`\n Top Links:`);
50
+ formatTable(
51
+ ["URL", "Clicks", "Unique"],
52
+ data.topLinks.slice(0, 10).map(l => [
53
+ (l.url || l.originalUrl || "").slice(0, 35),
54
+ l.clicks || 0,
55
+ l.uniqueClicks || 0,
56
+ ])
57
+ );
58
+ }
59
+ console.log("");
60
+ }
61
+ }
@@ -1,12 +1,25 @@
1
+ import { readFileSync } from "fs";
1
2
  import { apiRequest } from "../api.mjs";
2
- import { outputResult, success } from "../output.mjs";
3
+ import { formatTable, outputResult, success, info } from "../output.mjs";
3
4
 
4
5
  export async function validatePhone(args, jsonMode) {
5
6
  const phone = args["--phone"] || args._positional?.[0];
6
7
 
7
8
  if (!phone) {
8
- console.error("Usage: ses-cli validate phone --phone PHONE_NUMBER");
9
- console.error(" or: ses-cli validate phone +1234567890");
9
+ console.error(`
10
+ Usage: ses-cli validate phone PHONE_NUMBER
11
+ ses-cli validate phone --phone PHONE_NUMBER
12
+
13
+ Checks:
14
+ - Format validity
15
+ - Country detection
16
+ - Carrier lookup
17
+ - Line type (mobile, landline, voip)
18
+
19
+ Examples:
20
+ ses-cli validate phone +1234567890
21
+ ses-cli validate phone --phone "+44 7911 123456"
22
+ `);
10
23
  process.exit(1);
11
24
  }
12
25
 
@@ -15,12 +28,15 @@ export async function validatePhone(args, jsonMode) {
15
28
  if (jsonMode) {
16
29
  outputResult(result, true);
17
30
  } else {
18
- console.log(`Phone: ${phone}`);
19
- console.log(` Valid: ${result.valid ? "✓ Yes" : "✗ No"}`);
20
- if (result.carrier) console.log(` Carrier: ${result.carrier}`);
21
- if (result.type) console.log(` Type: ${result.type}`);
22
- if (result.country) console.log(` Country: ${result.country}`);
23
- if (result.formatted) console.log(` Format: ${result.formatted}`);
31
+ const data = result.data || result;
32
+ console.log(`\n Phone Validation: ${phone}`);
33
+ console.log(` ─────────────────────────`);
34
+ console.log(` Valid: ${data.valid ? "Yes" : "No"}`);
35
+ if (data.country || data.countryCode) console.log(` Country: ${data.country || data.countryCode}`);
36
+ if (data.carrier) console.log(` Carrier: ${data.carrier}`);
37
+ if (data.type || data.lineType) console.log(` Type: ${data.type || data.lineType}`);
38
+ if (data.formatted) console.log(` Format: ${data.formatted}`);
39
+ console.log("");
24
40
  }
25
41
  }
26
42
 
@@ -28,8 +44,21 @@ export async function validateEmail(args, jsonMode) {
28
44
  const email = args["--email"] || args._positional?.[0];
29
45
 
30
46
  if (!email) {
31
- console.error("Usage: ses-cli validate email --email ADDRESS");
32
- console.error(" or: ses-cli validate email user@example.com");
47
+ console.error(`
48
+ Usage: ses-cli validate email EMAIL_ADDRESS
49
+ ses-cli validate email --email EMAIL_ADDRESS
50
+
51
+ Checks:
52
+ - Syntax validity
53
+ - MX record existence
54
+ - Disposable email detection
55
+ - Role-based address detection
56
+ - Deliverability score
57
+
58
+ Examples:
59
+ ses-cli validate email user@example.com
60
+ ses-cli validate email --email "test@gmail.com"
61
+ `);
33
62
  process.exit(1);
34
63
  }
35
64
 
@@ -38,10 +67,77 @@ export async function validateEmail(args, jsonMode) {
38
67
  if (jsonMode) {
39
68
  outputResult(result, true);
40
69
  } else {
41
- console.log(`Email: ${email}`);
42
- console.log(` Valid: ${result.valid ? "✓ Yes" : "✗ No"}`);
43
- if (result.disposable !== undefined) console.log(` Disposable: ${result.disposable ? "⚠ Yes" : "No"}`);
44
- if (result.mx) console.log(` MX Record: ${result.mx}`);
45
- if (result.reason) console.log(` Reason: ${result.reason}`);
70
+ const data = result.data || result;
71
+ console.log(`\n Email Validation: ${email}`);
72
+ console.log(` ─────────────────────────`);
73
+ console.log(` Valid: ${data.valid ? "Yes" : "No"}`);
74
+ if (data.mx !== undefined || data.mxValid !== undefined) console.log(` MX Record: ${(data.mx || data.mxValid) ? "Yes" : "No"}`);
75
+ if (data.disposable !== undefined) console.log(` Disposable: ${data.disposable ? "Yes" : "No"}`);
76
+ if (data.roleBased !== undefined) console.log(` Role-based: ${data.roleBased ? "Yes" : "No"}`);
77
+ if (data.freeProvider !== undefined) console.log(` Free: ${data.freeProvider ? "Yes" : "No"}`);
78
+ if (data.score !== undefined) console.log(` Score: ${data.score}/100`);
79
+ if (data.reason) console.log(` Reason: ${data.reason}`);
80
+ if (data.suggestion) console.log(` Suggestion: ${data.suggestion}`);
81
+ console.log("");
82
+ }
83
+ }
84
+
85
+ export async function validateBulk(args, jsonMode) {
86
+ const file = args["--file"];
87
+ const type = args["--type"] || "email";
88
+
89
+ if (!file) {
90
+ console.error(`
91
+ Usage: ses-cli validate bulk --file FILE [--type email|phone]
92
+
93
+ Options:
94
+ --file CSV file with emails or phone numbers (one per line or first column)
95
+ --type Type of validation: "email" (default) or "phone"
96
+
97
+ Examples:
98
+ ses-cli validate bulk --file emails.csv --type email
99
+ ses-cli validate bulk --file phones.csv --type phone
100
+ `);
101
+ process.exit(1);
102
+ }
103
+
104
+ const csvContent = readFileSync(file, "utf-8");
105
+ const lines = csvContent.trim().split("\n");
106
+ const hasHeader = lines[0].toLowerCase().includes(type) || lines[0].toLowerCase().includes("@") === false;
107
+ const items = (hasHeader ? lines.slice(1) : lines)
108
+ .map(l => l.trim().split(",")[0].trim().replace(/"/g, ""))
109
+ .filter(Boolean);
110
+
111
+ if (items.length === 0) {
112
+ console.error("Error: No items found in file");
113
+ process.exit(1);
114
+ }
115
+
116
+ if (items.length > 500) {
117
+ console.error("Error: Maximum 500 items per batch. Split your file.");
118
+ process.exit(1);
119
+ }
120
+
121
+ info(`Validating ${items.length} ${type}(s)...`);
122
+
123
+ const result = await apiRequest("POST", `/validate/bulk`, { items, type });
124
+
125
+ if (jsonMode) {
126
+ outputResult(result, true);
127
+ } else {
128
+ const data = result.data || result;
129
+ const valid = data.valid || 0;
130
+ const invalid = data.invalid || 0;
131
+ const invalidItems = data.invalidItems || [];
132
+
133
+ success(`Validation complete: ${valid} valid, ${invalid} invalid out of ${items.length}`);
134
+ if (invalidItems.length > 0 && invalidItems.length <= 10) {
135
+ console.log(`\n Invalid ${type}s:`);
136
+ invalidItems.forEach(i => console.log(` - ${i}`));
137
+ } else if (invalidItems.length > 10) {
138
+ console.log(`\n First 10 invalid ${type}s:`);
139
+ invalidItems.slice(0, 10).forEach(i => console.log(` - ${i}`));
140
+ console.log(` ... and ${invalidItems.length - 10} more`);
141
+ }
46
142
  }
47
143
  }
@@ -0,0 +1,103 @@
1
+ import { apiRequest } from "../api.mjs";
2
+ import { formatTable, outputResult, success } from "../output.mjs";
3
+
4
+ export async function webhooksList(args, jsonMode) {
5
+ const result = await apiRequest("GET", "/webhooks");
6
+
7
+ if (jsonMode) {
8
+ outputResult(result, true);
9
+ } else {
10
+ const webhooks = result.data?.webhooks || result.webhooks || result.data || [];
11
+ if (webhooks.length === 0) {
12
+ console.log("No webhooks configured.");
13
+ return;
14
+ }
15
+ formatTable(
16
+ ["ID", "URL", "Events", "Status"],
17
+ webhooks.map((w) => [
18
+ w.id,
19
+ (w.url || "").slice(0, 40),
20
+ (w.events || []).join(", ").slice(0, 30),
21
+ w.isActive ? "Active" : "Inactive",
22
+ ])
23
+ );
24
+ }
25
+ }
26
+
27
+ export async function webhooksCreate(args, jsonMode) {
28
+ const url = args["--url"];
29
+ const events = args["--events"];
30
+
31
+ if (!url || !events) {
32
+ console.error(`
33
+ Usage: ses-cli webhooks create --url URL --events EVENTS
34
+
35
+ Options:
36
+ --url Webhook endpoint URL (must be HTTPS)
37
+ --events Comma-separated list of events to subscribe to
38
+
39
+ Available Events:
40
+ email.sent, email.delivered, email.bounced, email.complained
41
+ sms.sent, sms.delivered, sms.failed
42
+ link.clicked, email.opened
43
+
44
+ Examples:
45
+ ses-cli webhooks create --url https://myapp.com/webhook --events "email.delivered,email.bounced"
46
+ ses-cli webhooks create --url https://myapp.com/sms-hook --events "sms.sent,sms.delivered,sms.failed"
47
+ `);
48
+ process.exit(1);
49
+ }
50
+
51
+ const payload = {
52
+ url,
53
+ events: events.split(",").map(e => e.trim()),
54
+ };
55
+
56
+ const result = await apiRequest("POST", "/webhooks", payload);
57
+
58
+ if (jsonMode) {
59
+ outputResult(result, true);
60
+ } else {
61
+ success(`Webhook created: ${url}`);
62
+ if (result.data?.id) console.log(` ID: ${result.data.id}`);
63
+ if (result.data?.secret) console.log(` Secret: ${result.data.secret}`);
64
+ }
65
+ }
66
+
67
+ export async function webhooksDelete(args, jsonMode) {
68
+ const id = args["--id"];
69
+
70
+ if (!id) {
71
+ console.error("Usage: ses-cli webhooks delete --id WEBHOOK_ID");
72
+ process.exit(1);
73
+ }
74
+
75
+ const result = await apiRequest("DELETE", `/webhooks/${id}`);
76
+
77
+ if (jsonMode) {
78
+ outputResult(result, true);
79
+ } else {
80
+ success(`Webhook ${id} deleted`);
81
+ }
82
+ }
83
+
84
+ export async function webhooksTest(args, jsonMode) {
85
+ const id = args["--id"];
86
+
87
+ if (!id) {
88
+ console.error("Usage: ses-cli webhooks test --id WEBHOOK_ID");
89
+ process.exit(1);
90
+ }
91
+
92
+ const result = await apiRequest("POST", `/webhooks/${id}/test`);
93
+
94
+ if (jsonMode) {
95
+ outputResult(result, true);
96
+ } else {
97
+ if (result.data?.success || result.success) {
98
+ success(`Test webhook sent to webhook ${id}`);
99
+ } else {
100
+ console.error(`Webhook test failed: ${result.data?.error || "Unknown error"}`);
101
+ }
102
+ }
103
+ }