@unboundcx/sdk 4.11.0 → 4.12.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.0",
3
+ "version": "4.12.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,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
+ }
package/services/chat.js CHANGED
@@ -825,7 +825,9 @@ export class ChatService {
825
825
 
826
826
  /**
827
827
  * Admin: get account-level chat settings.
828
- * @returns {Promise<Object>} `{allowReports, reportReasons}`
828
+ * @returns {Promise<Object>} `{allowReports, reportReasons, reportNotifications}`
829
+ * `reportNotifications[]` items are `{channelId, channelName, channelKind, reasons}` —
830
+ * `reasons` is `null` when the rule notifies for every reason.
829
831
  */
830
832
  async adminGetSettings() {
831
833
  return internalRequest(this.sdk, "/chat/admin/settings", "GET");
@@ -836,18 +838,26 @@ export class ChatService {
836
838
  * @param {Object} params
837
839
  * @param {boolean} params.allowReports
838
840
  * @param {string[]} [params.reportReasons]
839
- * @returns {Promise<Object>} `{allowReports, reportReasons}`
841
+ * @param {Object[]} [params.reportNotifications] - Channels to post a
842
+ * "message reported" card to. Each item: `{channelId: string, reasons:
843
+ * string[]|null}` — `reasons` null/empty means "notify for every
844
+ * reason"; channel must be a non-archived public/private channel.
845
+ * @returns {Promise<Object>} `{allowReports, reportReasons, reportNotifications}`
840
846
  */
841
- async adminPutSettings({ allowReports, reportReasons } = {}) {
847
+ async adminPutSettings({ allowReports, reportReasons, reportNotifications } = {}) {
842
848
  this.sdk.validateParams(
843
- { allowReports, reportReasons },
849
+ { allowReports, reportReasons, reportNotifications },
844
850
  {
845
851
  allowReports: { type: "boolean", required: true },
846
852
  reportReasons: { type: "array", required: false },
853
+ reportNotifications: { type: "array", required: false },
847
854
  },
848
855
  );
849
856
  const body = { allowReports };
850
857
  if (reportReasons !== undefined) body.reportReasons = reportReasons;
858
+ if (reportNotifications !== undefined) {
859
+ body.reportNotifications = reportNotifications;
860
+ }
851
861
  return internalRequest(this.sdk, "/chat/admin/settings", "PUT", {
852
862
  body,
853
863
  });
@@ -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
+ }
package/services/drive.js CHANGED
@@ -228,4 +228,25 @@ export class DriveService {
228
228
  );
229
229
  return result;
230
230
  }
231
+
232
+ /**
233
+ * Walk/create a Drive folder path. Path is already interpolated.
234
+ * @param {Object} options
235
+ * @param {string} options.path - e.g. unbound/people/Ada Lovelace
236
+ * @param {string} [options.sharedDriveId]
237
+ * @param {boolean} [options.create]
238
+ */
239
+ async resolvePath({ path, sharedDriveId, create } = {}) {
240
+ this.sdk.validateParams(
241
+ { path, sharedDriveId },
242
+ {
243
+ path: { type: 'string', required: true },
244
+ sharedDriveId: { type: 'string', required: false },
245
+ },
246
+ );
247
+ const body = { path };
248
+ if (sharedDriveId !== undefined) body.sharedDriveId = sharedDriveId;
249
+ if (create !== undefined) body.create = create;
250
+ return internalRequest(this.sdk, '/drive/resolvePath', 'POST', { body });
251
+ }
231
252
  }
@@ -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
  }