@mailmodo/cli 0.0.21-beta.pr24.38 → 0.0.21

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.
@@ -6,9 +6,14 @@ export default class Deploy extends BaseCommand {
6
6
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
7
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
8
  };
9
+ /**
10
+ * Fetches current DNS verification status for the deploy flow.
11
+ *
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.
14
+ */
9
15
  private fetchDomainVerifyForDeploy;
10
16
  run(): Promise<void>;
11
- private validateSequence;
12
17
  private buildDeployPayload;
13
18
  private mapEmailToPayload;
14
19
  private buildBrandSection;
@@ -17,11 +22,32 @@ export default class Deploy extends BaseCommand {
17
22
  private buildProjectPayload;
18
23
  private confirmDeploy;
19
24
  private ensureDomainReady;
25
+ /**
26
+ * Lists emails about to be deployed (skipped when `--json` is set).
27
+ *
28
+ * @param yamlConfig - Loaded project YAML.
29
+ * @param jsonOutput - When true, skip human-readable output.
30
+ */
20
31
  private logPreDeploySummary;
21
- private logDiff;
32
+ /**
33
+ * Prints the post-deploy success message and SDK install snippet for interactive runs.
34
+ */
22
35
  private logDeploySuccessInstructions;
36
+ /**
37
+ * Interactive domain setup flow. Collects domain, sender email, and business
38
+ * address from the user, then calls the API to get DNS records to configure.
39
+ * Polls for verification when the user indicates they've added the records.
40
+ *
41
+ * @returns {Promise<boolean>} true if domain was verified, false if skipped.
42
+ */
23
43
  private runDomainSetup;
24
44
  private collectDomainInputs;
25
45
  private showDnsRecords;
46
+ /**
47
+ * Calls the domain verification API endpoint and reports pass/fail
48
+ * status for each DNS record (DKIM, DMARC, Return-Path).
49
+ *
50
+ * @returns {Promise<boolean>} true if all records pass.
51
+ */
26
52
  private verifyDomain;
27
53
  }
