@cakemail-org/cakemail-cli 1.5.0 → 2.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.
Files changed (234) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.env.example +40 -0
  3. package/.env.test.example +45 -0
  4. package/CHANGELOG.md +1031 -0
  5. package/README.md +319 -15
  6. package/audit-formats.js +128 -0
  7. package/cakemail.rb +20 -0
  8. package/dist/cli.js +27 -10
  9. package/dist/cli.js.map +1 -1
  10. package/dist/client.d.ts +2 -0
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +16 -6
  13. package/dist/client.js.map +1 -1
  14. package/dist/commands/account.js +1 -1
  15. package/dist/commands/account.js.map +1 -1
  16. package/dist/commands/attributes.js +1 -1
  17. package/dist/commands/attributes.js.map +1 -1
  18. package/dist/commands/campaigns.d.ts.map +1 -1
  19. package/dist/commands/campaigns.js +103 -8
  20. package/dist/commands/campaigns.js.map +1 -1
  21. package/dist/commands/config.d.ts.map +1 -1
  22. package/dist/commands/config.js +63 -4
  23. package/dist/commands/config.js.map +1 -1
  24. package/dist/commands/contacts.d.ts.map +1 -1
  25. package/dist/commands/contacts.js +91 -12
  26. package/dist/commands/contacts.js.map +1 -1
  27. package/dist/commands/emails.js +1 -1
  28. package/dist/commands/emails.js.map +1 -1
  29. package/dist/commands/interests.d.ts +5 -0
  30. package/dist/commands/interests.d.ts.map +1 -0
  31. package/dist/commands/interests.js +172 -0
  32. package/dist/commands/interests.js.map +1 -0
  33. package/dist/commands/lists.d.ts.map +1 -1
  34. package/dist/commands/lists.js +6 -8
  35. package/dist/commands/lists.js.map +1 -1
  36. package/dist/commands/logs.d.ts +5 -0
  37. package/dist/commands/logs.d.ts.map +1 -0
  38. package/dist/commands/logs.js +237 -0
  39. package/dist/commands/logs.js.map +1 -0
  40. package/dist/commands/reports.js +1 -1
  41. package/dist/commands/reports.js.map +1 -1
  42. package/dist/commands/segments.js +1 -1
  43. package/dist/commands/segments.js.map +1 -1
  44. package/dist/commands/senders.d.ts.map +1 -1
  45. package/dist/commands/senders.js +11 -8
  46. package/dist/commands/senders.js.map +1 -1
  47. package/dist/commands/suppressed.js +1 -1
  48. package/dist/commands/suppressed.js.map +1 -1
  49. package/dist/commands/tags.d.ts +5 -0
  50. package/dist/commands/tags.d.ts.map +1 -0
  51. package/dist/commands/tags.js +124 -0
  52. package/dist/commands/tags.js.map +1 -0
  53. package/dist/commands/templates.js +1 -1
  54. package/dist/commands/templates.js.map +1 -1
  55. package/dist/commands/transactional-templates.d.ts +5 -0
  56. package/dist/commands/transactional-templates.d.ts.map +1 -0
  57. package/dist/commands/transactional-templates.js +354 -0
  58. package/dist/commands/transactional-templates.js.map +1 -0
  59. package/dist/commands/webhooks.js +1 -1
  60. package/dist/commands/webhooks.js.map +1 -1
  61. package/dist/utils/auth.d.ts +8 -1
  62. package/dist/utils/auth.d.ts.map +1 -1
  63. package/dist/utils/auth.js +39 -11
  64. package/dist/utils/auth.js.map +1 -1
  65. package/dist/utils/config-file.d.ts +7 -0
  66. package/dist/utils/config-file.d.ts.map +1 -1
  67. package/dist/utils/config-file.js +15 -0
  68. package/dist/utils/config-file.js.map +1 -1
  69. package/dist/utils/config.d.ts +2 -0
  70. package/dist/utils/config.d.ts.map +1 -1
  71. package/dist/utils/config.js +12 -4
  72. package/dist/utils/config.js.map +1 -1
  73. package/dist/utils/errors.js +1 -1
  74. package/dist/utils/errors.js.map +1 -1
  75. package/dist/utils/list-defaults.d.ts +33 -0
  76. package/dist/utils/list-defaults.d.ts.map +1 -0
  77. package/dist/utils/list-defaults.js +52 -0
  78. package/dist/utils/list-defaults.js.map +1 -0
  79. package/dist/utils/output.d.ts.map +1 -1
  80. package/dist/utils/output.js +36 -13
  81. package/dist/utils/output.js.map +1 -1
  82. package/dist/utils/progress.d.ts.map +1 -1
  83. package/dist/utils/progress.js +32 -4
  84. package/dist/utils/progress.js.map +1 -1
  85. package/dist/utils/spinner.d.ts +17 -0
  86. package/dist/utils/spinner.d.ts.map +1 -0
  87. package/dist/utils/spinner.js +43 -0
  88. package/dist/utils/spinner.js.map +1 -0
  89. package/docs/DOCUMENTATION-STANDARD.md +1068 -0
  90. package/docs/README.md +161 -0
  91. package/docs/developer/ARCHITECTURE.md +516 -0
  92. package/docs/developer/AUTH.md +204 -0
  93. package/docs/developer/CONTRIBUTING.md +227 -0
  94. package/docs/developer/DOCUMENTATION_SUMMARY.md +346 -0
  95. package/docs/developer/PROJECT_INDEX.md +365 -0
  96. package/docs/planning/API_COVERAGE.md +1045 -0
  97. package/docs/planning/BACKLOG.md +1159 -0
  98. package/docs/planning/PROFILE_SYSTEM_TASKS.md +287 -0
  99. package/docs/planning/UX_IMPLEMENTATION_PLAN.md +691 -0
  100. package/docs/planning/archive/RELEASE_CHECKLIST_v1.3.0.md +332 -0
  101. package/docs/planning/archive/RELEASE_v1.3.0.md +428 -0
  102. package/docs/planning/archive/cakemail-cli-ux-improvements.md +438 -0
  103. package/docs/planning/cakemail-profile-system-plan.md +1121 -0
  104. package/docs/testing/AI_USER_SIMULATION_DESIGN.md +1342 -0
  105. package/docs/testing/KENOGAMI_BIDIRECTIONAL_FLOW.md +1517 -0
  106. package/docs/testing/KENOGAMI_TRUTH_RECONCILIATION_SYSTEM.md +1369 -0
  107. package/docs/user-manual/.obsidian/app.json +1 -0
  108. package/docs/user-manual/.obsidian/appearance.json +1 -0
  109. package/docs/user-manual/.obsidian/core-plugins.json +33 -0
  110. package/docs/user-manual/.obsidian/workspace.json +167 -0
  111. package/docs/user-manual/01-getting-started/01-installation.md +214 -0
  112. package/docs/user-manual/01-getting-started/02-quick-start.md +432 -0
  113. package/docs/user-manual/01-getting-started/03-authentication.md +448 -0
  114. package/docs/user-manual/01-getting-started/04-configuration.md +430 -0
  115. package/docs/user-manual/01-getting-started/05-output-formats.md +447 -0
  116. package/docs/user-manual/02-core-concepts/01-accounts.md +514 -0
  117. package/docs/user-manual/02-core-concepts/02-profile-system.md +771 -0
  118. package/docs/user-manual/02-core-concepts/03-smart-defaults.md +485 -0
  119. package/docs/user-manual/02-core-concepts/04-authentication-methods.md +435 -0
  120. package/docs/user-manual/02-core-concepts/05-pagination-filtering.md +600 -0
  121. package/docs/user-manual/02-core-concepts/06-error-handling.md +718 -0
  122. package/docs/user-manual/02-core-concepts/07-api-coverage.md +483 -0
  123. package/docs/user-manual/03-email-operations/01-senders.md +490 -0
  124. package/docs/user-manual/03-email-operations/02-templates.md +444 -0
  125. package/docs/user-manual/03-email-operations/03-transactional-emails.md +706 -0
  126. package/docs/user-manual/03-email-operations/04-email-tracking.md +407 -0
  127. package/docs/user-manual/04-campaign-management/01-campaigns-basics.md +394 -0
  128. package/docs/user-manual/04-campaign-management/02-campaign-scheduling.md +630 -0
  129. package/docs/user-manual/04-campaign-management/03-campaign-testing.md +997 -0
  130. package/docs/user-manual/04-campaign-management/04-campaign-lifecycle.md +709 -0
  131. package/docs/user-manual/04-campaign-management/05-campaign-links.md +934 -0
  132. package/docs/user-manual/05-contact-management/01-lists.md +836 -0
  133. package/docs/user-manual/05-contact-management/02-contacts.md +1035 -0
  134. package/docs/user-manual/05-contact-management/03-custom-attributes.md +788 -0
  135. package/docs/user-manual/05-contact-management/04-segments.md +1028 -0
  136. package/docs/user-manual/05-contact-management/05-contact-import-export.md +1031 -0
  137. package/docs/user-manual/06-analytics-reporting/01-campaign-analytics.md +867 -0
  138. package/docs/user-manual/06-analytics-reporting/02-account-reports.md +227 -0
  139. package/docs/user-manual/07-integrations/01-webhooks-integration.md +259 -0
  140. package/docs/user-manual/07-integrations/02-automation.md +326 -0
  141. package/docs/user-manual/08-advanced-usage/01-scripting-patterns.md +672 -0
  142. package/docs/user-manual/08-advanced-usage/02-bulk-operations.md +932 -0
  143. package/docs/user-manual/08-advanced-usage/03-ci-cd-integration.md +892 -0
  144. package/docs/user-manual/08-advanced-usage/04-performance-optimization.md +766 -0
  145. package/docs/user-manual/09-command-reference/01-config.md +776 -0
  146. package/docs/user-manual/09-command-reference/02-account.md +652 -0
  147. package/docs/user-manual/09-command-reference/03-lists.md +958 -0
  148. package/docs/user-manual/09-command-reference/04-contacts.md +1408 -0
  149. package/docs/user-manual/09-command-reference/05-attributes.md +617 -0
  150. package/docs/user-manual/09-command-reference/06-segments.md +894 -0
  151. package/docs/user-manual/09-command-reference/07-senders.md +803 -0
  152. package/docs/user-manual/09-command-reference/08-templates.md +818 -0
  153. package/docs/user-manual/09-command-reference/09-campaigns.md +1250 -0
  154. package/docs/user-manual/09-command-reference/10-emails.md +807 -0
  155. package/docs/user-manual/09-command-reference/11-reports.md +1135 -0
  156. package/docs/user-manual/09-command-reference/12-webhooks.md +773 -0
  157. package/docs/user-manual/09-command-reference/13-suppressed.md +797 -0
  158. package/docs/user-manual/09-command-reference/14-interests.md +630 -0
  159. package/docs/user-manual/09-command-reference/15-tags.md +584 -0
  160. package/docs/user-manual/09-command-reference/16-logs.md +656 -0
  161. package/docs/user-manual/09-command-reference/17-transactional-templates.md +850 -0
  162. package/docs/user-manual/10-troubleshooting/01-common-errors.md +457 -0
  163. package/docs/user-manual/10-troubleshooting/02-authentication-issues.md +558 -0
  164. package/docs/user-manual/10-troubleshooting/03-connection-problems.md +634 -0
  165. package/docs/user-manual/10-troubleshooting/04-debugging.md +725 -0
  166. package/docs/user-manual/11-appendix/04-faq.md +484 -0
  167. package/docs/user-manual/11-appendix/05-glossary.md +250 -0
  168. package/docs/user-manual/README.md +0 -0
  169. package/package.json +13 -47
  170. package/src/cli.ts +125 -0
  171. package/src/client.ts +16 -0
  172. package/src/commands/account.ts +267 -0
  173. package/src/commands/accounts.ts +78 -0
  174. package/src/commands/actions.ts +249 -0
  175. package/src/commands/attributes.ts +139 -0
  176. package/src/commands/campaign-blueprints.ts +106 -0
  177. package/src/commands/campaigns.ts +469 -0
  178. package/src/commands/config.ts +77 -0
  179. package/src/commands/contacts.ts +612 -0
  180. package/src/commands/custom-attributes.ts +127 -0
  181. package/src/commands/dkims.ts +117 -0
  182. package/src/commands/domains.ts +82 -0
  183. package/src/commands/email-apis.ts +569 -0
  184. package/src/commands/emails.ts +197 -0
  185. package/src/commands/forms.ts +283 -0
  186. package/src/commands/interests.ts +155 -0
  187. package/src/commands/links.ts +38 -0
  188. package/src/commands/lists.ts +406 -0
  189. package/src/commands/logos.ts +71 -0
  190. package/src/commands/logs.ts +386 -0
  191. package/src/commands/reports.ts +306 -0
  192. package/src/commands/segments.ts +158 -0
  193. package/src/commands/senders.ts +204 -0
  194. package/src/commands/sub-accounts.ts +271 -0
  195. package/src/commands/suppressed-emails.ts +234 -0
  196. package/src/commands/suppressed.ts +198 -0
  197. package/src/commands/system-emails.ts +85 -0
  198. package/src/commands/tags.ts +146 -0
  199. package/src/commands/tasks.ts +116 -0
  200. package/src/commands/templates.ts +189 -0
  201. package/src/commands/tokens.ts +83 -0
  202. package/src/commands/transactional-emails.ts +374 -0
  203. package/src/commands/transactional-templates.ts +385 -0
  204. package/src/commands/users.ts +506 -0
  205. package/src/commands/webhooks.ts +172 -0
  206. package/src/commands/workflow-blueprints.ts +123 -0
  207. package/src/commands/workflows.ts +265 -0
  208. package/src/types/profile.ts +93 -0
  209. package/src/utils/auth.ts +272 -0
  210. package/src/utils/config-file.ts +96 -0
  211. package/src/utils/config.ts +134 -0
  212. package/src/utils/confirm.ts +32 -0
  213. package/src/utils/defaults.ts +99 -0
  214. package/src/utils/errors.ts +116 -0
  215. package/src/utils/interactive.ts +91 -0
  216. package/src/utils/list-defaults.ts +74 -0
  217. package/src/utils/output.ts +190 -0
  218. package/src/utils/progress.ts +320 -0
  219. package/src/utils/spinner.ts +22 -0
  220. package/tests/IMPLEMENTATION_STATUS.md +258 -0
  221. package/tests/PTY_SETUP.md +118 -0
  222. package/tests/PTY_TESTING_GUIDE.md +507 -0
  223. package/tests/README.md +244 -0
  224. package/tests/fixtures/api-responses/campaigns.json +34 -0
  225. package/tests/fixtures/test-config.json +13 -0
  226. package/tests/helpers/cli-runner.ts +128 -0
  227. package/tests/helpers/mock-server.ts +301 -0
  228. package/tests/helpers/pty-runner.ts +181 -0
  229. package/tests/integration/campaigns-real-api.test.ts +196 -0
  230. package/tests/integration/setup-integration.ts +50 -0
  231. package/tests/pty/campaigns.test.ts +241 -0
  232. package/tests/setup.ts +34 -0
  233. package/tsconfig.json +15 -0
  234. package/vitest.config.ts +28 -0
