smartletter 1.0.0 → 1.2.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/README.md CHANGED
@@ -48,6 +48,8 @@ Renamed concepts:
48
48
 
49
49
  Resend methods with no Smartletter route, so absent from the client: `domains.update`, `domains.remove`, `domains.claims`, `emails.share`, `emails.metrics`, `emails.attachments`, `emails.receiving`, `webhooks.events`. Resend's raw HTTP methods on the client (`post`, `get`, `put`, `patch`, `delete`) are absent too.
50
50
 
51
+ `attachments[].filename` accepts `false` like Resend (the key is then left out of the request). That only makes sense with `path`.
52
+
51
53
  Also supported, with the same call styles as Resend:
52
54
 
53
55
  ```ts
@@ -95,6 +97,44 @@ const event = smartletter.webhooks.verify({
95
97
  });
96
98
  ```
97
99
 
100
+ ## Changelog
101
+
102
+ ### 1.2.0
103
+
104
+ **Breaking changes.** Two fields of the API's answers are renamed to snake_case, like every other field. There is no alias: the old name is gone.
105
+
106
+ - `body.blocks[].blockId` is now `block_id` in every newsletter returned with its body (`newsletters.get`, `newsletters.create`, `newsletters.update`, `newsletters.duplicate`…). Read `block.block_id`.
107
+ - `POST /agent-tasks/{id}/complete` answers `{ newsletter_id, scheduled_for, review }` instead of `{ newsletterId, scheduledFor, review }` (the MCP tool `agent_tasks.complete` too). This route is not in the SDK: update the scripts that call it.
108
+
109
+ Other API changes:
110
+
111
+ - `newsletters.list`: the `status` filter takes `cancelled` and `held`. `failed` is no longer in the type (no newsletter ever has it); the API still accepts it and returns an empty list.
112
+ - `pause_reason` may be `gmail_spam_rate` (paused after too many spam reports at Gmail). It was already sent; the type now lists it.
113
+ - `newsletters.test` returns `variants`: the number of emails sent (one per A/B variant, 1 without a test).
114
+ - `segments.domain.remove` returns `id` (the newsletter's id), like every deletion.
115
+ - `sequences.unenroll` returns the enrollment's real status: `exited` when the contact was taken out, `completed` or `exited` unchanged when the run was already over.
116
+ - `topics.list` returns `has_more: true` when the newsletter has more than 200 topics (it returns the newest 200).
117
+ - `threads.list` and `sequences.enrollments` take the standard pagination: `limit`, `after` and `before`. `threads.list` used to refuse `before`; `sequences.enrollments` ignored `after` and `before` (its `limit` still defaults to 100).
118
+ - `contacts.create` documents its `200` answer: an address already there returns the existing contact with `existed: true`, unchanged.
119
+
120
+ Response types now come from the API's response schemas, which are checked against what the API really answers. They no longer come from the documentation examples. The runtime is unchanged. Only the types are more exact:
121
+
122
+ - `object`, statuses and event names are literal types, for example `"list"`, `"sent"` or `"email.delivered"`, instead of `string`.
123
+ - Fields the examples left out are now typed, and so are the fields they showed as `null` or `[]` (until now `unknown`).
124
+ - 125 fields that can be `null` are now `T | null`. Under `strictNullChecks`, handle the `null` case.
125
+ - Fields that were in an example but never in a response are gone:
126
+ - `body` in the `newsletters.list` items;
127
+ - the spam fields on sent and draft inbox messages;
128
+ - `reasons.invalid` in `contacts.import`.
129
+ - Shapes that vary are unions. Narrow on `direction` (inbox messages), `body.format` (newsletters), the step and trigger `type` (sequences), or `deleted` (`sequences.remove`).
130
+ - Dictionaries whose keys you choose are `Record<string, …>`: `custom_fields`, `metadata`, `properties` and filter `rules`.
131
+ - Free-form objects are `Record<string, unknown>` too: the condition step's `rule` and the items of its `yes` and `no` branches (sequences), and the webhook `payload.data` (`webhooks.test`, `webhooks.deliveries`, `webhooks.retryDelivery`).
132
+ - `links.x` in `segments.archive.get` and `segments.archive.update` is optional: it is missing when no X link is set.
133
+ - Webhook `events` (`webhooks.list`, `webhooks.get`) is a union of event names, such as `"email.delivered"`, instead of `string[]`.
134
+ - `messages.send` returns the message sent (`status: "sent"`), or `{ object, email_id, status }` when the email was queued but its record could not be saved. `threads.reply` returns the same, or the saved draft (`status: "draft"`) with `draft: true`.
135
+
136
+ Answers may gain fields in a later version. The types stay open for reading: a field the API adds does not break your code. Lists of values (statuses, event names) may grow too, so keep a default branch when you switch on one.
137
+
98
138
  ## License
99
139
 
100
140
  MIT
package/dist/index.cjs CHANGED
@@ -81,7 +81,7 @@ var Client = class {
81
81
  }
82
82
  this.key = resolved;
83
83
  this.baseUrl = (options.baseUrl || env("SMARTLETTER_BASE_URL") || DEFAULT_BASE_URL).replace(/\/+$/, "");
84
- this.userAgent = options.userAgent ?? `smartletter-node/${"1.0.0"}`;
84
+ this.userAgent = options.userAgent ?? `smartletter-node/${"1.2.0"}`;
85
85
  }