@@ -12,6 +12,12 @@ export default class Deploy extends BaseCommand {
12
12
  static flags = {
13
13
  ...BaseCommand.baseFlags,
14
14
  };
15
+ /**
16
+ * Fetches current DNS verification status for the deploy flow.
17
+ *
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.
20
+ */
15
21
  fetchDomainVerifyForDeploy(jsonOutput, domain) {
16
22
  return this.withApiSpinner({ json: jsonOutput, text: ' Checking domain verification...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY, {
17
23
  domain: domain || '',
@@ -24,12 +30,11 @@ export default class Deploy extends BaseCommand {
24
30
  const domainReady = await this.ensureDomainReady(yamlConfig, flags);
25
31
  if (!domainReady)
26
32
  return;
27
- const payload = await this.buildDeployPayload(yamlConfig);
28
- const validateResult = await this.validateSequence(payload, flags);
29
- this.logPreDeploySummary(yamlConfig, validateResult, flags.json);
33
+ this.logPreDeploySummary(yamlConfig, flags.json);
30
34
  const confirmed = await this.confirmDeploy(yamlConfig, flags);
31
35
  if (!confirmed)
32
36
  return;
37
+ const payload = await this.buildDeployPayload(yamlConfig);
33
38
  const response = await this.withApiSpinner({ json: flags.json, text: ' Deploying email sequences...' }, () => this.apiClient.post(API_ENDPOINTS.SEQUENCES_DEPLOY, payload));
34
39
  if (!response.ok) {
35
40
  this.handleApiError(response);
@@ -46,19 +51,6 @@ export default class Deploy extends BaseCommand {
46
51
  }
47
52
  this.logDeploySuccessInstructions(response.data.sdkSnippet);
48
53
  }
49
- async validateSequence(payload, flags) {
50
- const response = await this.withApiSpinner({ json: flags.json, text: ' Validating sequence...' }, () => this.apiClient.post(API_ENDPOINTS.SEQUENCES_VALIDATE, payload));
51
- if (!response.ok) {
52
- if (response.data.error === 'senderDomainNotFound') {
53
- this.error(`Sending domain not registered. Run: ${chalk.cyan('mailmodo domain')}`);
54
- }
55
- if (response.data.error === 'senderDomainNotVerified') {
56
- this.error(`Sending domain not verified. Run: ${chalk.cyan('mailmodo domain --verify')}`);
57
- }
58
- this.handleApiError(response);
59
- }
60
- return response.data;
61
- }
62
54
  async buildDeployPayload(yamlConfig) {
63
55
  const emailsWithHtml = await Promise.all(yamlConfig.emails.map(async (email) => {
64
56
  const html = (await loadTemplate(`${email.id}.html`)) || '';
@@ -72,7 +64,7 @@ export default class Deploy extends BaseCommand {
72
64
  mapEmailToPayload(email) {
73
65
  return {
74
66
  condition: email.condition || null,
75
- ctaText: email.ctaText || '',
67
+ ctaText: '',
76
68
  delay: typeof email.delay === 'string'
77
69
  ? Number.parseInt(email.delay, 10) || 0
78
70
  : email.delay,
@@ -94,11 +86,11 @@ export default class Deploy extends BaseCommand {
94
86
  buildProductSection(project) {
95
87
  return {
96
88
  businessType: project?.type || '',
97
- description: project?.description || '',
98
- pricingModel: project?.pricingModel || '',
89
+ description: '',
90
+ pricingModel: '',
99
91
  productName: project?.name || '',
100
- saasModel: project?.saasModel || '',
101
- targetUser: project?.targetUser || '',
92
+ saasModel: '',
93
+ targetUser: '',
102
94
  url: project?.url || '',
103
95
  };
104
96
  }
@@ -158,41 +150,25 @@ export default class Deploy extends BaseCommand {
158
150
  }
159
151
  return this.runDomainSetup(yamlConfig, flags);
160
152
  }
161
- logPreDeploySummary(yamlConfig, validateResult, jsonOutput) {
153
+ /**
154
+ * Lists emails about to be deployed (skipped when `--json` is set).
155
+ *
156
+ * @param yamlConfig - Loaded project YAML.
157
+ * @param jsonOutput - When true, skip human-readable output.
158
+ */
159
+ logPreDeploySummary(yamlConfig, jsonOutput) {
162
160
  if (jsonOutput)
163
161
  return;
164
162
  this.log(`\n ${chalk.green('✓')} Domain: ${yamlConfig.project?.domain || 'verified'}\n`);
165
- if (!validateResult.existingDeployment || !validateResult.diff) {
166
- this.log(` Deploying:`);
167
- for (const email of yamlConfig.emails) {
168
- this.log(` ${chalk.green('+')} ${email.id.padEnd(24)} ${email.trigger}`);
169
- }
170
- }
171
- else {
172
- this.logDiff(validateResult.diff);
163
+ this.log(` Deploying:`);
164
+ for (const email of yamlConfig.emails) {
165
+ this.log(` ${chalk.green('+')} ${email.id.padEnd(24)} ${email.trigger}`);
173
166
  }
174
167
  this.log('');
175
168
  }
176
- logDiff(diff) {
177
- if (!diff.hasChanges) {
178
- this.log(` No changes from last deployment.`);
179
- return;
180
- }
181
- this.log(` Changes vs. last deployment:`);
182
- for (const email of diff.added) {
183
- this.log(` ${chalk.green('+')} ${email.id.padEnd(24)} ${email.trigger || ''}`);
184
- }
185
- for (const email of diff.removed) {
186
- this.log(` ${chalk.red('-')} ${email.id.padEnd(24)} ${email.trigger || ''}`);
187
- }
188
- for (const email of diff.modified) {
189
- const fields = email.changedFields?.join(', ') || '';
190
- this.log(` ${chalk.yellow('~')} ${email.id.padEnd(24)} ${fields}`);
191
- }
192
- if (diff.unchanged.length > 0) {
193
- this.log(` ${chalk.dim(`∙ ${diff.unchanged.length} unchanged`)}`);
194
- }
195
- }
169
+ /**
170
+ * Prints the post-deploy success message and SDK install snippet for interactive runs.
171
+ */
196
172
  logDeploySuccessInstructions(sdkSnippet) {
197
173
  this.log(` ${chalk.green('Deployed.')} Emails are live.\n`);
198
174
  this.log(` ${'─'.repeat(53)}`);
@@ -209,6 +185,13 @@ export default class Deploy extends BaseCommand {
209
185
  this.log(` Full SDK docs: ${chalk.cyan('mailmodo.com/docs/sdk')}\n`);
210
186
  this.log(` ${'─'.repeat(53)}\n`);
211
187
  }
188
+ /**
189
+ * Interactive domain setup flow. Collects domain, sender email, and business
190
+ * address from the user, then calls the API to get DNS records to configure.
191
+ * Polls for verification when the user indicates they've added the records.
192
+ *
193
+ * @returns {Promise<boolean>} true if domain was verified, false if skipped.
194
+ */
212
195
  async runDomainSetup(yamlConfig, flags) {
213
196
  const { address, domain, senderEmail } = await this.collectDomainInputs(yamlConfig, flags);
214
197
  const domainResponse = await this.withApiSpinner({ json: flags.json, text: ' Configuring domain...' }, () => this.apiClient.post(API_ENDPOINTS.DOMAIN, {
@@ -277,6 +260,12 @@ export default class Deploy extends BaseCommand {
277
260
  this.log(` DNS changes take 5–30 minutes to propagate.`);
278
261
  this.log(` Full guide: ${chalk.cyan(DNS_GUIDE_URL)}\n`);
279
262
  }
263
+ /**
264
+ * Calls the domain verification API endpoint and reports pass/fail
265
+ * status for each DNS record (DKIM, DMARC, Return-Path).
266
+ *
267
+ * @returns {Promise<boolean>} true if all records pass.
268
+ */
280
269
  async verifyDomain(jsonOutput, domain) {
281
270
  const verify = await this.withApiSpinner({ json: jsonOutput, text: ' Checking DNS...' }, () => this.apiClient.get(API_ENDPOINTS.DOMAIN_VERIFY, {
282
271
  domain,
@@ -179,7 +179,7 @@ export default class Domain extends BaseCommand {
179
179
  this.log(` Spam rate: ${data.spamRate ?? 'N/A'}%\n`);
180
180
  }
181
181
  recordLabel(index) {
182
- const labels = ['DKIM', 'DMARC', 'Return Path'];
182
+ const labels = ['SPF', 'DKIM', 'DMARC'];
183
183
  return labels[index] || `Record ${index + 1}`;
184
184
  }
185
185
  }
@@ -194,8 +194,6 @@ export default class Edit extends BaseCommand {
194
194
  email.subject = updated.subject;
195
195
  if (updated.previewText)
196
196
  email.previewText = updated.previewText;
197
- if (updated.ctaText)
198
- email.ctaText = updated.ctaText;
199
197
  const updatedYaml = {
200
198
  ...yamlConfig,
201
199
  emails: [...yamlConfig.emails],
@@ -128,7 +128,6 @@ export default class Init extends BaseCommand {
128
128
  ...(generated?.previewText
129
129
  ? { previewText: generated.previewText }
130
130
  : {}),
131
- ...(generated?.ctaText ? { ctaText: generated.ctaText } : {}),
132
131
  goal: rec.goal,
133
132
  };
134
133
  });
@@ -136,18 +135,14 @@ export default class Init extends BaseCommand {
136
135
  emails: emailConfigs,
137
136
  project: {
138
137
  brandColor: analysisPayload.brand?.color || DEFAULT_BRAND_COLOR,
139
- description: analysisPayload.description,
140
138
  emailStyle: 'branded',
141
139
  fromEmail: '',
142
140
  fromName: `Team ${analysisPayload.productName}`,
143
141
  logoUrl: analysisPayload.brand?.logoUrl || '',
144
142
  monthlyCap: DEFAULT_MONTHLY_CAP,
145
143
  name: analysisPayload.productName,
146
- pricingModel: analysisPayload.pricingModel,
147
144
  replyTo: '',
148
- saasModel: analysisPayload.saasModel,
149
- targetUser: analysisPayload.targetUser,
150
- type: analysisPayload.businessType,
145
+ type: analysisPayload.pricingModel,
151
146
  url: productUrl,
152
147
  webhookUrl: '',
153
148
  },
@@ -233,7 +233,7 @@ export default class Settings extends BaseCommand {
233
233
  this.log(` Help: ${chalk.cyan(DNS_GUIDE_URL)}\n`);
234
234
  }
235
235
  recordLabel(index) {
236
- const labels = ['DKIM', 'DMARC', 'Return Path'];
236
+ const labels = ['SPF', 'DKIM', 'DMARC'];
237
237
  return labels[index] || `Record ${index + 1}`;
238
238
  }
239
239
  /**
@@ -1,6 +1,5 @@
1
1
  export interface EmailConfig {
2
2
  condition?: string;
3
- ctaText?: string;
4
3
  delay: number | string;
5
4
  goal?: string;
6
5
  id: string;
@@ -13,7 +12,6 @@ export interface EmailConfig {
13
12
  export interface ProjectConfig {
14
13
  address?: string;
15
14
  brandColor?: string;
16
- description?: string;
17
15
  domain?: string;
18
16
  emailStyle?: 'branded' | 'plain';
19
17
  fromEmail?: string;
@@ -22,10 +20,7 @@ export interface ProjectConfig {
22
20
  logoUrl?: string;
23
21
  monthlyCap?: number;
24
22
  name?: string;
25
- pricingModel?: string;
26
23
  replyTo?: string;
27
- saasModel?: string;
28
- targetUser?: string;
29
24
  type?: string;
30
25
  url?: string;
31
26
  webhookUrl?: string;
@@ -342,12 +342,13 @@
342
342
  "index.js"
343
343
  ]
344
344
  },
345
- "logout": {
345
+ "login": {
346
346
  "aliases": [],
347
347
  "args": {},
348
- "description": "Sign out by removing saved credentials from this machine",
348
+ "description": "Authenticate with Mailmodo using your API key",
349
349
  "examples": [
350
- "<%= config.bin %> logout"
350
+ "<%= config.bin %> login",
351
+ "MAILMODO_API_KEY=mm_live_xxx <%= config.bin %> login"
351
352
  ],
352
353
  "flags": {
353
354
  "json": {
@@ -366,7 +367,7 @@
366
367
  },
367
368
  "hasDynamicHelp": false,
368
369
  "hiddenAliases": [],
369
- "id": "logout",
370
+ "id": "login",
370
371
  "pluginAlias": "@mailmodo/cli",
371
372
  "pluginName": "@mailmodo/cli",
372
373
  "pluginType": "core",
@@ -376,19 +377,16 @@
376
377
  "relativePath": [
377
378
  "dist",
378
379
  "commands",
379
- "logout",
380
+ "login",
380
381
  "index.js"
381
382
  ]
382
383
  },
383
- "logs": {
384
+ "logout": {
384
385
  "aliases": [],
385
386
  "args": {},
386
- "description": "View email send logs and delivery events",
387
+ "description": "Sign out by removing saved credentials from this machine",
387
388
  "examples": [
388
- "<%= config.bin %> logs",
389
- "<%= config.bin %> logs --email sarah@example.com",
390
- "<%= config.bin %> logs --failed",
391
- "<%= config.bin %> logs --json"
389
+ "<%= config.bin %> logout"
392
390
  ],
393
391
  "flags": {
394
392
  "json": {
@@ -403,24 +401,11 @@
403
401
  "name": "yes",
404
402
  "allowNo": false,
405
403
  "type": "boolean"
406
- },
407
- "email": {
408
- "description": "Filter logs by contact email",
409
- "name": "email",
410
- "hasDynamicHelp": false,
411
- "multiple": false,
412
- "type": "option"
413
- },
414
- "failed": {
415
- "description": "Show only failed/bounced events",
416
- "name": "failed",
417
- "allowNo": false,
418
- "type": "boolean"
419
404
  }
420
405
  },
421
406
  "hasDynamicHelp": false,
422
407
  "hiddenAliases": [],
423
- "id": "logs",
408
+ "id": "logout",
424
409
  "pluginAlias": "@mailmodo/cli",
425
410
  "pluginName": "@mailmodo/cli",
426
411
  "pluginType": "core",
@@ -430,17 +415,19 @@
430
415
  "relativePath": [
431
416
  "dist",
432
417
  "commands",
433
- "logs",
418
+ "logout",
434
419
  "index.js"
435
420
  ]
436
421
  },
437
- "login": {
422
+ "logs": {
438
423
  "aliases": [],
439
424
  "args": {},
440
- "description": "Authenticate with Mailmodo using your API key",
425
+ "description": "View email send logs and delivery events",
441
426
  "examples": [
442
- "<%= config.bin %> login",
443
- "MAILMODO_API_KEY=mm_live_xxx <%= config.bin %> login"
427
+ "<%= config.bin %> logs",
428
+ "<%= config.bin %> logs --email sarah@example.com",
429
+ "<%= config.bin %> logs --failed",
430
+ "<%= config.bin %> logs --json"
444
431
  ],
445
432
  "flags": {
446
433
  "json": {
@@ -455,11 +442,24 @@
455
442
  "name": "yes",
456
443
  "allowNo": false,
457
444
  "type": "boolean"
445
+ },
446
+ "email": {
447
+ "description": "Filter logs by contact email",
448
+ "name": "email",
449
+ "hasDynamicHelp": false,
450
+ "multiple": false,
451
+ "type": "option"
452
+ },
453
+ "failed": {
454
+ "description": "Show only failed/bounced events",
455
+ "name": "failed",
456
+ "allowNo": false,
457
+ "type": "boolean"
458
458
  }
459
459
  },
460
460
  "hasDynamicHelp": false,
461
461
  "hiddenAliases": [],
462
- "id": "login",
462
+ "id": "logs",
463
463
  "pluginAlias": "@mailmodo/cli",
464
464
  "pluginName": "@mailmodo/cli",
465
465
  "pluginType": "core",
@@ -469,7 +469,7 @@
469
469
  "relativePath": [
470
470
  "dist",
471
471
  "commands",
472
- "login",
472
+ "logs",
473
473
  "index.js"
474
474
  ]
475
475
  },
@@ -618,5 +618,5 @@
618
618
  ]
619
619
  }
620
620
  },
621
- "version": "0.0.21-beta.pr24.38"
621
+ "version": "0.0.21"
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.21-beta.pr24.38",
4
+ "version": "0.0.21",
5
5
  "author": "provishalk",
6
6
  "bin": {
7
7
  "mailmodo": "bin/run.js"