@@ -0,0 +1,385 @@
1
+ import { Command } from 'commander';
2
+ import { CakemailClient } from '../client.js';
3
+ import { OutputFormatter } from '../utils/output.js';
4
+ import ora from '../utils/spinner.js';
5
+ import { displayError, validate } from '../utils/errors.js';
6
+ import { confirmDelete } from '../utils/confirm.js';
7
+ import { autoDetectList } from '../utils/defaults.js';
8
+
9
+ export function createTransactionalTemplatesCommand(
10
+ client: CakemailClient,
11
+ formatter: OutputFormatter
12
+ ): Command {
13
+ const templates = new Command('transactional-templates')
14
+ .description('Manage transactional email templates');
15
+
16
+ // List transactional templates
17
+ templates
18
+ .command('list [list-id]')
19
+ .description('List transactional email templates (auto-detects if only one list exists)')
20
+ .option('-p, --page <number>', 'Page number')
21
+ .option('--per-page <number>', 'Results per page')
22
+ .option('--with-count', 'Include total count')
23
+ .action(async (listId, options) => {
24
+ // Auto-detect list ID if not provided
25
+ const detectedListId = await autoDetectList(client, formatter, listId, { useCache: true });
26
+
27
+ if (!detectedListId) {
28
+ process.exit(1);
29
+ }
30
+
31
+ const spinner = ora('Fetching transactional templates...').start();
32
+ try {
33
+ const params: any = {
34
+ listId: detectedListId
35
+ };
36
+ if (options.page) params.page = parseInt(options.page);
37
+ if (options.perPage) params.perPage = parseInt(options.perPage);
38
+ if (options.withCount) params.withCount = true;
39
+
40
+ const data = await client.sdk.transactionalEmailService.listTransactionalEmailTemplates(params);
41
+ spinner.stop();
42
+ formatter.output(data);
43
+ } catch (error: any) {
44
+ spinner.stop();
45
+ displayError(error, {
46
+ command: 'transactional-templates list',
47
+ operation: 'list templates'
48
+ });
49
+ process.exit(1);
50
+ }
51
+ });
52
+
53
+ // Show transactional template
54
+ templates
55
+ .command('show [list-id] <template-id>')
56
+ .description('Show transactional email template details (auto-detects if only one list exists)')
57
+ .action(async (listId, templateId) => {
58
+ // Auto-detect list ID if not provided
59
+ const detectedListId = await autoDetectList(client, formatter, listId, { useCache: true });
60
+
61
+ if (!detectedListId) {
62
+ process.exit(1);
63
+ }
64
+
65
+ const spinner = ora(`Fetching template ${templateId}...`).start();
66
+ try {
67
+ const data = await client.sdk.transactionalEmailService.showTransactionalEmailTemplate({
68
+ listId: detectedListId,
69
+ transactionalEmailTemplateId: parseInt(templateId)
70
+ });
71
+
72
+ spinner.stop();
73
+ formatter.output(data);
74
+ } catch (error: any) {
75
+ spinner.stop();
76
+ displayError(error, {
77
+ command: 'transactional-templates show',
78
+ resource: 'template',
79
+ resourceId: templateId,
80
+ operation: 'fetch'
81
+ });
82
+ process.exit(1);
83
+ }
84
+ });
85
+
86
+ // Create transactional template
87
+ templates
88
+ .command('create [list-id]')
89
+ .description('Create a transactional email template (auto-detects if only one list exists)')
90
+ .requiredOption('-n, --name <name>', 'Template name')
91
+ .requiredOption('-s, --subject <subject>', 'Email subject')
92
+ .requiredOption('--html <html>', 'HTML content')
93
+ .option('--text <text>', 'Plain text content')
94
+ .option('--sender-id <id>', 'Sender ID')
95
+ .option('--tracking <tracking>', 'Tracking settings (open, click)')
96
+ .action(async (listId, options) => {
97
+ // Auto-detect list ID if not provided
98
+ const detectedListId = await autoDetectList(client, formatter, listId, { useCache: true });
99
+
100
+ if (!detectedListId) {
101
+ process.exit(1);
102
+ }
103
+
104
+ const spinner = ora('Creating transactional template...').start();
105
+ try {
106
+ const payload: any = {
107
+ name: options.name,
108
+ subject: options.subject,
109
+ html: options.html
110
+ };
111
+ if (options.text) payload.text = options.text;
112
+ if (options.senderId) payload.sender_id = parseInt(options.senderId);
113
+ if (options.tracking) payload.tracking = options.tracking;
114
+
115
+ const data = await client.sdk.transactionalEmailService.createTransactionalEmailTemplate({
116
+ listId: detectedListId,
117
+ requestBody: payload
118
+ });
119
+
120
+ spinner.stop();
121
+ formatter.success('Transactional template created successfully');
122
+ formatter.output(data);
123
+ } catch (error: any) {
124
+ spinner.stop();
125
+ displayError(error, {
126
+ command: 'transactional-templates create',
127
+ operation: 'create template'
128
+ });
129
+ process.exit(1);
130
+ }
131
+ });
132
+
133
+ // Update transactional template
134
+ templates
135
+ .command('update [list-id] <template-id>')
136
+ .description('Update a transactional email template (auto-detects if only one list exists)')
137
+ .option('-n, --name <name>', 'Template name')
138
+ .option('-s, --subject <subject>', 'Email subject')
139
+ .option('--html <html>', 'HTML content')
140
+ .option('--text <text>', 'Plain text content')
141
+ .option('--sender-id <id>', 'Sender ID')
142
+ .option('--tracking <tracking>', 'Tracking settings')
143
+ .action(async (listId, templateId, options) => {
144
+ // Auto-detect list ID if not provided
145
+ const detectedListId = await autoDetectList(client, formatter, listId, { useCache: true });
146
+
147
+ if (!detectedListId) {
148
+ process.exit(1);
149
+ }
150
+
151
+ const spinner = ora(`Updating template ${templateId}...`).start();
152
+ try {
153
+ const payload: any = {};
154
+ if (options.name) payload.name = options.name;
155
+ if (options.subject) payload.subject = options.subject;
156
+ if (options.html) payload.html = options.html;
157
+ if (options.text) payload.text = options.text;
158
+ if (options.senderId) payload.sender_id = parseInt(options.senderId);
159
+ if (options.tracking) payload.tracking = options.tracking;
160
+
161
+ const data = await client.sdk.transactionalEmailService.updateTransactionalEmailTemplate({
162
+ listId: detectedListId,
163
+ transactionalEmailTemplateId: parseInt(templateId),
164
+ requestBody: payload
165
+ });
166
+
167
+ spinner.stop();
168
+ formatter.success('Template updated successfully');
169
+ formatter.output(data);
170
+ } catch (error: any) {
171
+ spinner.stop();
172
+ displayError(error, {
173
+ command: 'transactional-templates update',
174
+ resource: 'template',
175
+ resourceId: templateId,
176
+ operation: 'update'
177
+ });
178
+ process.exit(1);
179
+ }
180
+ });
181
+
182
+ // Delete transactional template
183
+ templates
184
+ .command('delete [list-id] <template-id>')
185
+ .description('Delete a transactional email template (auto-detects if only one list exists)')
186
+ .option('-f, --force', 'Skip confirmation prompt')
187
+ .action(async (listId, templateId, options) => {
188
+ // Auto-detect list ID if not provided
189
+ const detectedListId = await autoDetectList(client, formatter, listId, { useCache: true });
190
+
191
+ if (!detectedListId) {
192
+ process.exit(1);
193
+ }
194
+
195
+ // Interactive confirmation (unless --force is used)
196
+ if (!options.force) {
197
+ const confirmed = await confirmDelete('transactional template', templateId, [
198
+ 'Template will be permanently deleted',
199
+ 'This action cannot be undone'
200
+ ]);
201
+
202
+ if (!confirmed) {
203
+ formatter.info('Deletion cancelled');
204
+ return;
205
+ }
206
+ }
207
+
208
+ const spinner = ora(`Deleting template ${templateId}...`).start();
209
+ try {
210
+ await client.sdk.transactionalEmailService.deleteTransactionalEmailTemplate({
211
+ listId: detectedListId,
212
+ transactionalEmailTemplateId: parseInt(templateId)
213
+ });
214
+
215
+ spinner.stop();
216
+ formatter.success('Template deleted successfully');
217
+ } catch (error: any) {
218
+ spinner.stop();
219
+ displayError(error, {
220
+ command: 'transactional-templates delete',
221
+ resource: 'template',
222
+ resourceId: templateId,
223
+ operation: 'delete'
224
+ });
225
+ process.exit(1);
226
+ }
227
+ });
228
+
229
+ // Send transactional email
230
+ templates
231
+ .command('send [list-id] <template-id>')
232
+ .description('Send a transactional email from template (auto-detects if only one list exists)')
233
+ .option('-c, --contact-id <id>', 'Contact ID to send to')
234
+ .option('-e, --email <email>', 'Email address to send to (alternative to contact-id)')
235
+ .option('--variables <json>', 'Template variables as JSON')
236
+ .action(async (listId, templateId, options) => {
237
+ // Auto-detect list ID if not provided
238
+ const detectedListId = await autoDetectList(client, formatter, listId, { useCache: true });
239
+
240
+ if (!detectedListId) {
241
+ process.exit(1);
242
+ }
243
+
244
+ // Validate that either contact-id or email is provided
245
+ if (!options.contactId && !options.email) {
246
+ formatter.error('Either --contact-id or --email is required');
247
+ process.exit(1);
248
+ }
249
+
250
+ const spinner = ora('Sending transactional email...').start();
251
+ try {
252
+ const payload: any = {};
253
+
254
+ if (options.contactId) {
255
+ payload.contact_id = parseInt(options.contactId);
256
+ } else if (options.email) {
257
+ payload.email = options.email;
258
+ }
259
+
260
+ if (options.variables) {
261
+ payload.variables = JSON.parse(options.variables);
262
+ }
263
+
264
+ const data = await client.sdk.transactionalEmailService.sendTransactionalEmail({
265
+ listId: detectedListId,
266
+ transactionalEmailTemplateId: parseInt(templateId),
267
+ requestBody: payload
268
+ });
269
+
270
+ spinner.stop();
271
+ formatter.success('Transactional email sent successfully');
272
+ formatter.output(data);
273
+ } catch (error: any) {
274
+ spinner.stop();
275
+ displayError(error, {
276
+ command: 'transactional-templates send',
277
+ resource: 'template',
278
+ resourceId: templateId,
279
+ operation: 'send email'
280
+ });
281
+ process.exit(1);
282
+ }
283
+ });
284
+
285
+ // Send test email
286
+ templates
287
+ .command('test [list-id] <template-id>')
288
+ .description('Send a test transactional email (auto-detects if only one list exists)')
289
+ .requiredOption('-e, --email <email>', 'Email address to send test to')
290
+ .option('--variables <json>', 'Template variables as JSON')
291
+ .action(async (listId, templateId, options) => {
292
+ // Auto-detect list ID if not provided
293
+ const detectedListId = await autoDetectList(client, formatter, listId, { useCache: true });
294
+
295
+ if (!detectedListId) {
296
+ process.exit(1);
297
+ }
298
+
299
+ // Validate email
300
+ const emailValidation = validate.email(options.email);
301
+ if (!emailValidation.valid) {
302
+ formatter.error(emailValidation.error!);
303
+ process.exit(1);
304
+ }
305
+
306
+ const spinner = ora('Sending test email...').start();
307
+ try {
308
+ const payload: any = {
309
+ email: options.email
310
+ };
311
+
312
+ if (options.variables) {
313
+ payload.variables = JSON.parse(options.variables);
314
+ }
315
+
316
+ const data = await client.sdk.transactionalEmailService.sendTestTransactionalEmail({
317
+ listId: detectedListId,
318
+ transactionalEmailTemplateId: parseInt(templateId),
319
+ requestBody: payload
320
+ });
321
+
322
+ spinner.stop();
323
+ formatter.success(`Test email sent to ${options.email}`);
324
+ formatter.output(data);
325
+ } catch (error: any) {
326
+ spinner.stop();
327
+ displayError(error, {
328
+ command: 'transactional-templates test',
329
+ resource: 'template',
330
+ resourceId: templateId,
331
+ operation: 'send test'
332
+ });
333
+ process.exit(1);
334
+ }
335
+ });
336
+
337
+ // Render template
338
+ templates
339
+ .command('render [list-id] <template-id>')
340
+ .description('Render a transactional email template (auto-detects if only one list exists)')
341
+ .option('-c, --contact-id <id>', 'Contact ID for personalization')
342
+ .option('--variables <json>', 'Template variables as JSON')
343
+ .action(async (listId, templateId, options) => {
344
+ // Auto-detect list ID if not provided
345
+ const detectedListId = await autoDetectList(client, formatter, listId, { useCache: true });
346
+
347
+ if (!detectedListId) {
348
+ process.exit(1);
349
+ }
350
+
351
+ const spinner = ora('Rendering template...').start();
352
+ try {
353
+ const payload: any = {};
354
+
355
+ if (options.contactId) {
356
+ payload.contact_id = parseInt(options.contactId);
357
+ }
358
+
359
+ if (options.variables) {
360
+ payload.variables = JSON.parse(options.variables);
361
+ }
362
+
363
+ const html = await client.sdk.transactionalEmailService.renderTransactionalEmailTemplate({
364
+ listId: detectedListId,
365
+ transactionalEmailTemplateId: parseInt(templateId),
366
+ requestBody: payload
367
+ });
368
+
369
+ spinner.stop();
370
+ formatter.success('Template rendered successfully');
371
+ formatter.output({ html, template_id: templateId, list_id: detectedListId });
372
+ } catch (error: any) {
373
+ spinner.stop();
374
+ displayError(error, {
375
+ command: 'transactional-templates render',
376
+ resource: 'template',
377
+ resourceId: templateId,
378
+ operation: 'render'
379
+ });
380
+ process.exit(1);
381
+ }
382
+ });
383
+
384
+ return templates;
385
+ }