@unboundcx/sdk 4.11.1 → 4.13.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/index.js CHANGED
@@ -15,6 +15,8 @@ import { WorkflowsService } from './services/workflows.js';
15
15
  import { NotesService } from './services/notes.js';
16
16
  import { StorageService } from './services/storage.js';
17
17
  import { BrandingService } from './services/branding.js';
18
+ import { BrandService } from './services/brand.js';
19
+ import { ContentService } from './services/content.js';
18
20
  import { VerificationService } from './services/verification.js';
19
21
  import { PortalsService } from './services/portals.js';
20
22
  import { SipEndpointsService } from './services/sipEndpoints.js';
@@ -93,6 +95,8 @@ class UnboundSDK extends BaseSDK {
93
95
  this.notes = new NotesService(this);
94
96
  this.storage = new StorageService(this);
95
97
  this.branding = new BrandingService(this);
98
+ this.brand = new BrandService(this);
99
+ this.content = new ContentService(this);
96
100
  this.verification = new VerificationService(this);
97
101
  this.portals = new PortalsService(this);
98
102
  this.sipEndpoints = new SipEndpointsService(this);
@@ -275,6 +279,13 @@ export { WorkflowsService } from './services/workflows.js';
275
279
  export { NotesService } from './services/notes.js';
276
280
  export { StorageService } from './services/storage.js';
277
281
  export { BrandingService, BrandingEmailTemplatesService } from './services/branding.js';
282
+ export { BrandService, BrandKitsService } from './services/brand.js';
283
+ export {
284
+ ContentService,
285
+ ContentBlocksService,
286
+ ContentLibraryService,
287
+ ContentStockService,
288
+ } from './services/content.js';
278
289
  export { VerificationService } from './services/verification.js';
279
290
  export { PortalsService } from './services/portals.js';
280
291
  export { SipEndpointsService } from './services/sipEndpoints.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboundcx/sdk",
3
- "version": "4.11.1",
3
+ "version": "4.13.0",
4
4
  "description": "Official JavaScript SDK for the Unbound API - A comprehensive toolkit for integrating with Unbound's communication, AI, and data management services",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -0,0 +1,116 @@
1
+ import { internalRequest } from '../../base.js';
2
+
3
+ function pickDefined(fields) {
4
+ const body = {};
5
+ for (const [key, value] of Object.entries(fields)) {
6
+ if (value !== undefined) body[key] = value;
7
+ }
8
+ return body;
9
+ }
10
+
11
+ /**
12
+ * Email generation via SpaceXAI. `sdk.ai.email.*`.
13
+ *
14
+ * @see app1-api src/services/ai/routes/email.js
15
+ */
16
+ export class EmailService {
17
+ constructor(sdk) {
18
+ this.sdk = sdk;
19
+ }
20
+
21
+ /**
22
+ * Generate or revise an email as a block-tree document.
23
+ *
24
+ * @param {Object} params
25
+ * @param {string} params.prompt - Generation prompt (required)
26
+ * @param {string} [params.brandKitId] - Brand kit to apply
27
+ * @param {string} [params.appearance] - `client` or `marketing`
28
+ * @param {Object|Array} [params.tree] - Existing block tree to revise
29
+ * @returns {Promise<Object>} `{ tree }`
30
+ */
31
+ async generate({ prompt, brandKitId, appearance, tree } = {}) {
32
+ this.sdk.validateParams(
33
+ { prompt, brandKitId, appearance, tree },
34
+ {
35
+ prompt: { type: 'string', required: true },
36
+ brandKitId: { type: 'string', required: false },
37
+ appearance: { type: 'string', required: false },
38
+ tree: { type: 'object', required: false },
39
+ },
40
+ );
41
+
42
+ return internalRequest(this.sdk, '/ai/email/generate', 'POST', {
43
+ body: pickDefined({ prompt, brandKitId, appearance, tree }),
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Rewrite email copy.
49
+ *
50
+ * @param {Object} params
51
+ * @param {string} params.text - Source copy (required)
52
+ * @param {string} params.instruction - `tone` | `shorten` | `expand` | `translate`
53
+ * @param {string} [params.lang] - Target language (required when `translate`)
54
+ * @returns {Promise<Object>} `{ text }`
55
+ */
56
+ async rewrite({ text, instruction, lang } = {}) {
57
+ this.sdk.validateParams(
58
+ { text, instruction, lang },
59
+ {
60
+ text: { type: 'string', required: true },
61
+ instruction: { type: 'string', required: true },
62
+ lang: { type: 'string', required: false },
63
+ },
64
+ );
65
+
66
+ return internalRequest(this.sdk, '/ai/email/rewrite', 'POST', {
67
+ body: pickDefined({ text, instruction, lang }),
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Generate subject + preheader variants.
73
+ *
74
+ * @param {Object} params
75
+ * @param {string} [params.html] - Rendered email HTML
76
+ * @param {string} [params.prompt] - Prompt describing the email
77
+ * @param {number} [params.n] - Variant count (1–5, default 3)
78
+ * @returns {Promise<Object>} `{ variants: [{ subject, preheader }] }`
79
+ */
80
+ async subjects({ html, prompt, n } = {}) {
81
+ this.sdk.validateParams(
82
+ { html, prompt, n },
83
+ {
84
+ html: { type: 'string', required: false },
85
+ prompt: { type: 'string', required: false },
86
+ n: { type: 'number', required: false },
87
+ },
88
+ );
89
+
90
+ return internalRequest(this.sdk, '/ai/email/subjects', 'POST', {
91
+ body: pickDefined({ html, prompt, n }),
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Generate image alt text.
97
+ *
98
+ * @param {Object} params
99
+ * @param {string} [params.imageUrl] - https or data:image URL
100
+ * @param {string} [params.description] - Image description
101
+ * @returns {Promise<Object>} `{ alt }`
102
+ */
103
+ async altText({ imageUrl, description } = {}) {
104
+ this.sdk.validateParams(
105
+ { imageUrl, description },
106
+ {
107
+ imageUrl: { type: 'string', required: false },
108
+ description: { type: 'string', required: false },
109
+ },
110
+ );
111
+
112
+ return internalRequest(this.sdk, '/ai/email/alt-text', 'POST', {
113
+ body: pickDefined({ imageUrl, description }),
114
+ });
115
+ }
116
+ }
package/services/ai.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { internalRequest } from '../base.js';
2
2
  import { PlaybooksService } from './ai/playbooks.js';
3
3
  import { VocabularyService } from './ai/vocabulary.js';
4
+ import { EmailService } from './ai/email.js';
4
5
  import { translate as translateItems } from './ai/translate.js';
5
6
  import {
6
7
  getSettings as getAiSettings,
@@ -16,6 +17,7 @@ export class AIService {
16
17
  this.extract = new ExtractService(sdk);
17
18
  this.playbooks = new PlaybooksService(sdk);
18
19
  this.vocabulary = new VocabularyService(sdk);
20
+ this.email = new EmailService(sdk);
19
21
  }
20
22
 
21
23
  /**
@@ -0,0 +1,166 @@
1
+ import { internalRequest } from '../base.js';
2
+
3
+ function pickDefined(fields) {
4
+ const body = {};
5
+ for (const [key, value] of Object.entries(fields)) {
6
+ if (value !== undefined) body[key] = value;
7
+ }
8
+ return body;
9
+ }
10
+
11
+ /**
12
+ * Tenant brand kits used in content we send/host (emails, later landing
13
+ * pages). Distinct from white-label `sdk.branding`.
14
+ *
15
+ * @see app1-api src/services/brand/routes.js
16
+ */
17
+ export class BrandService {
18
+ constructor(sdk) {
19
+ this.sdk = sdk;
20
+ this.kits = new BrandKitsService(sdk);
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Brand kit CRUD + URL extract. `sdk.brand.kits.*`.
26
+ */
27
+ export class BrandKitsService {
28
+ constructor(sdk) {
29
+ this.sdk = sdk;
30
+ }
31
+
32
+ /**
33
+ * List brand kits for the account. First call may seed a Default kit.
34
+ *
35
+ * @returns {Promise<Array>} Kits
36
+ */
37
+ async list() {
38
+ const result = await internalRequest(this.sdk, '/brand/kits', 'GET', {});
39
+ return result;
40
+ }
41
+
42
+ /**
43
+ * Create a brand kit.
44
+ *
45
+ * @param {Object} params
46
+ * @param {string} params.name - Kit name (required)
47
+ * @param {string} [params.logoStorageId]
48
+ * @param {string} [params.logoDarkStorageId]
49
+ * @param {string} [params.faviconStorageId]
50
+ * @param {Object} [params.colors]
51
+ * @param {Object} [params.fonts]
52
+ * @param {Object} [params.styles]
53
+ * @param {Object} [params.socialLinks]
54
+ * @param {string} [params.legalFooter]
55
+ * @param {string} [params.websiteUrl]
56
+ * @param {boolean} [params.isDefault]
57
+ * @returns {Promise<Object>} Created kit
58
+ */
59
+ async create(params = {}) {
60
+ this.sdk.validateParams(
61
+ { name: params.name },
62
+ { name: { type: 'string', required: true } },
63
+ );
64
+
65
+ const result = await internalRequest(this.sdk, '/brand/kits', 'POST', {
66
+ body: pickDefined(params),
67
+ });
68
+ return result;
69
+ }
70
+
71
+ /**
72
+ * Get a brand kit by id.
73
+ *
74
+ * @param {string} id - Kit id
75
+ * @returns {Promise<Object>} Kit
76
+ */
77
+ async get(id) {
78
+ this.sdk.validateParams(
79
+ { id },
80
+ { id: { type: 'string', required: true } },
81
+ );
82
+
83
+ const result = await internalRequest(this.sdk, `/brand/kits/${id}`, 'GET', {});
84
+ return result;
85
+ }
86
+
87
+ /**
88
+ * Update a brand kit. Only defined fields are sent.
89
+ *
90
+ * @param {string} id - Kit id
91
+ * @param {Object} params - Fields to update
92
+ * @returns {Promise<Object>} Updated kit
93
+ */
94
+ async update(id, params = {}) {
95
+ this.sdk.validateParams(
96
+ { id },
97
+ { id: { type: 'string', required: true } },
98
+ );
99
+
100
+ const result = await internalRequest(this.sdk, `/brand/kits/${id}`, 'PUT', {
101
+ body: pickDefined(params),
102
+ });
103
+ return result;
104
+ }
105
+
106
+ /**
107
+ * Delete a brand kit.
108
+ *
109
+ * @param {string} id - Kit id
110
+ * @returns {Promise<Object>} Confirmation
111
+ */
112
+ async delete(id) {
113
+ this.sdk.validateParams(
114
+ { id },
115
+ { id: { type: 'string', required: true } },
116
+ );
117
+
118
+ const result = await internalRequest(
119
+ this.sdk,
120
+ `/brand/kits/${id}`,
121
+ 'DELETE',
122
+ {},
123
+ );
124
+ return result;
125
+ }
126
+
127
+ /**
128
+ * Make this kit the account default. Exactly one kit is default.
129
+ *
130
+ * @param {string} id - Kit id
131
+ * @returns {Promise<Object>} Updated kit
132
+ */
133
+ async setDefault(id) {
134
+ this.sdk.validateParams(
135
+ { id },
136
+ { id: { type: 'string', required: true } },
137
+ );
138
+
139
+ const result = await internalRequest(
140
+ this.sdk,
141
+ `/brand/kits/${id}/default`,
142
+ 'POST',
143
+ {},
144
+ );
145
+ return result;
146
+ }
147
+
148
+ /**
149
+ * Extract a brand-kit proposal from a public https URL. Does not save.
150
+ *
151
+ * @param {Object} params
152
+ * @param {string} params.url - https URL to scrape
153
+ * @returns {Promise<Object>} Proposal (`logoCandidates`, `colors`, `fonts`)
154
+ */
155
+ async extract({ url } = {}) {
156
+ this.sdk.validateParams(
157
+ { url },
158
+ { url: { type: 'string', required: true } },
159
+ );
160
+
161
+ const result = await internalRequest(this.sdk, '/brand/kits/extract', 'POST', {
162
+ body: { url },
163
+ });
164
+ return result;
165
+ }
166
+ }
@@ -0,0 +1,299 @@
1
+ import { internalRequest } from '../base.js';
2
+
3
+ function pickDefined(fields) {
4
+ const body = {};
5
+ for (const [key, value] of Object.entries(fields)) {
6
+ if (value !== undefined) body[key] = value;
7
+ }
8
+ return body;
9
+ }
10
+
11
+ /**
12
+ * Content blocks, template library, and stock imagery.
13
+ *
14
+ * @see app1-api src/services/content/routes.js
15
+ */
16
+ export class ContentService {
17
+ constructor(sdk) {
18
+ this.sdk = sdk;
19
+ this.blocks = new ContentBlocksService(sdk);
20
+ this.library = new ContentLibraryService(sdk);
21
+ this.stock = new ContentStockService(sdk);
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Saved / synced content blocks. `sdk.content.blocks.*`.
27
+ */
28
+ export class ContentBlocksService {
29
+ constructor(sdk) {
30
+ this.sdk = sdk;
31
+ }
32
+
33
+ /**
34
+ * List content blocks.
35
+ *
36
+ * @param {Object} [filters]
37
+ * @param {string} [filters.channel] - `email` or `web`
38
+ * @param {string} [filters.category]
39
+ * @param {boolean} [filters.isSynced]
40
+ * @param {number} [filters.limit]
41
+ * @returns {Promise<Object>} `{ results }`
42
+ */
43
+ async list({ channel, category, isSynced, limit } = {}) {
44
+ const query = pickDefined({ channel, category, isSynced, limit });
45
+ const options = Object.keys(query).length ? { query } : {};
46
+ const result = await internalRequest(
47
+ this.sdk,
48
+ '/content/blocks',
49
+ 'GET',
50
+ options,
51
+ );
52
+ return result;
53
+ }
54
+
55
+ /**
56
+ * Create a content block.
57
+ *
58
+ * @param {Object} params
59
+ * @param {string} params.name - Block name (required)
60
+ * @param {string} [params.channel]
61
+ * @param {boolean} [params.isSynced]
62
+ * @param {string} [params.category]
63
+ * @param {Object|Array} [params.design] - Block-tree JSON
64
+ * @param {string} [params.designStorageId]
65
+ * @param {string} [params.thumbnailStorageId]
66
+ * @returns {Promise<Object>} Created block
67
+ */
68
+ async create(params = {}) {
69
+ this.sdk.validateParams(
70
+ { name: params.name },
71
+ { name: { type: 'string', required: true } },
72
+ );
73
+
74
+ const result = await internalRequest(this.sdk, '/content/blocks', 'POST', {
75
+ body: pickDefined(params),
76
+ });
77
+ return result;
78
+ }
79
+
80
+ /**
81
+ * Get a content block by id.
82
+ *
83
+ * @param {string} id
84
+ * @returns {Promise<Object>} Block
85
+ */
86
+ async get(id) {
87
+ this.sdk.validateParams(
88
+ { id },
89
+ { id: { type: 'string', required: true } },
90
+ );
91
+
92
+ const result = await internalRequest(
93
+ this.sdk,
94
+ `/content/blocks/${id}`,
95
+ 'GET',
96
+ {},
97
+ );
98
+ return result;
99
+ }
100
+
101
+ /**
102
+ * Update a content block. Only defined fields are sent.
103
+ *
104
+ * @param {string} id
105
+ * @param {Object} params
106
+ * @returns {Promise<Object>} Updated block
107
+ */
108
+ async update(id, params = {}) {
109
+ this.sdk.validateParams(
110
+ { id },
111
+ { id: { type: 'string', required: true } },
112
+ );
113
+
114
+ const result = await internalRequest(this.sdk, `/content/blocks/${id}`, 'PUT', {
115
+ body: pickDefined(params),
116
+ });
117
+ return result;
118
+ }
119
+
120
+ /**
121
+ * Delete a content block.
122
+ *
123
+ * @param {string} id
124
+ * @returns {Promise<Object>} Confirmation
125
+ */
126
+ async delete(id) {
127
+ this.sdk.validateParams(
128
+ { id },
129
+ { id: { type: 'string', required: true } },
130
+ );
131
+
132
+ const result = await internalRequest(
133
+ this.sdk,
134
+ `/content/blocks/${id}`,
135
+ 'DELETE',
136
+ {},
137
+ );
138
+ return result;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Platform template library. `sdk.content.library.*`.
144
+ * No `/use` route exists yet — list / get / create only.
145
+ */
146
+ export class ContentLibraryService {
147
+ constructor(sdk) {
148
+ this.sdk = sdk;
149
+ }
150
+
151
+ /**
152
+ * List library templates.
153
+ *
154
+ * @param {Object} [filters]
155
+ * @param {string} [filters.channel]
156
+ * @param {string} [filters.category]
157
+ * @param {string} [filters.appearance] - `client` or `marketing`
158
+ * @param {boolean} [filters.isPublished]
159
+ * @param {number} [filters.limit]
160
+ * @returns {Promise<Object>} `{ results }`
161
+ */
162
+ async list({ channel, category, appearance, isPublished, limit } = {}) {
163
+ const query = pickDefined({
164
+ channel,
165
+ category,
166
+ appearance,
167
+ isPublished,
168
+ limit,
169
+ });
170
+ const options = Object.keys(query).length ? { query } : {};
171
+ const result = await internalRequest(
172
+ this.sdk,
173
+ '/content/library',
174
+ 'GET',
175
+ options,
176
+ );
177
+ return result;
178
+ }
179
+
180
+ /**
181
+ * Get a library template by id.
182
+ *
183
+ * @param {string} id
184
+ * @returns {Promise<Object>} Library item
185
+ */
186
+ async get(id) {
187
+ this.sdk.validateParams(
188
+ { id },
189
+ { id: { type: 'string', required: true } },
190
+ );
191
+
192
+ const result = await internalRequest(
193
+ this.sdk,
194
+ `/content/library/${id}`,
195
+ 'GET',
196
+ {},
197
+ );
198
+ return result;
199
+ }
200
+
201
+ /**
202
+ * Create a library template.
203
+ *
204
+ * @param {Object} params
205
+ * @param {string} params.name - Name (required)
206
+ * @returns {Promise<Object>} Created item
207
+ */
208
+ async create(params = {}) {
209
+ this.sdk.validateParams(
210
+ { name: params.name },
211
+ { name: { type: 'string', required: true } },
212
+ );
213
+
214
+ const result = await internalRequest(this.sdk, '/content/library', 'POST', {
215
+ body: pickDefined(params),
216
+ });
217
+ return result;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Stock / GIF search + import-to-storage. `sdk.content.stock.*`.
223
+ */
224
+ export class ContentStockService {
225
+ constructor(sdk) {
226
+ this.sdk = sdk;
227
+ }
228
+
229
+ /**
230
+ * Search Unsplash.
231
+ *
232
+ * @param {Object} [params]
233
+ * @param {string} [params.q]
234
+ * @param {number} [params.page]
235
+ * @returns {Promise<Object>} `{ items }`
236
+ */
237
+ async searchUnsplash({ q, page } = {}) {
238
+ return this._search('unsplash', { q, page });
239
+ }
240
+
241
+ /**
242
+ * Search Pexels.
243
+ *
244
+ * @param {Object} [params]
245
+ * @param {string} [params.q]
246
+ * @param {number} [params.page]
247
+ * @returns {Promise<Object>} `{ items }`
248
+ */
249
+ async searchPexels({ q, page } = {}) {
250
+ return this._search('pexels', { q, page });
251
+ }
252
+
253
+ /**
254
+ * Search GIPHY.
255
+ *
256
+ * @param {Object} [params]
257
+ * @param {string} [params.q]
258
+ * @param {number} [params.page]
259
+ * @returns {Promise<Object>} `{ items }`
260
+ */
261
+ async searchGiphy({ q, page } = {}) {
262
+ return this._search('giphy', { q, page });
263
+ }
264
+
265
+ /**
266
+ * Import a stock asset into account storage.
267
+ *
268
+ * @param {Object} params
269
+ * @param {string} params.source - `unsplash` | `pexels` | `giphy`
270
+ * @param {string} [params.remoteId]
271
+ * @param {string} [params.url]
272
+ * @returns {Promise<Object>} `{ storageId, url }`
273
+ */
274
+ async import(params = {}) {
275
+ this.sdk.validateParams(
276
+ { source: params.source },
277
+ { source: { type: 'string', required: true } },
278
+ );
279
+
280
+ const result = await internalRequest(
281
+ this.sdk,
282
+ '/content/stock/import',
283
+ 'POST',
284
+ { body: pickDefined(params) },
285
+ );
286
+ return result;
287
+ }
288
+
289
+ async _search(source, { q, page } = {}) {
290
+ const query = pickDefined({ q, page });
291
+ const options = Object.keys(query).length ? { query } : {};
292
+ return internalRequest(
293
+ this.sdk,
294
+ `/content/stock/${source}`,
295
+ 'GET',
296
+ options,
297
+ );
298
+ }
299
+ }
@@ -1,4 +1,13 @@
1
1
  import { internalRequest } from '../../base.js';
2
+
3
+ function pickDefined(fields) {
4
+ const body = {};
5
+ for (const [key, value] of Object.entries(fields)) {
6
+ if (value !== undefined) body[key] = value;
7
+ }
8
+ return body;
9
+ }
10
+
2
11
  export class EmailTemplatesService {
3
12
  constructor(sdk) {
4
13
  this.sdk = sdk;
@@ -11,6 +20,12 @@ export class EmailTemplatesService {
11
20
  * @param {string} params.subject - Template subject (required)
12
21
  * @param {string} [params.html] - HTML template body
13
22
  * @param {string} [params.text] - Plain text template body
23
+ * @param {Object} [params.design] - Block-tree design JSON (compiled server-side)
24
+ * @param {string} [params.appearance] - `client` or `marketing` (immutable after create)
25
+ * @param {boolean} [params.allowOneOff] - Usable as a one-off / compose send
26
+ * @param {boolean} [params.allowCampaign] - Usable in campaigns / journeys
27
+ * @param {string} [params.brandKitId] - Brand kit to apply
28
+ * @param {string} [params.category] - Template category
14
29
  * @param {Array<Object>} [params.variables] - Variable metadata definitions
15
30
  * @param {string} params.variables[].key - Variable key (unique, alphanumeric + underscores)
16
31
  * @param {string} params.variables[].label - Human-readable display name
@@ -22,6 +37,10 @@ export class EmailTemplatesService {
22
37
  * const template = await sdk.messaging.email.templates.create({
23
38
  * name: 'Welcome Email',
24
39
  * subject: 'Welcome {{firstName}}!',
40
+ * appearance: 'marketing',
41
+ * allowOneOff: true,
42
+ * allowCampaign: true,
43
+ * design: { type: 'email', children: [] },
25
44
  * html: '<h1>Hello {{firstName}}</h1><p>{{body}}</p>',
26
45
  * text: 'Hello {{firstName}}',
27
46
  * variables: [
@@ -30,7 +49,19 @@ export class EmailTemplatesService {
30
49
  * ],
31
50
  * });
32
51
  */
33
- async create({ name, subject, html, text, variables }) {
52
+ async create({
53
+ name,
54
+ subject,
55
+ html,
56
+ text,
57
+ variables,
58
+ design,
59
+ appearance,
60
+ allowOneOff,
61
+ allowCampaign,
62
+ brandKitId,
63
+ category,
64
+ }) {
34
65
  this.sdk.validateParams(
35
66
  { name, subject },
36
67
  {
@@ -39,16 +70,29 @@ export class EmailTemplatesService {
39
70
  html: { type: 'string', required: false },
40
71
  text: { type: 'string', required: false },
41
72
  variables: { type: 'array', required: false },
73
+ design: { type: 'object', required: false },
74
+ appearance: { type: 'string', required: false },
75
+ allowOneOff: { type: 'boolean', required: false },
76
+ allowCampaign: { type: 'boolean', required: false },
77
+ brandKitId: { type: 'string', required: false },
78
+ category: { type: 'string', required: false },
42
79
  },
43
80
  );
44
81
 
45
- const templateData = { name, subject };
46
- if (html) templateData.html = html;
47
- if (text) templateData.text = text;
48
- if (variables) templateData.variables = variables;
49
-
50
82
  const options = {
51
- body: templateData,
83
+ body: pickDefined({
84
+ name,
85
+ subject,
86
+ html,
87
+ text,
88
+ variables,
89
+ design,
90
+ appearance,
91
+ allowOneOff,
92
+ allowCampaign,
93
+ brandKitId,
94
+ category,
95
+ }),
52
96
  };
53
97
 
54
98
  const result = await internalRequest(this.sdk,
@@ -60,13 +104,20 @@ export class EmailTemplatesService {
60
104
  }
61
105
 
62
106
  /**
63
- * Update email template
107
+ * Update email template. Only defined fields are sent. Appearance is
108
+ * immutable after create and should usually be omitted.
64
109
  * @param {string} id - Template ID (required)
65
110
  * @param {Object} params - Update parameters
66
111
  * @param {string} [params.name] - Template name
67
112
  * @param {string} [params.subject] - Template subject
68
113
  * @param {string} [params.html] - HTML template body
69
114
  * @param {string} [params.text] - Plain text template body
115
+ * @param {Object} [params.design] - Block-tree design JSON (compiled server-side)
116
+ * @param {string} [params.appearance] - Usually omit; immutable after create
117
+ * @param {boolean} [params.allowOneOff] - Usable as a one-off / compose send
118
+ * @param {boolean} [params.allowCampaign] - Usable in campaigns / journeys
119
+ * @param {string} [params.brandKitId] - Brand kit to apply
120
+ * @param {string} [params.category] - Template category
70
121
  * @param {Array<Object>} [params.variables] - Variable metadata definitions
71
122
  * @param {string} params.variables[].key - Variable key (unique, alphanumeric + underscores)
72
123
  * @param {string} params.variables[].label - Human-readable display name
@@ -77,13 +128,31 @@ export class EmailTemplatesService {
77
128
  * @example
78
129
  * const updated = await sdk.messaging.email.templates.update('tpl_123', {
79
130
  * subject: 'Hi {{firstName}}, welcome to {{companyName}}!',
131
+ * allowOneOff: true,
132
+ * allowCampaign: false,
133
+ * design: { type: 'email', children: [] },
80
134
  * variables: [
81
135
  * { key: 'firstName', label: 'First Name', type: 'text', required: true },
82
136
  * { key: 'companyName', label: 'Company Name', type: 'text' },
83
137
  * ],
84
138
  * });
85
139
  */
86
- async update(id, { name, subject, html, text, variables }) {
140
+ async update(
141
+ id,
142
+ {
143
+ name,
144
+ subject,
145
+ html,
146
+ text,
147
+ variables,
148
+ design,
149
+ appearance,
150
+ allowOneOff,
151
+ allowCampaign,
152
+ brandKitId,
153
+ category,
154
+ },
155
+ ) {
87
156
  this.sdk.validateParams(
88
157
  { id },
89
158
  {
@@ -93,18 +162,29 @@ export class EmailTemplatesService {
93
162
  html: { type: 'string', required: false },
94
163
  text: { type: 'string', required: false },
95
164
  variables: { type: 'array', required: false },
165
+ design: { type: 'object', required: false },
166
+ appearance: { type: 'string', required: false },
167
+ allowOneOff: { type: 'boolean', required: false },
168
+ allowCampaign: { type: 'boolean', required: false },
169
+ brandKitId: { type: 'string', required: false },
170
+ category: { type: 'string', required: false },
96
171
  },
97
172
  );
98
173
 
99
- const updateData = {};
100
- if (name) updateData.name = name;
101
- if (subject) updateData.subject = subject;
102
- if (html) updateData.html = html;
103
- if (text) updateData.text = text;
104
- if (variables) updateData.variables = variables;
105
-
106
174
  const options = {
107
- body: updateData,
175
+ body: pickDefined({
176
+ name,
177
+ subject,
178
+ html,
179
+ text,
180
+ variables,
181
+ design,
182
+ appearance,
183
+ allowOneOff,
184
+ allowCampaign,
185
+ brandKitId,
186
+ category,
187
+ }),
108
188
  };
109
189
 
110
190
  const result = await internalRequest(this.sdk,
@@ -156,11 +236,22 @@ export class EmailTemplatesService {
156
236
  }
157
237
 
158
238
  /**
159
- * List all email templates
239
+ * List email templates, optionally filtered by appearance / usage flags.
240
+ * @param {Object} [filters]
241
+ * @param {string} [filters.appearance] - `client` or `marketing`
242
+ * @param {boolean} [filters.allowOneOff]
243
+ * @param {boolean} [filters.allowCampaign]
160
244
  * @returns {Promise<Array>} List of email templates
161
245
  */
162
- async list() {
163
- const result = await internalRequest(this.sdk, '/messaging/email/template', 'GET');
246
+ async list({ appearance, allowOneOff, allowCampaign } = {}) {
247
+ const query = pickDefined({ appearance, allowOneOff, allowCampaign });
248
+ const options = Object.keys(query).length ? { query } : {};
249
+ const result = await internalRequest(
250
+ this.sdk,
251
+ '/messaging/email/template',
252
+ 'GET',
253
+ options,
254
+ );
164
255
  return result;
165
256
  }
166
257
 
@@ -173,13 +264,15 @@ export class EmailTemplatesService {
173
264
  * @param {string} [body.html] - Draft HTML body
174
265
  * @param {string} [body.text] - Draft plain-text body
175
266
  * @param {Object} [body.variables] - Variable substitution values
176
- * @returns {Promise<Object>} Rendered preview
267
+ * @returns {Promise<Object>} Rendered preview. May include `unresolvedTags`
268
+ * (string[]) for merge tags that were not substituted.
177
269
  * @example
178
270
  * const preview = await sdk.messaging.email.templates.preview('tpl_123', {
179
271
  * subject: 'Welcome {{firstName}}',
180
272
  * html: '<p>Hello {{firstName}}</p>',
181
273
  * variables: { firstName: 'Jane' },
182
274
  * });
275
+ * // preview.unresolvedTags → [] or ['companyName', ...]
183
276
  */
184
277
  async preview(id, { subject, html, text, variables } = {}) {
185
278
  this.sdk.validateParams(
@@ -240,4 +333,72 @@ export class EmailTemplatesService {
240
333
  );
241
334
  return result;
242
335
  }
336
+
337
+ /**
338
+ * Autosave a draft design tree without compiling or snapshotting a version.
339
+ * @param {string} id - Template ID (required)
340
+ * @param {Object} body
341
+ * @param {Object} body.design - Block-tree design JSON (required)
342
+ * @returns {Promise<Object>} `{ id, draftDesignStorageId }`
343
+ */
344
+ async autosave(id, { design } = {}) {
345
+ this.sdk.validateParams(
346
+ { id, design },
347
+ {
348
+ id: { type: 'string', required: true },
349
+ design: { type: 'object', required: true },
350
+ },
351
+ );
352
+
353
+ const result = await internalRequest(this.sdk,
354
+ `/messaging/email/template/${id}/autosave`,
355
+ 'POST',
356
+ { body: { design } },
357
+ );
358
+ return result;
359
+ }
360
+
361
+ /**
362
+ * List saved versions of a template.
363
+ * @param {string} id - Template ID (required)
364
+ * @returns {Promise<Array>} Version rows
365
+ */
366
+ async listVersions(id) {
367
+ this.sdk.validateParams(
368
+ { id },
369
+ {
370
+ id: { type: 'string', required: true },
371
+ },
372
+ );
373
+
374
+ const result = await internalRequest(this.sdk,
375
+ `/messaging/email/template/${id}/versions`,
376
+ 'GET',
377
+ );
378
+ return result;
379
+ }
380
+
381
+ /**
382
+ * Restore a previously saved version as the current template.
383
+ * @param {string} id - Template ID (required)
384
+ * @param {number|string} version - Version number (required)
385
+ * @returns {Promise<Object>} Restored template
386
+ */
387
+ async restoreVersion(id, version) {
388
+ this.sdk.validateParams(
389
+ { id },
390
+ {
391
+ id: { type: 'string', required: true },
392
+ },
393
+ );
394
+ if (version === undefined || version === null || version === '') {
395
+ throw new Error('Missing required parameter version');
396
+ }
397
+
398
+ const result = await internalRequest(this.sdk,
399
+ `/messaging/email/template/${id}/versions/${version}/restore`,
400
+ 'POST',
401
+ );
402
+ return result;
403
+ }
243
404
  }