@mailmodo/cli 0.0.18-beta.pr20.29 → 0.0.19-beta.pr21.30

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.
@@ -10,9 +10,15 @@ export default class Deploy extends BaseCommand {
10
10
  * Fetches current DNS verification status for the deploy flow.
11
11
  *
12
12
  * @param jsonOutput - When true, spinner uses stderr for stdout-safe JSON runs.
13
+ * @param domain - Sending domain to check. If empty, request will fail and trigger setup flow.
13
14
  */
14
15
  private fetchDomainVerifyForDeploy;
15
16
  run(): Promise<void>;
17
+ private buildDeployPayload;
18
+ private mapEmailToPayload;
19
+ private buildProjectPayload;
20
+ private confirmDeploy;
21
+ private ensureDomainReady;
16
22
  /**
17
23
  * Lists emails about to be deployed (skipped when `--json` is set).
18
24
  *
@@ -32,9 +38,11 @@ export default class Deploy extends BaseCommand {
32
38
  * @returns {Promise<boolean>} true if domain was verified, false if skipped.
33
39
  */
34
40
  private runDomainSetup;
41
+ private collectDomainInputs;
42
+ private showDnsRecords;
35
43
  /**
36
44
  * Calls the domain verification API endpoint and reports pass/fail
37
- * status for each DNS record (SPF, DKIM, DMARC).
45
+ * status for each DNS record (DKIM, DMARC, Return-Path).
38
46
  *
39
47
  * @returns {Promise<boolean>} true if all records pass.
40
48
  */
@@ -1,8 +1,8 @@
1
1
  import { confirm, input } from '@inquirer/prompts';
2
2
  import chalk from 'chalk';
3
3
  import { BaseCommand } from '../../lib/base-command.js';
4
- import { API_ENDPOINTS, DNS_GUIDE_URL } from '../../lib/constants.js';
5
- import { saveYaml } from '../../lib/yaml-config.js';
4
+ import { API_ENDPOINTS, DEFAULT_BRAND_COLOR, DEFAULT_MONTHLY_CAP, DNS_GUIDE_URL, } from '../../lib/constants.js';
5
+ import { loadTemplate, saveYaml, } from '../../lib/yaml-config.js';
6
6
  export default class Deploy extends BaseCommand {
7
7
  static description = 'Deploy email sequences and verify sending domain';
8
8
  static examples = [
@@ -16,69 +16,130 @@ export default class Deploy extends BaseCommand {
16
16
  * Fetches current DNS verification status for the deploy flow.
17
17
  *
18
18
  * @param jsonOutput - When true, spinner uses stderr for stdout-safe JSON runs.
19
+ * @param domain - Sending domain to check. If empty, request will fail and trigger setup flow.
19
20
  */
20
- fetchDomainVerifyForDeploy(jsonOutput) {
21
- return this.withApiSpinner({ json: jsonOutput, text: ' Checking domain verification...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY));
21
+ fetchDomainVerifyForDeploy(jsonOutput, domain) {
22
+ return this.withApiSpinner({ json: jsonOutput, text: ' Checking domain verification...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY, {
23
+ domain: domain || '',
24
+ }));
22
25
  }
23
26
  async run() {
24
27
  const { flags } = await this.parse(Deploy);
25
28
  await this.ensureAuth();
26
29
  const yamlConfig = await this.ensureYaml();
27
- const domainVerify = await this.fetchDomainVerifyForDeploy(flags.json);
28
- const domainVerified = domainVerify.ok &&
29
- domainVerify.data?.spf === 'pass' &&
30
- domainVerify.data?.dkim === 'pass' &&
31
- domainVerify.data?.dmarc === 'pass';
32
- if (!domainVerified) {
33
- if (!flags.json) {
34
- this.log(`\n No sending domain verified yet.`);
35
- this.log(` You need to verify a domain before sending emails.`);
36
- this.log(` This is a one-time setup. Takes about 5 minutes.\n`);
37
- }
38
- if (!flags.yes) {
39
- const setupNow = await confirm({
40
- default: true,
41
- message: 'Set up your sending domain now?',
42
- });
43
- if (!setupNow) {
44
- this.log(`\n Sequences saved but ${chalk.yellow('NOT deployed')}.`);
45
- this.log(` Emails will not send until your domain is verified.`);
46
- this.log(` When ready, run: ${chalk.cyan('mailmodo domain')}`);
47
- this.log(` Then: ${chalk.cyan('mailmodo deploy')}\n`);
48
- return;
49
- }
50
- }
51
- const completed = await this.runDomainSetup(yamlConfig, flags);
52
- if (!completed)
53
- return;
54
- }
30
+ const domainReady = await this.ensureDomainReady(yamlConfig, flags);
31
+ if (!domainReady)
32
+ return;
55
33
  this.logPreDeploySummary(yamlConfig, flags.json);
56
- if (!flags.yes) {
57
- const proceed = await confirm({
58
- default: true,
59
- message: `Deploy ${yamlConfig.emails.length} emails?`,
60
- });
61
- if (!proceed) {
62
- this.log('\n Deploy cancelled.\n');
63
- return;
64
- }
65
- }
66
- const response = await this.withApiSpinner({ json: flags.json, text: ' Deploying email sequences...' }, () => this.apiClient.post(API_ENDPOINTS.SEQUENCES, {
67
- emails: yamlConfig.emails,
68
- project: yamlConfig.project,
69
- }));
34
+ const confirmed = await this.confirmDeploy(yamlConfig, flags);
35
+ if (!confirmed)
36
+ return;
37
+ const payload = await this.buildDeployPayload(yamlConfig);
38
+ const response = await this.withApiSpinner({ json: flags.json, text: ' Deploying email sequences...' }, () => this.apiClient.post(API_ENDPOINTS.SEQUENCES_DEPLOY, payload));
70
39
  if (!response.ok) {
71
40
  this.handleApiError(response);
72
41
  }
73
42
  if (flags.json) {
74
43
  this.log(JSON.stringify({
75
- deployed: true,
76
- emailsLive: yamlConfig.emails.length,
77
- sdkSnippet: response.data?.sdkSnippet,
44
+ deployed: response.data.deployed,
45
+ diff: response.data.diff,
46
+ emailsLive: response.data.emailsLive,
47
+ sdkSnippet: response.data.sdkSnippet,
48
+ sequenceId: response.data.sequenceId,
78
49
  }, null, 2));
79
50
  return;
80
51
  }
81
- this.logDeploySuccessInstructions();
52
+ this.logDeploySuccessInstructions(response.data.sdkSnippet);
53
+ }
54
+ async buildDeployPayload(yamlConfig) {
55
+ const emailsWithHtml = await Promise.all(yamlConfig.emails.map(async (email) => {
56
+ const html = (await loadTemplate(`${email.id}.html`)) || '';
57
+ return { ...this.mapEmailToPayload(email), html, plainHtml: html };
58
+ }));
59
+ return {
60
+ ...this.buildProjectPayload(yamlConfig.project),
61
+ emails: emailsWithHtml,
62
+ };
63
+ }
64
+ mapEmailToPayload(email) {
65
+ return {
66
+ condition: email.condition || null,
67
+ ctaText: '',
68
+ delay: typeof email.delay === 'string'
69
+ ? Number.parseInt(email.delay, 10) || 0
70
+ : email.delay,
71
+ goal: email.goal || '',
72
+ id: email.id,
73
+ isReminder: false,
74
+ previewText: email.previewText || '',
75
+ priority: 'medium',
76
+ subject: email.subject,
77
+ trigger: email.trigger,
78
+ };
79
+ }
80
+ buildProjectPayload(project) {
81
+ return {
82
+ brand: {
83
+ colors: [project?.brandColor || DEFAULT_BRAND_COLOR],
84
+ logoUrl: project?.logoUrl || '',
85
+ },
86
+ emailStyle: project?.emailStyle || 'branded',
87
+ monthlyCap: project?.monthlyCap ?? DEFAULT_MONTHLY_CAP,
88
+ product: {
89
+ businessType: project?.type || '',
90
+ description: '',
91
+ pricingModel: '',
92
+ productName: project?.name || '',
93
+ saasModel: '',
94
+ targetUser: '',
95
+ url: project?.url || '',
96
+ },
97
+ senderDetails: {
98
+ address: project?.address || '',
99
+ domain: project?.domain || '',
100
+ fromEmail: project?.fromEmail || '',
101
+ fromName: project?.fromName || '',
102
+ replyTo: project?.replyTo || project?.fromEmail || '',
103
+ },
104
+ webhookUrl: project?.webhookUrl || '',
105
+ };
106
+ }
107
+ async confirmDeploy(yamlConfig, flags) {
108
+ if (flags.yes)
109
+ return true;
110
+ const proceed = await confirm({
111
+ default: true,
112
+ message: `Deploy ${yamlConfig.emails.length} emails?`,
113
+ });
114
+ if (!proceed) {
115
+ this.log('\n Deploy cancelled.\n');
116
+ }
117
+ return proceed;
118
+ }
119
+ async ensureDomainReady(yamlConfig, flags) {
120
+ const domainVerify = await this.fetchDomainVerifyForDeploy(flags.json, yamlConfig.project?.domain);
121
+ if (domainVerify.ok && domainVerify.data?.domainStatus === 'VERIFIED') {
122
+ return true;
123
+ }
124
+ if (!flags.json) {
125
+ this.log(`\n No sending domain verified yet.`);
126
+ this.log(` You need to verify a domain before sending emails.`);
127
+ this.log(` This is a one-time setup. Takes about 5 minutes.\n`);
128
+ }
129
+ if (!flags.yes) {
130
+ const setupNow = await confirm({
131
+ default: true,
132
+ message: 'Set up your sending domain now?',
133
+ });
134
+ if (!setupNow) {
135
+ this.log(`\n Sequences saved but ${chalk.yellow('NOT deployed')}.`);
136
+ this.log(` Emails will not send until your domain is verified.`);
137
+ this.log(` When ready, run: ${chalk.cyan('mailmodo domain')}`);
138
+ this.log(` Then: ${chalk.cyan('mailmodo deploy')}\n`);
139
+ return false;
140
+ }
141
+ }
142
+ return this.runDomainSetup(yamlConfig, flags);
82
143
  }
83
144
  /**
84
145
  * Lists emails about to be deployed (skipped when `--json` is set).
@@ -99,19 +160,19 @@ export default class Deploy extends BaseCommand {
99
160
  /**
100
161
  * Prints the post-deploy success message and SDK install snippet for interactive runs.
101
162
  */
102
- logDeploySuccessInstructions() {
163
+ logDeploySuccessInstructions(sdkSnippet) {
103
164
  this.log(` ${chalk.green('Deployed.')} Emails are live.\n`);
104
165
  this.log(` ${'─'.repeat(53)}`);
105
166
  this.log(` ${chalk.bold('ADD THIS TO YOUR APP (one-time only):')}`);
106
167
  this.log(` ${'─'.repeat(53)}\n`);
107
- this.log(` ${chalk.cyan('npm install @mailmodo/sdk')}\n`);
168
+ this.log(` ${chalk.cyan(sdkSnippet.install ?? 'npm install @mailmodo/sdk')}\n`);
108
169
  this.log(` ${chalk.dim("import { track, identify } from '@mailmodo/sdk'")}\n`);
109
- this.log(` ${chalk.dim('// On user signup:')}`);
110
- this.log(` ${chalk.dim("track('user.signup', { email, first_name, app_url })")}\n`);
111
- this.log(` ${chalk.dim('// When user creates a project:')}`);
112
- this.log(` ${chalk.dim('identify(email, { has_created_project: true })')}\n`);
113
- this.log(` ${chalk.dim('// On trial expiry:')}`);
114
- this.log(` ${chalk.dim("track('user.trial_expiry', { email, first_name })")}\n`);
170
+ for (const [key, snippet] of Object.entries(sdkSnippet)) {
171
+ if (key === 'install')
172
+ continue;
173
+ this.log(` ${chalk.dim(snippet)}`);
174
+ }
175
+ this.log('');
115
176
  this.log(` Full SDK docs: ${chalk.cyan('mailmodo.com/docs/sdk')}\n`);
116
177
  this.log(` ${'─'.repeat(53)}\n`);
117
178
  }
@@ -123,31 +184,7 @@ export default class Deploy extends BaseCommand {
123
184
  * @returns {Promise<boolean>} true if domain was verified, false if skipped.
124
185
  */
125
186
  async runDomainSetup(yamlConfig, flags) {
126
- let domain;
127
- let senderEmail;
128
- let address;
129
- if (flags.yes) {
130
- domain = yamlConfig.project?.domain || '';
131
- senderEmail = yamlConfig.project?.fromEmail || '';
132
- address = yamlConfig.project?.address || '';
133
- }
134
- else {
135
- this.log(`\n ${'─'.repeat(53)}`);
136
- this.log(` ${chalk.bold('DOMAIN SETUP')}`);
137
- this.log(` ${'─'.repeat(53)}\n`);
138
- domain = await input({
139
- message: 'What domain will you send from?',
140
- validate: (v) => (v?.trim() ? true : 'Domain is required'),
141
- });
142
- senderEmail = await input({
143
- message: 'Sender email address:',
144
- validate: (v) => v?.includes('@') ? true : 'Please enter a valid email',
145
- });
146
- address = await input({
147
- message: 'Business address (required by law for email footers):',
148
- validate: (v) => (v?.trim() ? true : 'Address is required'),
149
- });
150
- }
187
+ const { address, domain, senderEmail } = await this.collectDomainInputs(yamlConfig, flags);
151
188
  const domainResponse = await this.withApiSpinner({ json: flags.json, text: ' Configuring domain...' }, () => this.apiClient.post(API_ENDPOINTS.DOMAIN, {
152
189
  address,
153
190
  domain,
@@ -160,20 +197,9 @@ export default class Deploy extends BaseCommand {
160
197
  yamlConfig.project.fromEmail = senderEmail;
161
198
  yamlConfig.project.address = address;
162
199
  await saveYaml(yamlConfig);
163
- const dnsRecords = domainResponse.data?.dnsRecords || [];
164
- if (!flags.json) {
165
- this.log(`\n Add these ${dnsRecords.length} DNS records to your domain provider:\n`);
166
- for (const [i, record] of dnsRecords.entries()) {
167
- this.log(` ${chalk.bold(`RECORD ${i + 1}`)}`);
168
- this.log(` Type: ${record.type}`);
169
- this.log(` Host: ${record.host}`);
170
- this.log(` Value: ${record.value}\n`);
171
- }
172
- this.log(` DNS changes take 5–30 minutes to propagate.`);
173
- this.log(` Full guide: ${chalk.cyan(DNS_GUIDE_URL)}\n`);
174
- }
200
+ this.showDnsRecords(domainResponse.data?.dnsRecords || [], flags.json);
175
201
  if (flags.yes) {
176
- return this.verifyDomain(flags.json);
202
+ return this.verifyDomain(flags.json, domain);
177
203
  }
178
204
  const action = await input({
179
205
  default: '',
@@ -185,25 +211,65 @@ export default class Deploy extends BaseCommand {
185
211
  this.log(` Then: ${chalk.cyan('mailmodo deploy')}\n`);
186
212
  return false;
187
213
  }
188
- return this.verifyDomain(flags.json);
214
+ return this.verifyDomain(flags.json, domain);
215
+ }
216
+ async collectDomainInputs(yamlConfig, flags) {
217
+ if (flags.yes) {
218
+ return {
219
+ address: yamlConfig.project?.address || '',
220
+ domain: yamlConfig.project?.domain || '',
221
+ senderEmail: yamlConfig.project?.fromEmail || '',
222
+ };
223
+ }
224
+ this.log(`\n ${'─'.repeat(53)}`);
225
+ this.log(` ${chalk.bold('DOMAIN SETUP')}`);
226
+ this.log(` ${'─'.repeat(53)}\n`);
227
+ const domain = await input({
228
+ message: 'What domain will you send from?',
229
+ validate: (v) => (v?.trim() ? true : 'Domain is required'),
230
+ });
231
+ const senderEmail = await input({
232
+ message: 'Sender email address:',
233
+ validate: (v) => (v?.includes('@') ? true : 'Please enter a valid email'),
234
+ });
235
+ const address = await input({
236
+ message: 'Business address (required by law for email footers):',
237
+ validate: (v) => (v?.trim() ? true : 'Address is required'),
238
+ });
239
+ return { address, domain, senderEmail };
240
+ }
241
+ showDnsRecords(dnsRecords, jsonOutput) {
242
+ if (jsonOutput)
243
+ return;
244
+ this.log(`\n Add these ${dnsRecords.length} DNS records to your domain provider:\n`);
245
+ for (const [i, record] of dnsRecords.entries()) {
246
+ this.log(` ${chalk.bold(`RECORD ${i + 1}`)}`);
247
+ this.log(` Type: ${record.type}`);
248
+ this.log(` Host: ${record.host}`);
249
+ this.log(` Value: ${record.value}\n`);
250
+ }
251
+ this.log(` DNS changes take 5–30 minutes to propagate.`);
252
+ this.log(` Full guide: ${chalk.cyan(DNS_GUIDE_URL)}\n`);
189
253
  }
190
254
  /**
191
255
  * Calls the domain verification API endpoint and reports pass/fail
192
- * status for each DNS record (SPF, DKIM, DMARC).
256
+ * status for each DNS record (DKIM, DMARC, Return-Path).
193
257
  *
194
258
  * @returns {Promise<boolean>} true if all records pass.
195
259
  */
196
- async verifyDomain(jsonOutput) {
197
- const verify = await this.withApiSpinner({ json: jsonOutput, text: ' Checking DNS...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY));
260
+ async verifyDomain(jsonOutput, domain) {
261
+ const verify = await this.withApiSpinner({ json: jsonOutput, text: ' Checking DNS...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY, {
262
+ domain,
263
+ }));
198
264
  if (!verify.ok) {
199
265
  this.handleApiError(verify);
200
266
  }
201
- const { dkim, dmarc, spf } = verify.data;
202
- const allPassed = spf === 'pass' && dkim === 'pass' && dmarc === 'pass';
267
+ const { dkim, dmarc, domainStatus, returnPath } = verify.data;
268
+ const allPassed = domainStatus === 'VERIFIED';
203
269
  if (!jsonOutput) {
204
- this.log(` SPF ${spf === 'pass' ? chalk.green('✓') : chalk.red('✗')}`);
205
- this.log(` DKIM ${dkim === 'pass' ? chalk.green('✓') : chalk.red('✗')}`);
206
- this.log(` DMARC ${dmarc === 'pass' ? chalk.green('✓') : chalk.red('✗')}`);
270
+ this.log(` DKIM ${dkim ? chalk.green('✓') : chalk.red('✗')}`);
271
+ this.log(` DMARC ${dmarc ? chalk.green('✓') : chalk.red('✗')}`);
272
+ this.log(` Return-Path ${returnPath ? chalk.green('✓') : chalk.red('✗')}`);
207
273
  if (allPassed) {
208
274
  this.log(`\n ${chalk.green('Domain verified.')} Continuing deploy...\n`);
209
275
  }
@@ -125,19 +125,20 @@ export default class Preview extends BaseCommand {
125
125
  */
126
126
  async sendTestEmail(templateId, toAddress, jsonOutput) {
127
127
  await this.ensureAuth();
128
- const response = await this.withApiSpinner({ json: jsonOutput, text: ' Sending test email...' }, () => this.apiClient.post(`${API_ENDPOINTS.PREVIEW}/${templateId}/send`, {
129
- to: toAddress,
130
- }));
128
+ const response = await this.withApiSpinner({ json: jsonOutput, text: ' Sending test email...' }, () => this.apiClient.post(`${API_ENDPOINTS.PREVIEW}/${templateId}/send`, { to: toAddress }));
131
129
  if (!response.ok) {
132
130
  this.handleApiError(response);
133
131
  }
132
+ const { note, sentTo, sentVia, status } = response.data;
134
133
  if (jsonOutput) {
135
- this.log(JSON.stringify({ templateId, sentTo: toAddress, status: 'sent' }, null, 2));
134
+ this.log(JSON.stringify({ note, sentTo, sentVia, status }, null, 2));
136
135
  return;
137
136
  }
138
- this.log(`\n Sending test to ${chalk.cyan(toAddress)}...`);
139
- this.log(` ${chalk.dim('Note: If domain is not yet verified, test sends via mailmodo.com domain.')}`);
140
- this.log(` ${chalk.green('✓')} Sent.\n`);
137
+ this.log(`\n ${chalk.green('✓')} Test email sent to ${chalk.cyan(sentTo)} via ${chalk.cyan(sentVia)}.`);
138
+ if (note) {
139
+ this.log(` ${chalk.dim(note)}`);
140
+ }
141
+ this.log('');
141
142
  }
142
143
  /**
143
144
  * Starts a local HTTP server on PREVIEW_PORT to serve the rendered email
@@ -17,6 +17,8 @@ export declare const API_ENDPOINTS: Readonly<{
17
17
  LOGS: "/logs";
18
18
  PREVIEW: "/preview";
19
19
  SEQUENCES: "/sequences";
20
+ SEQUENCES_DEPLOY: "/sequences/deploy";
21
+ SEQUENCES_VALIDATE: "/sequences/validate";
20
22
  }>;
21
23
  export declare const LOGIN_URL = "https://app-vertex-debug.azurewebsites.net/login.html";
22
24
  export declare const DOCS_URL = "https://mailmodo.com/docs/cli";
@@ -23,6 +23,8 @@ export const API_ENDPOINTS = Object.freeze({
23
23
  LOGS: '/logs',
24
24
  PREVIEW: '/preview',
25
25
  SEQUENCES: '/sequences',
26
+ SEQUENCES_DEPLOY: '/sequences/deploy',
27
+ SEQUENCES_VALIDATE: '/sequences/validate',
26
28
  });
27
29
  const DEV_LOGIN_URL = 'https://app-vertex-debug.azurewebsites.net/login.html';
28
30
  // const PRODUCTION_LOGIN_URL = 'https://mailmodo.com/cli';
@@ -419,19 +419,15 @@
419
419
  "index.js"
420
420
  ]
421
421
  },
422
- "preview": {
422
+ "logs": {
423
423
  "aliases": [],
424
- "args": {
425
- "id": {
426
- "description": "Email template ID to preview",
427
- "name": "id"
428
- }
429
- },
430
- "description": "Preview an email in browser, as text, or send a test",
424
+ "args": {},
425
+ "description": "View email send logs and delivery events",
431
426
  "examples": [
432
- "<%= config.bin %> preview welcome",
433
- "<%= config.bin %> preview welcome --text",
434
- "<%= config.bin %> preview welcome --send me@example.com"
427
+ "<%= config.bin %> logs",
428
+ "<%= config.bin %> logs --email sarah@example.com",
429
+ "<%= config.bin %> logs --failed",
430
+ "<%= config.bin %> logs --json"
435
431
  ],
436
432
  "flags": {
437
433
  "json": {
@@ -447,23 +443,23 @@
447
443
  "allowNo": false,
448
444
  "type": "boolean"
449
445
  },
450
- "send": {
451
- "description": "Send test email to this address",
452
- "name": "send",
446
+ "email": {
447
+ "description": "Filter logs by contact email",
448
+ "name": "email",
453
449
  "hasDynamicHelp": false,
454
450
  "multiple": false,
455
451
  "type": "option"
456
452
  },
457
- "text": {
458
- "description": "Output plain text version (for AI agents)",
459
- "name": "text",
453
+ "failed": {
454
+ "description": "Show only failed/bounced events",
455
+ "name": "failed",
460
456
  "allowNo": false,
461
457
  "type": "boolean"
462
458
  }
463
459
  },
464
460
  "hasDynamicHelp": false,
465
461
  "hiddenAliases": [],
466
- "id": "preview",
462
+ "id": "logs",
467
463
  "pluginAlias": "@mailmodo/cli",
468
464
  "pluginName": "@mailmodo/cli",
469
465
  "pluginType": "core",
@@ -473,19 +469,18 @@
473
469
  "relativePath": [
474
470
  "dist",
475
471
  "commands",
476
- "preview",
472
+ "logs",
477
473
  "index.js"
478
474
  ]
479
475
  },
480
- "logs": {
476
+ "settings": {
481
477
  "aliases": [],
482
478
  "args": {},
483
- "description": "View email send logs and delivery events",
479
+ "description": "View and update project settings",
484
480
  "examples": [
485
- "<%= config.bin %> logs",
486
- "<%= config.bin %> logs --email sarah@example.com",
487
- "<%= config.bin %> logs --failed",
488
- "<%= config.bin %> logs --json"
481
+ "<%= config.bin %> settings",
482
+ "<%= config.bin %> settings --set brand_color=#0F3460",
483
+ "<%= config.bin %> settings --json"
489
484
  ],
490
485
  "flags": {
491
486
  "json": {
@@ -501,23 +496,17 @@
501
496
  "allowNo": false,
502
497
  "type": "boolean"
503
498
  },
504
- "email": {
505
- "description": "Filter logs by contact email",
506
- "name": "email",
499
+ "set": {
500
+ "description": "Set a setting (format: key=value)",
501
+ "name": "set",
507
502
  "hasDynamicHelp": false,
508
503
  "multiple": false,
509
504
  "type": "option"
510
- },
511
- "failed": {
512
- "description": "Show only failed/bounced events",
513
- "name": "failed",
514
- "allowNo": false,
515
- "type": "boolean"
516
505
  }
517
506
  },
518
507
  "hasDynamicHelp": false,
519
508
  "hiddenAliases": [],
520
- "id": "logs",
509
+ "id": "settings",
521
510
  "pluginAlias": "@mailmodo/cli",
522
511
  "pluginName": "@mailmodo/cli",
523
512
  "pluginType": "core",
@@ -527,18 +516,23 @@
527
516
  "relativePath": [
528
517
  "dist",
529
518
  "commands",
530
- "logs",
519
+ "settings",
531
520
  "index.js"
532
521
  ]
533
522
  },
534
- "settings": {
523
+ "preview": {
535
524
  "aliases": [],
536
- "args": {},
537
- "description": "View and update project settings",
525
+ "args": {
526
+ "id": {
527
+ "description": "Email template ID to preview",
528
+ "name": "id"
529
+ }
530
+ },
531
+ "description": "Preview an email in browser, as text, or send a test",
538
532
  "examples": [
539
- "<%= config.bin %> settings",
540
- "<%= config.bin %> settings --set brand_color=#0F3460",
541
- "<%= config.bin %> settings --json"
533
+ "<%= config.bin %> preview welcome",
534
+ "<%= config.bin %> preview welcome --text",
535
+ "<%= config.bin %> preview welcome --send me@example.com"
542
536
  ],
543
537
  "flags": {
544
538
  "json": {
@@ -554,17 +548,23 @@
554
548
  "allowNo": false,
555
549
  "type": "boolean"
556
550
  },
557
- "set": {
558
- "description": "Set a setting (format: key=value)",
559
- "name": "set",
551
+ "send": {
552
+ "description": "Send test email to this address",
553
+ "name": "send",
560
554
  "hasDynamicHelp": false,
561
555
  "multiple": false,
562
556
  "type": "option"
557
+ },
558
+ "text": {
559
+ "description": "Output plain text version (for AI agents)",
560
+ "name": "text",
561
+ "allowNo": false,
562
+ "type": "boolean"
563
563
  }
564
564
  },
565
565
  "hasDynamicHelp": false,
566
566
  "hiddenAliases": [],
567
- "id": "settings",
567
+ "id": "preview",
568
568
  "pluginAlias": "@mailmodo/cli",
569
569
  "pluginName": "@mailmodo/cli",
570
570
  "pluginType": "core",
@@ -574,7 +574,7 @@
574
574
  "relativePath": [
575
575
  "dist",
576
576
  "commands",
577
- "settings",
577
+ "preview",
578
578
  "index.js"
579
579
  ]
580
580
  },
@@ -618,5 +618,5 @@
618
618
  ]
619
619
  }
620
620
  },
621
- "version": "0.0.18-beta.pr20.29"
621
+ "version": "0.0.19-beta.pr21.30"
622
622
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mailmodo/cli",
3
3
  "description": "Email lifecycle automation for the AI-native builder generation.",
4
- "version": "0.0.18-beta.pr20.29",
4
+ "version": "0.0.19-beta.pr21.30",
5
5
  "author": "provishalk",
6
6
  "bin": {
7
7
  "mailmodo": "bin/run.js"