@proveanything/smartlinks 2.1.6 → 2.1.7

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.
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.1.6 | Generated: 2026-10-31T10:38:17.995Z
3
+ Version: 2.1.7 | Generated: 2026-11-01T12:48:45.040Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -5402,6 +5402,7 @@ interface MessageBookersInput {
5402
5402
  resourceId?: string
5403
5403
  statuses?: BookingStatus[]
5404
5404
  ref?: string
5405
+ attach?: import('./comms').EmailAttachmentSpec[]
5405
5406
  }
5406
5407
  ```
5407
5408
 
@@ -5512,6 +5513,7 @@ interface BroadcastRecord {
5512
5513
  priority?: number
5513
5514
  }>
5514
5515
  }
5516
+ attach?: Array<{ asset: string }>
5515
5517
  [key: string]: unknown
5516
5518
  }
5517
5519
  createdAt: string
@@ -6242,6 +6244,7 @@ interface TransactionalSendRequest {
6242
6244
  }
6243
6245
  ref?: string
6244
6246
  appId?: string
6247
+ attach?: EmailAttachmentSpec[]
6245
6248
  }
6246
6249
  ```
6247
6250
 
@@ -6251,6 +6254,7 @@ interface TransactionalSendResponse {
6251
6254
  ok: true
6252
6255
  channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'
6253
6256
  messageId?: string
6257
+ attachmentsLeftOut?: string[]
6254
6258
  }
6255
6259
  ```
6256
6260
 
@@ -6270,6 +6274,8 @@ interface TransactionalSendError {
6270
6274
 
6271
6275
  **ConsentChannels** = `Partial<Record<BroadcastChannel, boolean>>`
6272
6276
 
6277
+ **EmailAttachmentSpec** = ``
6278
+
6273
6279
  **TransactionalSendResult** = `// src/types/comms.ts
6274
6280
  // Communication and notification types for the Smartlinks API
6275
6281
  import type { IdField } from './common'
@@ -6595,14 +6601,36 @@ export interface TransactionalSendRequest {
6595
6601
  ref?: string
6596
6602
  /** App context stored on the comms-events row */
6597
6603
  appId?: string
6604
+ /**
6605
+ * Files to attach (email only; at most 5, 10 MB in all). What can't be made is left out and listed in
6606
+ * `attachmentsLeftOut` — the email still goes.
6607
+ */
6608
+ attach?: EmailAttachmentSpec[]
6598
6609
  }
6599
6610
 
6611
+ /**
6612
+ * One file for an email:
6613
+ * - `'calendar'` — the booking's `.ics` calendar invite (needs `include.booking` and `appId`)
6614
+ * - `{ asset }` — a file uploaded to this collection's assets
6615
+ * - `{ function }` — the app's (`appId`) own server function makes it (see server-functions.md, "Making an email attachment")
6616
+ * - `{ filename, contentType, content }` — a file you made, base64 (PDF, Office documents, images, text, CSV, calendar)
6617
+ * - `{ filename, file }` — a private file you made (e.g. `pdf.create` with `return: 'signedUrl'`)
6618
+ */
6619
+ export type EmailAttachmentSpec =
6620
+ | 'calendar'
6621
+ | { asset: string }
6622
+ | { function: string }
6623
+ | { filename: string; contentType: string; content: string }
6624
+ | { filename: string; contentType?: string; file: string }
6625
+
6600
6626
  export interface TransactionalSendResponse {
6601
6627
  ok: true
6602
6628
  /** The channel the message was actually sent on */
6603
6629
  channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'
6604
6630
  /** Provider message ID (email/SMS/WhatsApp); absent for push/wallet */
6605
6631
  messageId?: string
6632
+ /** Attachments that couldn't be made, and why (the email went without them) */
6633
+ attachmentsLeftOut?: string[]
6606
6634
  }
6607
6635
 
6608
6636
  export interface TransactionalSendError {
@@ -368,6 +368,17 @@ wording.
368
368
  A **public** resource record (a staff profile the booking page lists) keeps its address on a
369
369
  **private child record** instead (`visibility: 'admin'`, `parentType: 'record'`, `parentId` = the
370
370
  resource) — the platform looks there too, and the address never ships with the public listing.
371
+ - Any step can also take `replyTo` — an address, or `"contact"` (replies go to the person the event is
372
+ about: a team notification answered straight to the booker) — and `cc` / `bcc` (written-out addresses,
373
+ same rules as `to`, counted in the same daily cap; over it the person still gets their email, without the
374
+ copies). A site's `cloud/email.json` (`{ "fromName", "replyTo" }`) sets the name the business's emails come
375
+ from and their default reply-to; the From address is the business's verified domain.
376
+ - `attach` on a step adds files (at most 5, 10 MB in all): `"calendar"` — the booking as an `.ics` invite
377
+ that drops straight into the person's calendar (a cancelled booking's invite cancels it there);
378
+ `{ "asset": "<assetId>" }` — a file in the collection's assets (terms, a price list);
379
+ `{ "function": "<name>" }` — your app's own function makes it (a receipt PDF: server-functions.md,
380
+ "Making an email attachment"). E.g. `{ "send": "booking-confirmed", "to": "contact", "attach": ["calendar"] }`.
381
+ One that can't be made is left out; the email still goes.
371
382
  - Who gets what, in templates: `{{ booking }}` is the booking **ready-formatted**: its fields and