86
86
  async call(op, args, options = {}) {
87
87
  const headers = new Headers(options.headers);
@@ -238,6 +238,9 @@ var OPERATIONS = {
238
238
  scheduledAt: {
239
239
  wire: "scheduled_at"
240
240
  },
241
+ topicId: {
242
+ wire: "topic_id"
243
+ },
241
244
  attachments: {
242
245
  wire: "attachments",
243
246
  items: {
@@ -320,6 +323,9 @@ var OPERATIONS = {
320
323
  },
321
324
  scheduledAt: {
322
325
  wire: "scheduled_at"
326
+ },
327
+ topicId: {
328
+ wire: "topic_id"
323
329
  }
324
330
  }
325
331
  },
@@ -2105,14 +2111,20 @@ var OPERATIONS = {
2105
2111
  method: "GET",
2106
2112
  path: "/sequences/{id}/enrollments",
2107
2113
  pathParams: ["id"],
2108
- paginated: false,
2114
+ paginated: true,
2109
2115
  idempotent: false,
2110
2116
  query: {
2111
- status: {
2112
- wire: "status"
2113
- },
2114
2117
  limit: {
2115
2118
  wire: "limit"
2119
+ },
2120
+ after: {
2121
+ wire: "after"
2122
+ },
2123
+ before: {
2124
+ wire: "before"
2125
+ },
2126
+ status: {
2127
+ wire: "status"
2116
2128
  }
2117
2129
  }
2118
2130
  },
@@ -2369,9 +2381,18 @@ var OPERATIONS = {
2369
2381
  method: "GET",
2370
2382
  path: "/threads",
2371
2383
  pathParams: [],
2372
- paginated: false,
2384
+ paginated: true,
2373
2385
  idempotent: false,
2374
2386
  query: {
2387
+ limit: {
2388
+ wire: "limit"
2389
+ },
2390
+ after: {
2391
+ wire: "after"
2392
+ },
2393
+ before: {
2394
+ wire: "before"
2395
+ },
2375
2396
  inboxId: {
2376
2397
  wire: "inbox_id"
2377
2398
  },
@@ -2389,12 +2410,6 @@ var OPERATIONS = {
2389
2410
  },
2390
2411
  unread: {
2391
2412
  wire: "unread"
2392
- },
2393
- limit: {
2394
- wire: "limit"
2395
- },
2396
- after: {
2397
- wire: "after"
2398
2413
  }
2399
2414
  }
2400
2415
  },
@@ -2564,7 +2579,14 @@ async function prepare(email) {
2564
2579
  const out = { ...rest };
2565
2580
  if (react) out.html = await renderReact(react);
2566
2581
  if (attachments) {
2567
- out.attachments = attachments.map((a) => a.content instanceof Uint8Array ? { ...a, content: toBase64(a.content) } : a);
2582
+ out.attachments = attachments.map((a) => {
2583
+ const { filename, ...file } = a;
2584
+ return {
2585
+ ...file,
2586
+ ...filename === false ? {} : { filename },
2587
+ ...a.content instanceof Uint8Array ? { content: toBase64(a.content) } : {}
2588
+ };
2589
+ });
2568
2590
  }
2569
2591
  return out;
2570
2592
  }