372
383
  `data`, plus `serviceName`, `staffName`, `customer`, `durationMinutes`, `when` (`date`,
373
384
  `shortDate`, `time`, `endTime`, `dateTime`, `timezone` — in the business's timezone) and, when the
@@ -393,9 +404,14 @@ window (optionally with one resource), each hydrated with **their own** booking.
393
404
  await SL.bookings.message(collectionId, appId, { templateId, slotId })
394
405
  // Everyone booked with Sam next week, by email even if they prefer texts
395
406
  await SL.bookings.message(collectionId, appId, { templateId, channel: 'email', from, to, resourceType: 'record', resourceId: samId })
407
+ // Everyone on the class, each with their own calendar invite
408
+ await SL.bookings.message(collectionId, appId, { templateId, slotId, attach: ['calendar'] })
396
409
  // → { matched, sent, skipped: { noContact, duplicate, failed }, failures: [{ bookingId, reason }] }
397
410
  ```
398
411
 
412
+ `attach` takes the same files as `comms.sendTransactional` (comms.md, "Attachments"); `'calendar'` is
413
+ each person's own booking.
414
+
399
415
  Statuses default to held, pending and confirmed; at most 500 recipients per call (narrow the window
400
416
  for more). One message per contact on a slot, one per booking in a window. Also
401
417
  `ctx.sl.bookings.message(input)` in a server function (`sl:bookings:write`) — a cron function that
@@ -56,6 +56,7 @@ if (result.ok) {
56
56
  | `include` | object | Hydration flags — see table below. |
57
57
  | `ref` | `string` | Arbitrary reference string stored with the event. |
58
58
  | `appId` | `string` | App identifier, used for context/logging. |
59
+ | `attach` | `EmailAttachmentSpec[]` | Files on the email — see [Attachments](#attachments). |
59
60
 
60
61
  **`include` fields:**
61
62
 
@@ -73,12 +74,36 @@ if (result.ok) {
73
74
 
74
75
  ```typescript
75
76
  // Success
76
- { ok: true; channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'; messageId?: string }
77
+ { ok: true; channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'; messageId?: string; attachmentsLeftOut?: string[] }
77
78
 
78
79
  // Failure
79
80
  { ok: false; error: string }
80
81
  ```
81
82
 
83
+ ### Attachments
84
+
85
+ `attach` puts files on the email (email only — ignored when the send goes by text or push). At most 5
86
+ files and 10 MB in all; PDF, Word / Excel / PowerPoint, images, text, CSV and calendar files.
87
+
88
+ ```typescript
89
+ await comms.sendTransactional(collectionId, {
90
+ contactId, templateId: 'booking-confirmed', appId: 'appointment-booking',
91
+ include: { booking: bookingId },
92
+ attach: [
93
+ 'calendar', // the booking's .ics invite (needs include.booking + appId)
94
+ { asset: 'a1b2c3' }, // a file uploaded to the collection's assets
95
+ { function: 'receiptPdf' }, // the app's own server function makes it
96
+ { filename: 'Quote.pdf', contentType: 'application/pdf', content: base64 }, // a file you made
97
+ { filename: 'Report.pdf', file: signedUrl }, // a private file you made (pdf.create return: 'signedUrl')
98
+ ],
99
+ })
100
+ ```
101
+
102
+ A malformed list is refused (`transactional.attach_invalid`). A file that can't be made at send time
103
+ (a missing asset, a function that fails) is left out and listed in `attachmentsLeftOut` — the email
104
+ still goes. The request body is capped at 10 MB, so for big files make a private file and pass `file`.
105
+ `bookings.message` takes the same `attach` (`'calendar'` is each person's own booking).
106
+
82
107
  ---
83
108
 
84
109
  ## Broadcasts
@@ -149,11 +174,12 @@ Consent resolution order (per recipient, per channel):
149
174
  3. `preferences._default.topics[topic]`
150
175
  4. Subject-specific preferences (when hydrating a subject context)
151
176
 
152
- Minimal broadcast data shape:
177
+ Minimal broadcast data shape (`attach` is optional — uploaded assets only, the same files on every email):
153
178
  ```json
154
179
  {
155
180
  "data": {
156
181
  "topic": "newsletter",
182
+ "attach": [{ "asset": "a1b2c3" }],
157
183
  "channelSettings": {
158
184
  "mode": "preferred",
159
185
  "channels": [
@@ -703,6 +703,35 @@ export async function printLabel(ctx, event) {
703
703
  - **Verify:** `pdf.decodeBarcodes` on a `signedUrl` reads the codes back deterministically, so you can
704
704
  confirm the digits before answering.
705
705
 
706
+ ### Making an email attachment
707
+
708
+ A flow step can attach a file your function makes: `"attach": [{ "function": "receiptPdf" }]` in a
709
+ site's `cloud/flows.json` (or an app's blueprint). When the email goes, the platform calls your function
710
+ (your app's, in this collection, as the collection, like a connector step) with:
711
+
712
+ ```ts
713
+ event.body = { purpose: 'attachment', eventType, interactionId, outcome, contactId, metadata, booking? } // booking: ready-formatted
714
+ ```
715
+
716
+ and attaches what it returns:
717
+
718
+ ```ts
719
+ // capabilities: ["tools:pdf.create"]
720
+ export async function receiptPdf(ctx, event) {
721
+ const b = event.body.booking
722
+ const pdf = await ctx.sl.tools.run('pdf.create', { format: 'A4', html: `<h1>Receipt</h1><p>${b.serviceName}, ${b.when.dateTime}</p>`, return: 'signedUrl' })
723
+ return { filename: `receipt-${b.id}.pdf`, file: pdf.url } // a private file you made
724
+ // or { filename, contentType: 'application/pdf', content: '<base64>' }, a list of those, or null for none
725
+ }
726
+ ```
727
+
728
+ - Files: PDF, calendar (`.ics`), images, text / CSV and Office documents; at most 5 per email and 10 MB
729
+ in all. A private file must be one this platform made (`return: 'signedUrl'`), never an outside URL.
730
+ - If your function fails or returns something else, the email still goes without it (the reason is in
731
+ your function's runs).
732
+ - Other sources need no code: `"calendar"` (a booking event's calendar invite) and
733
+ `{ "asset": "<assetId>" }` (a file in the collection's assets) — see bookings.md, "Flows on booking events".
734
+
706
735
  ---
707
736
 
708
737
  ## Worked example — a public competition entry
package/dist/openapi.yaml CHANGED
@@ -24805,6 +24805,11 @@ components:
24805
24805
  $ref: "#/components/schemas/BookingStatus"
24806
24806
  ref:
24807
24807
  type: string
24808
+ attach:
24809
+ type: array
24810
+ items:
24811
+ type: object
24812
+ additionalProperties: true
24808
24813
  required:
24809
24814
  - templateId
24810
24815
  MessageBookersResult:
@@ -24999,6 +25004,11 @@ components:
24999
25004
  type: boolean
25000
25005
  priority:
25001
25006
  type: number
25007
+ attach:
25008
+ type: array
25009
+ items:
25010
+ type: object
25011
+ additionalProperties: true
25002
25012
  createdAt:
25003
25013
  type: string
25004
25014
  required:
@@ -26130,6 +26140,10 @@ components:
26130
26140
  type: string
26131
26141
  appId:
26132
26142
  type: string
26143
+ attach:
26144
+ type: array
26145
+ items:
26146
+ $ref: "#/components/schemas/EmailAttachmentSpec"
26133
26147
  required:
26134
26148
  - contactId
26135
26149
  - templateId
@@ -26149,6 +26163,10 @@ components:
26149
26163
  - wallet
26150
26164
  messageId:
26151
26165
  type: string
26166
+ attachmentsLeftOut:
26167
+ type: array
26168
+ items:
26169
+ type: string
26152
26170
  required:
26153
26171
  - ok
26154
26172
  - channel
@@ -26172,6 +26190,9 @@ components:
26172
26190
  $ref: "#/components/schemas/CommsSettings"
26173
26191
  ConsentChannels:
26174
26192
  $ref: "#/components/schemas/BroadcastChannel"
26193
+ EmailAttachmentSpec:
26194
+ type: object
26195
+ additionalProperties: true
26175
26196
  TransactionalSendResult:
26176
26197
  anyOf:
26177
26198
  - $ref: "#/components/schemas/TransactionalSendResponse"
@@ -283,6 +283,8 @@ export interface MessageBookersInput {
283
283
  /** Default: held, pending, confirmed. */
284
284
  statuses?: BookingStatus[];
285
285
  ref?: string;
286
+ /** Files on each email, as comms.sendTransactional — `'calendar'` is each person's own booking. */
287
+ attach?: import('./comms.js').EmailAttachmentSpec[];
286
288
  }
287
289
  export interface MessageBookersResult {
288
290
  matched: number;
@@ -34,6 +34,10 @@ export interface BroadcastRecord {
34
34
  priority?: number;
35
35
  }>;
36
36
  };
37
+ /** Files on every email of the broadcast (at most 5, 10 MB in all): uploaded assets only. */
38
+ attach?: Array<{
39
+ asset: string;
40
+ }>;
37
41
  [key: string]: unknown;
38
42
  };
39
43
  createdAt: string;
@@ -355,13 +355,41 @@ export interface TransactionalSendRequest {
355
355
  ref?: string;
356
356
  /** App context stored on the comms-events row */
357
357
  appId?: string;
358
+ /**
359
+ * Files to attach (email only; at most 5, 10 MB in all). What can't be made is left out and listed in
360
+ * `attachmentsLeftOut` — the email still goes.
361
+ */
362
+ attach?: EmailAttachmentSpec[];
358
363
  }
364
+ /**
365
+ * One file for an email:
366
+ * - `'calendar'` — the booking's `.ics` calendar invite (needs `include.booking` and `appId`)
367
+ * - `{ asset }` — a file uploaded to this collection's assets
368
+ * - `{ function }` — the app's (`appId`) own server function makes it (see server-functions.md, "Making an email attachment")
369
+ * - `{ filename, contentType, content }` — a file you made, base64 (PDF, Office documents, images, text, CSV, calendar)
370
+ * - `{ filename, file }` — a private file you made (e.g. `pdf.create` with `return: 'signedUrl'`)
371
+ */
372
+ export type EmailAttachmentSpec = 'calendar' | {
373
+ asset: string;
374
+ } | {
375
+ function: string;
376
+ } | {
377
+ filename: string;
378
+ contentType: string;
379
+ content: string;
380
+ } | {
381
+ filename: string;
382
+ contentType?: string;
383
+ file: string;
384
+ };
359
385
  export interface TransactionalSendResponse {
360
386
  ok: true;
361
387
  /** The channel the message was actually sent on */
362
388
  channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet';
363
389
  /** Provider message ID (email/SMS/WhatsApp); absent for push/wallet */
364
390
  messageId?: string;
391
+ /** Attachments that couldn't be made, and why (the email went without them) */
392
+ attachmentsLeftOut?: string[];
365
393
  }
366
394
  export interface TransactionalSendError {
367
395
  ok: false;
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.1.6 | Generated: 2026-10-31T10:38:17.995Z
3
+ Version: 2.1.7 | Generated: 2026-11-01T12:48:45.040Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -5402,6 +5402,7 @@ interface MessageBookersInput {
5402
5402
  resourceId?: string
5403
5403
  statuses?: BookingStatus[]
5404
5404
  ref?: string
5405
+ attach?: import('./comms').EmailAttachmentSpec[]
5405
5406
  }
5406
5407
  ```
5407
5408
 
@@ -5512,6 +5513,7 @@ interface BroadcastRecord {
5512
5513
  priority?: number
5513
5514
  }>
5514
5515
  }
5516
+ attach?: Array<{ asset: string }>
5515
5517
  [key: string]: unknown
5516
5518
  }
5517
5519
  createdAt: string
@@ -6242,6 +6244,7 @@ interface TransactionalSendRequest {
6242
6244
  }
6243
6245
  ref?: string
6244
6246
  appId?: string
6247
+ attach?: EmailAttachmentSpec[]
6245
6248
  }
6246
6249
  ```
6247
6250
 
@@ -6251,6 +6254,7 @@ interface TransactionalSendResponse {
6251
6254
  ok: true
6252
6255
  channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'
6253
6256
  messageId?: string
6257
+ attachmentsLeftOut?: string[]
6254
6258
  }
6255
6259
  ```
6256
6260
 
@@ -6270,6 +6274,8 @@ interface TransactionalSendError {
6270
6274
 
6271
6275
  **ConsentChannels** = `Partial<Record<BroadcastChannel, boolean>>`
6272
6276
 
6277
+ **EmailAttachmentSpec** = ``
6278
+
6273
6279
  **TransactionalSendResult** = `// src/types/comms.ts
6274
6280
  // Communication and notification types for the Smartlinks API
6275
6281
  import type { IdField } from './common'
@@ -6595,14 +6601,36 @@ export interface TransactionalSendRequest {
6595
6601
  ref?: string
6596
6602
  /** App context stored on the comms-events row */
6597
6603
  appId?: string
6604
+ /**
6605
+ * Files to attach (email only; at most 5, 10 MB in all). What can't be made is left out and listed in
6606
+ * `attachmentsLeftOut` — the email still goes.
6607
+ */
6608
+ attach?: EmailAttachmentSpec[]
6598
6609
  }
6599
6610
 
6611
+ /**
6612
+ * One file for an email:
6613
+ * - `'calendar'` — the booking's `.ics` calendar invite (needs `include.booking` and `appId`)
6614
+ * - `{ asset }` — a file uploaded to this collection's assets
6615
+ * - `{ function }` — the app's (`appId`) own server function makes it (see server-functions.md, "Making an email attachment")
6616
+ * - `{ filename, contentType, content }` — a file you made, base64 (PDF, Office documents, images, text, CSV, calendar)
6617
+ * - `{ filename, file }` — a private file you made (e.g. `pdf.create` with `return: 'signedUrl'`)
6618
+ */
6619
+ export type EmailAttachmentSpec =
6620
+ | 'calendar'
6621
+ | { asset: string }
6622
+ | { function: string }
6623
+ | { filename: string; contentType: string; content: string }
6624
+ | { filename: string; contentType?: string; file: string }
6625
+
6600
6626
  export interface TransactionalSendResponse {
6601
6627
  ok: true
6602
6628
  /** The channel the message was actually sent on */
6603
6629
  channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'
6604
6630
  /** Provider message ID (email/SMS/WhatsApp); absent for push/wallet */
6605
6631
  messageId?: string
6632
+ /** Attachments that couldn't be made, and why (the email went without them) */
6633
+ attachmentsLeftOut?: string[]
6606
6634
  }
6607
6635
 
6608
6636
  export interface TransactionalSendError {
package/docs/bookings.md CHANGED
@@ -368,6 +368,17 @@ wording.
368
368
  A **public** resource record (a staff profile the booking page lists) keeps its address on a
369
369
  **private child record** instead (`visibility: 'admin'`, `parentType: 'record'`, `parentId` = the
370
370
  resource) — the platform looks there too, and the address never ships with the public listing.
371
+ - Any step can also take `replyTo` — an address, or `"contact"` (replies go to the person the event is
372
+ about: a team notification answered straight to the booker) — and `cc` / `bcc` (written-out addresses,
373
+ same rules as `to`, counted in the same daily cap; over it the person still gets their email, without the
374
+ copies). A site's `cloud/email.json` (`{ "fromName", "replyTo" }`) sets the name the business's emails come
375
+ from and their default reply-to; the From address is the business's verified domain.
376
+ - `attach` on a step adds files (at most 5, 10 MB in all): `"calendar"` — the booking as an `.ics` invite
377
+ that drops straight into the person's calendar (a cancelled booking's invite cancels it there);
378
+ `{ "asset": "<assetId>" }` — a file in the collection's assets (terms, a price list);
379
+ `{ "function": "<name>" }` — your app's own function makes it (a receipt PDF: server-functions.md,
380
+ "Making an email attachment"). E.g. `{ "send": "booking-confirmed", "to": "contact", "attach": ["calendar"] }`.
381
+ One that can't be made is left out; the email still goes.
371
382
  - Who gets what, in templates: `{{ booking }}` is the booking **ready-formatted**: its fields and
372
383
  `data`, plus `serviceName`, `staffName`, `customer`, `durationMinutes`, `when` (`date`,
373
384
  `shortDate`, `time`, `endTime`, `dateTime`, `timezone` — in the business's timezone) and, when the
@@ -393,9 +404,14 @@ window (optionally with one resource), each hydrated with **their own** booking.
393
404
  await SL.bookings.message(collectionId, appId, { templateId, slotId })
394
405
  // Everyone booked with Sam next week, by email even if they prefer texts
395
406
  await SL.bookings.message(collectionId, appId, { templateId, channel: 'email', from, to, resourceType: 'record', resourceId: samId })
407
+ // Everyone on the class, each with their own calendar invite
408
+ await SL.bookings.message(collectionId, appId, { templateId, slotId, attach: ['calendar'] })
396
409
  // → { matched, sent, skipped: { noContact, duplicate, failed }, failures: [{ bookingId, reason }] }
397
410
  ```
398
411
 
412
+ `attach` takes the same files as `comms.sendTransactional` (comms.md, "Attachments"); `'calendar'` is
413
+ each person's own booking.
414
+
399
415
  Statuses default to held, pending and confirmed; at most 500 recipients per call (narrow the window
400
416
  for more). One message per contact on a slot, one per booking in a window. Also
401
417
  `ctx.sl.bookings.message(input)` in a server function (`sl:bookings:write`) — a cron function that
package/docs/comms.md CHANGED
@@ -56,6 +56,7 @@ if (result.ok) {
56
56
  | `include` | object | Hydration flags — see table below. |
57
57
  | `ref` | `string` | Arbitrary reference string stored with the event. |
58
58
  | `appId` | `string` | App identifier, used for context/logging. |
59
+ | `attach` | `EmailAttachmentSpec[]` | Files on the email — see [Attachments](#attachments). |
59
60
 
60
61
  **`include` fields:**
61
62
 
@@ -73,12 +74,36 @@ if (result.ok) {
73
74
 
74
75
  ```typescript
75
76
  // Success
76
- { ok: true; channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'; messageId?: string }
77
+ { ok: true; channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'; messageId?: string; attachmentsLeftOut?: string[] }
77
78
 
78
79
  // Failure
79
80
  { ok: false; error: string }
80
81
  ```
81
82
 
83
+ ### Attachments
84
+
85
+ `attach` puts files on the email (email only — ignored when the send goes by text or push). At most 5
86
+ files and 10 MB in all; PDF, Word / Excel / PowerPoint, images, text, CSV and calendar files.
87
+
88
+ ```typescript
89
+ await comms.sendTransactional(collectionId, {
90
+ contactId, templateId: 'booking-confirmed', appId: 'appointment-booking',
91
+ include: { booking: bookingId },
92
+ attach: [
93
+ 'calendar', // the booking's .ics invite (needs include.booking + appId)
94
+ { asset: 'a1b2c3' }, // a file uploaded to the collection's assets
95
+ { function: 'receiptPdf' }, // the app's own server function makes it
96
+ { filename: 'Quote.pdf', contentType: 'application/pdf', content: base64 }, // a file you made
97
+ { filename: 'Report.pdf', file: signedUrl }, // a private file you made (pdf.create return: 'signedUrl')
98
+ ],
99
+ })
100
+ ```
101
+
102
+ A malformed list is refused (`transactional.attach_invalid`). A file that can't be made at send time
103
+ (a missing asset, a function that fails) is left out and listed in `attachmentsLeftOut` — the email
104
+ still goes. The request body is capped at 10 MB, so for big files make a private file and pass `file`.
105
+ `bookings.message` takes the same `attach` (`'calendar'` is each person's own booking).
106
+
82
107
  ---
83
108
 
84
109
  ## Broadcasts
@@ -149,11 +174,12 @@ Consent resolution order (per recipient, per channel):
149
174
  3. `preferences._default.topics[topic]`
150
175
  4. Subject-specific preferences (when hydrating a subject context)
151
176
 
152
- Minimal broadcast data shape:
177
+ Minimal broadcast data shape (`attach` is optional — uploaded assets only, the same files on every email):
153
178
  ```json
154
179
  {
155
180
  "data": {
156
181
  "topic": "newsletter",
182
+ "attach": [{ "asset": "a1b2c3" }],
157
183
  "channelSettings": {
158
184
  "mode": "preferred",
159
185
  "channels": [
@@ -703,6 +703,35 @@ export async function printLabel(ctx, event) {
703
703
  - **Verify:** `pdf.decodeBarcodes` on a `signedUrl` reads the codes back deterministically, so you can
704
704
  confirm the digits before answering.
705
705
 
706
+ ### Making an email attachment
707
+
708
+ A flow step can attach a file your function makes: `"attach": [{ "function": "receiptPdf" }]` in a
709
+ site's `cloud/flows.json` (or an app's blueprint). When the email goes, the platform calls your function
710
+ (your app's, in this collection, as the collection, like a connector step) with:
711
+
712
+ ```ts
713
+ event.body = { purpose: 'attachment', eventType, interactionId, outcome, contactId, metadata, booking? } // booking: ready-formatted
714
+ ```
715
+
716
+ and attaches what it returns:
717
+
718
+ ```ts
719
+ // capabilities: ["tools:pdf.create"]
720
+ export async function receiptPdf(ctx, event) {
721
+ const b = event.body.booking
722
+ const pdf = await ctx.sl.tools.run('pdf.create', { format: 'A4', html: `<h1>Receipt</h1><p>${b.serviceName}, ${b.when.dateTime}</p>`, return: 'signedUrl' })
723
+ return { filename: `receipt-${b.id}.pdf`, file: pdf.url } // a private file you made
724
+ // or { filename, contentType: 'application/pdf', content: '<base64>' }, a list of those, or null for none
725
+ }
726
+ ```
727
+
728
+ - Files: PDF, calendar (`.ics`), images, text / CSV and Office documents; at most 5 per email and 10 MB
729
+ in all. A private file must be one this platform made (`return: 'signedUrl'`), never an outside URL.
730
+ - If your function fails or returns something else, the email still goes without it (the reason is in
731
+ your function's runs).
732
+ - Other sources need no code: `"calendar"` (a booking event's calendar invite) and
733
+ `{ "asset": "<assetId>" }` (a file in the collection's assets) — see bookings.md, "Flows on booking events".
734
+
706
735
  ---
707
736
 
708
737
  ## Worked example — a public competition entry
package/openapi.yaml CHANGED
@@ -24805,6 +24805,11 @@ components:
24805
24805
  $ref: "#/components/schemas/BookingStatus"
24806
24806
  ref:
24807
24807
  type: string
24808
+ attach:
24809
+ type: array
24810
+ items:
24811
+ type: object
24812
+ additionalProperties: true
24808
24813
  required:
24809
24814
  - templateId
24810
24815
  MessageBookersResult:
@@ -24999,6 +25004,11 @@ components:
24999
25004
  type: boolean
25000
25005
  priority:
25001
25006
  type: number
25007
+ attach:
25008
+ type: array
25009
+ items:
25010
+ type: object
25011
+ additionalProperties: true
25002
25012
  createdAt:
25003
25013
  type: string
25004
25014
  required:
@@ -26130,6 +26140,10 @@ components:
26130
26140
  type: string
26131
26141
  appId:
26132
26142
  type: string
26143
+ attach:
26144
+ type: array
26145
+ items:
26146
+ $ref: "#/components/schemas/EmailAttachmentSpec"
26133
26147
  required:
26134
26148
  - contactId
26135
26149
  - templateId
@@ -26149,6 +26163,10 @@ components:
26149
26163
  - wallet
26150
26164
  messageId:
26151
26165
  type: string
26166
+ attachmentsLeftOut:
26167
+ type: array
26168
+ items:
26169
+ type: string
26152
26170
  required:
26153
26171
  - ok
26154
26172
  - channel
@@ -26172,6 +26190,9 @@ components:
26172
26190
  $ref: "#/components/schemas/CommsSettings"
26173
26191
  ConsentChannels:
26174
26192
  $ref: "#/components/schemas/BroadcastChannel"
26193
+ EmailAttachmentSpec:
26194
+ type: object
26195
+ additionalProperties: true
26175
26196
  TransactionalSendResult:
26176
26197
  anyOf:
26177
26198
  - $ref: "#/components/schemas/TransactionalSendResponse"
package/package.json CHANGED
@@ -1,87 +1,87 @@
1
- {
2
- "name": "@proveanything/smartlinks",
3
- "version": "2.1.6",
4
- "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js",
12
- "default": "./dist/index.js"
13
- },
14
- "./testing": {
15
- "types": "./dist/testing/index.d.ts",
16
- "import": "./dist/testing/index.js",
17
- "default": "./dist/testing/index.js"
18
- },
19
- "./*": {
20
- "types": "./dist/*.d.ts",
21
- "import": "./dist/*.js",
22
- "default": "./dist/*.js"
23
- },
24
- "./baseline.css": "./dist/baseline.css",
25
- "./baseline.classes.json": "./dist/baseline.classes.json",
26
- "./theme.css": "./dist/theme.css",
27
- "./theme-boot.js": "./dist/theme-boot.js",
28
- "./package.json": "./package.json"
29
- },
30
- "bin": {
31
- "smartlinks-register-release": "scripts/register-release.mjs",
32
- "smartlinks-publish": "scripts/publish.mjs",
33
- "smartlinks-doctor": "scripts/doctor.mjs",
34
- "smartlinks-headless": "scripts/headless-check.mjs"
35
- },
36
- "files": [
37
- "dist/",
38
- "docs/",
39
- "scripts/register-release.mjs",
40
- "scripts/publish.mjs",
41
- "scripts/doctor.mjs",
42
- "scripts/headless-check.mjs",
43
- "scripts/lib/",
44
- "openapi.yaml",
45
- "README.md"
46
- ],
47
- "scripts": {
48
- "test": "node test/run.cjs",
49
- "build": "tsc && node scripts/fix-esm-extensions.mjs && node generate-api-summary.cjs && node generate-openapi.cjs && node scripts/copy-docs-to-dist.cjs && node scripts/build-baseline.mjs",
50
- "docs": "typedoc",
51
- "docs:summary": "node generate-api-summary.cjs",
52
- "docs:openapi": "node generate-openapi.cjs",
53
- "docs:openapi:lint": "npx --yes @redocly/cli@latest lint openapi.yaml",
54
- "docs:openapi:check": "node generate-openapi.cjs && npx --yes @redocly/cli@latest lint openapi.yaml",
55
- "build:docs": "tsc build-docs.ts --outDir dist && node dist/build-docs.js",
56
- "prepublishOnly": "npm run build"
57
- },
58
- "keywords": [
59
- "smartlinks",
60
- "api",
61
- "authentication",
62
- "traceability",
63
- "blockchain",
64
- "typescript",
65
- "sdk"
66
- ],
67
- "author": "Glenn Shoosmith",
68
- "license": "MIT",
69
- "publishConfig": {
70
- "access": "public"
71
- },
72
- "dependencies": {
73
- "cross-fetch": "^3.1.5",
74
- "es-module-lexer": "^1.7.0"
75
- },
76
- "devDependencies": {
77
- "ts-node": "^10.9.1",
78
- "typedoc": "^0.25.13",
79
- "typedoc-plugin-markdown": "^3.17.1",
80
- "typescript": "^4.9.0"
81
- },
82
- "repository": {
83
- "type": "git",
84
- "url": "https://github.com/Prove-Anything/smartlinks.git"
85
- },
86
- "homepage": "https://smartlinks.app"
87
- }
1
+ {
2
+ "name": "@proveanything/smartlinks",
3
+ "version": "2.1.7",
4
+ "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./testing": {
15
+ "types": "./dist/testing/index.d.ts",
16
+ "import": "./dist/testing/index.js",
17
+ "default": "./dist/testing/index.js"
18
+ },
19
+ "./*": {
20
+ "types": "./dist/*.d.ts",
21
+ "import": "./dist/*.js",
22
+ "default": "./dist/*.js"
23
+ },
24
+ "./baseline.css": "./dist/baseline.css",
25
+ "./baseline.classes.json": "./dist/baseline.classes.json",
26
+ "./theme.css": "./dist/theme.css",
27
+ "./theme-boot.js": "./dist/theme-boot.js",
28
+ "./package.json": "./package.json"
29
+ },
30
+ "bin": {
31
+ "smartlinks-register-release": "scripts/register-release.mjs",
32
+ "smartlinks-publish": "scripts/publish.mjs",
33
+ "smartlinks-doctor": "scripts/doctor.mjs",
34
+ "smartlinks-headless": "scripts/headless-check.mjs"
35
+ },
36
+ "files": [
37
+ "dist/",
38
+ "docs/",
39
+ "scripts/register-release.mjs",
40
+ "scripts/publish.mjs",
41
+ "scripts/doctor.mjs",
42
+ "scripts/headless-check.mjs",
43
+ "scripts/lib/",
44
+ "openapi.yaml",
45
+ "README.md"
46
+ ],
47
+ "scripts": {
48
+ "test": "node test/run.cjs",
49
+ "build": "tsc && node scripts/fix-esm-extensions.mjs && node generate-api-summary.cjs && node generate-openapi.cjs && node scripts/copy-docs-to-dist.cjs && node scripts/build-baseline.mjs",
50
+ "docs": "typedoc",
51
+ "docs:summary": "node generate-api-summary.cjs",
52
+ "docs:openapi": "node generate-openapi.cjs",
53
+ "docs:openapi:lint": "npx --yes @redocly/cli@latest lint openapi.yaml",
54
+ "docs:openapi:check": "node generate-openapi.cjs && npx --yes @redocly/cli@latest lint openapi.yaml",
55
+ "build:docs": "tsc build-docs.ts --outDir dist && node dist/build-docs.js",
56
+ "prepublishOnly": "npm run build"
57
+ },
58
+ "keywords": [
59
+ "smartlinks",
60
+ "api",
61
+ "authentication",
62
+ "traceability",
63
+ "blockchain",
64
+ "typescript",
65
+ "sdk"
66
+ ],
67
+ "author": "Glenn Shoosmith",
68
+ "license": "MIT",
69
+ "publishConfig": {
70
+ "access": "public"
71
+ },
72
+ "dependencies": {
73
+ "cross-fetch": "^3.1.5",
74
+ "es-module-lexer": "^1.7.0"
75
+ },
76
+ "devDependencies": {
77
+ "ts-node": "^10.9.1",
78
+ "typedoc": "^0.25.13",
79
+ "typedoc-plugin-markdown": "^3.17.1",
80
+ "typescript": "^4.9.0"
81
+ },
82
+ "repository": {
83
+ "type": "git",
84
+ "url": "https://github.com/Prove-Anything/smartlinks.git"
85
+ },
86
+ "homepage": "https://smartlinks.app"
87
+ }