@proveanything/smartlinks 1.13.6 → 1.13.9

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/docs/comms.md CHANGED
@@ -19,7 +19,7 @@ This guide covers the full communications surface of the SDK: transactional send
19
19
 
20
20
  Sends a single message to one contact using a template. No broadcast record is created. The send is logged to the contact's communication history with `sourceType: 'transactional'`.
21
21
 
22
- **Endpoint:** `POST /admin/collection/:collectionId/comm.send`
22
+ **Endpoint:** `POST /admin/collection/:collectionId/comm/send`
23
23
 
24
24
  ```typescript
25
25
  import { comms } from '@proveanything/smartlinks'
@@ -28,7 +28,7 @@ import type { TransactionalSendRequest } from '@proveanything/smartlinks'
28
28
  const result = await comms.sendTransactional('collectionId', {
29
29
  contactId: 'e4f2a1b0-...',
30
30
  templateId: 'warranty-update',
31
- channel: 'preferred', // 'email' | 'sms' | 'push' | 'wallet' | 'preferred' (default)
31
+ channel: 'preferred', // 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet' | 'preferred' (default)
32
32
  props: { claimRef: 'CLM-0042', decision: 'approved' },
33
33
  include: {
34
34
  productId: 'prod-abc123', // hydrate product context into template
@@ -51,7 +51,7 @@ if (result.ok) {
51
51
  |-------|------|-------------|
52
52
  | `contactId` | `string` | **Required.** Contact to send to. |
53
53
  | `templateId` | `string` | **Required.** Template to render. |
54
- | `channel` | `'email' \| 'sms' \| 'push' \| 'wallet' \| 'preferred'` | Channel to send on. `'preferred'` (default) picks the contact's best available channel. |
54
+ | `channel` | `'email' \| 'sms' \| 'whatsapp' \| 'push' \| 'wallet' \| 'preferred'` | Channel to send on. `'preferred'` (default) picks the contact's best available channel. |
55
55
  | `props` | `Record<string, unknown>` | Extra variables merged into template rendering. |
56
56
  | `include` | object | Hydration flags — see table below. |
57
57
  | `ref` | `string` | Arbitrary reference string stored with the event. |
@@ -73,7 +73,7 @@ if (result.ok) {
73
73
 
74
74
  ```typescript
75
75
  // Success
76
- { ok: true; channel: 'email' | 'sms' | 'push' | 'wallet'; messageId?: string }
76
+ { ok: true; channel: 'email' | 'sms' | 'whatsapp' | 'push' | 'wallet'; messageId?: string }
77
77
 
78
78
  // Failure
79
79
  { ok: false; error: string }
@@ -280,6 +280,195 @@ When defining a journey trigger, reference the `interactionId` that should fire
280
280
 
281
281
  ---
282
282
 
283
+ ## Interaction Effects
284
+
285
+ **Interaction effects** are automatic side-effects that fire immediately after an interaction event is recorded. They are defined directly on the interaction definition (`data.effects`) and run inline, fire-and-forget — they never block or fail the interaction response.
286
+
287
+ Common use cases: send a confirmation email, post a webhook to a CRM, tag the contact, award loyalty points, create an app record, or add the contact to a segment.
288
+
289
+ ### How It Works
290
+
291
+ 1. Event is logged to BigQuery
292
+ 2. Interaction definition is loaded
293
+ 3. Each configured effect runs **in order**, fire-and-forget
294
+ 4. Response is returned to the caller immediately
295
+
296
+ A failure in one effect is logged and swallowed; subsequent effects still run.
297
+
298
+ > **Note:** `transactional` and `webhook` effects currently run inline. They are planned to move to background jobs for retry-on-failure support once the jobs infrastructure is fully operational.
299
+
300
+ ### Configuring Effects
301
+
302
+ Pass `data.effects` when creating or updating an interaction type:
303
+
304
+ ```typescript
305
+ await SL.interactions.create(collectionId, {
306
+ id: 'donation',
307
+ appId: 'my-app',
308
+ permissions: { allowPublicSubmit: true },
309
+ data: {
310
+ effects: [
311
+ {
312
+ type: 'transactional',
313
+ config: {
314
+ templateId: 'tmpl_donation_receipt',
315
+ channel: 'email',
316
+ props: {
317
+ donationAmount: '{{metadata.amount}}',
318
+ campaignName: '{{metadata.campaign}}',
319
+ },
320
+ },
321
+ },
322
+ {
323
+ type: 'tag',
324
+ config: { tags: ['donor'] },
325
+ },
326
+ ],
327
+ },
328
+ });
329
+ ```
330
+
331
+ ### Token Interpolation
332
+
333
+ All effect `config` values support `{{token}}` interpolation resolved from the event context at runtime. Nested paths using dot notation are supported (e.g. `{{metadata.amount}}`).
334
+
335
+ | Token | Description |
336
+ |---|---|
337
+ | `{{eventId}}` | BigQuery event UUID |
338
+ | `{{collectionId}}` | Collection / brand ID |
339
+ | `{{appId}}` | App ID that submitted the event |
340
+ | `{{interactionId}}` | Interaction definition ID |
341
+ | `{{contactId}}` | Contact UUID (if resolved) |
342
+ | `{{userId}}` | Firebase UID (if authenticated) |
343
+ | `{{productId}}` | Product ID (if provided with event) |
344
+ | `{{proofId}}` | Proof ID (if provided with event) |
345
+ | `{{variantId}}` | Variant ID (if provided with event) |
346
+ | `{{batchId}}` | Batch ID (if provided with event) |
347
+ | `{{outcome}}` | Event outcome (e.g. `"submitted"`) |
348
+ | `{{eventType}}` | Event type string |
349
+ | `{{source}}` | Event source string |
350
+ | `{{timestamp}}` | ISO 8601 event timestamp |
351
+ | `{{metadata.*}}` | Any key from the event `metadata` object |
352
+
353
+ Tokens that resolve to `null` or `undefined` become an empty string.
354
+
355
+ ### Effect Types
356
+
357
+ #### `loyalty`
358
+
359
+ Awards loyalty points by evaluating `LoyaltyEarningRule` records configured against this interaction. Earning rules are managed separately via the Loyalty API. If no rules are configured, this effect is a no-op.
360
+
361
+ ```json
362
+ { "type": "loyalty", "config": {} }
363
+ ```
364
+
365
+ #### `transactional`
366
+
367
+ Sends a message to the contact using a comms template. Supports all channels with full Liquid template hydration.
368
+
369
+ ```json
370
+ {
371
+ "type": "transactional",
372
+ "config": {
373
+ "templateId": "tmpl_donation_receipt",
374
+ "channel": "email",
375
+ "props": {
376
+ "donationAmount": "{{metadata.amount}}"
377
+ }
378
+ }
379
+ }
380
+ ```
381
+
382
+ `channel` defaults to `"preferred"` — auto-selects the contact's best available channel respecting consent, suppression, and template availability. Optionally pass `include` to hydrate product/proof/user context, and `props` for additional Liquid variables.
383
+
384
+ #### `webhook`
385
+
386
+ Posts a JSON payload to an HTTP endpoint. All config values support `{{token}}` interpolation.
387
+
388
+ ```json
389
+ {
390
+ "type": "webhook",
391
+ "config": {
392
+ "url": "https://crm.example.com/api/events",
393
+ "headers": { "Authorization": "Bearer sk_live_abc123" },
394
+ "body": {
395
+ "contactId": "{{contactId}}",
396
+ "amount": "{{metadata.amount}}"
397
+ },
398
+ "timeout": 8000
399
+ }
400
+ }
401
+ ```
402
+
403
+ When `body` is omitted, a standard payload is sent containing `eventId`, `collectionId`, `appId`, `interactionId`, `contactId`, `userId`, `productId`, `proofId`, `outcome`, `eventType`, `metadata`, and `timestamp`.
404
+
405
+ #### `tag`
406
+
407
+ Adds or removes string tags on the contact record. Skipped if there is no `contactId` on the event.
408
+
409
+ ```json
410
+ {
411
+ "type": "tag",
412
+ "config": {
413
+ "tags": ["donor", "tier-{{metadata.tier}}"],
414
+ "action": "add"
415
+ }
416
+ }
417
+ ```
418
+
419
+ `action` defaults to `"add"`. Duplicate adds are no-ops; removing a tag not present is a no-op.
420
+
421
+ #### `appRecord`
422
+
423
+ Creates or upserts an app record scoped to the event context. Use `singletonPer` for cardinality control — if a matching singleton already exists it is updated rather than duplicated.
424
+
425
+ ```json
426
+ {
427
+ "type": "appRecord",
428
+ "config": {
429
+ "recordType": "participation",
430
+ "singletonPer": "contact",
431
+ "data": {
432
+ "participatedAt": "{{timestamp}}",
433
+ "outcome": "{{outcome}}"
434
+ }
435
+ }
436
+ }
437
+ ```
438
+
439
+ Common `singletonPer` values: `"contact"`, `"product"`, `"proof"`, `"global"`.
440
+
441
+ #### `segment`
442
+
443
+ Adds or removes the contact from a **static** segment. Skipped if there is no `contactId` on the event.
444
+
445
+ ```json
446
+ {
447
+ "type": "segment",
448
+ "config": {
449
+ "segmentId": "seg_donors_2026",
450
+ "action": "add"
451
+ }
452
+ }
453
+ ```
454
+
455
+ The segment must already exist and be of `filterType: 'static'`.
456
+
457
+ ### Effects Error Handling
458
+
459
+ | Scenario | Behaviour |
460
+ |---|---|
461
+ | Effect throws (e.g. template not found, webhook 500) | Error logged server-side; next effect continues |
462
+ | Contact has no email, `transactional` channel = `email` | Effect throws, logged, skipped |
463
+ | `contactId` absent on anonymous event | `tag`, `appRecord`, `segment` log a warning and skip |
464
+ | Unknown `type` value | Warning logged, effect skipped |
465
+ | `loyalty` — no earning rules configured | Silent no-op |
466
+ | Interaction has no `data.effects` | No effects run, no overhead |
467
+
468
+ Effects do **not** propagate errors back to the API caller.
469
+
470
+ ---
471
+
283
472
  ## TypeScript Types
284
473
 
285
474
  ```typescript
@@ -299,6 +488,17 @@ import type {
299
488
  AdminInteractionsCountsByOutcomeRequest,
300
489
  PublicInteractionsCountsByOutcomeRequest,
301
490
  PublicInteractionsByUserRequest,
491
+ // Effects
492
+ InteractionEffect, // Single effect entry { type, config? }
493
+ EffectType, // 'loyalty' | 'transactional' | 'webhook' | 'tag' | 'appRecord' | 'segment'
494
+ EffectConfig, // Union of all effect config shapes
495
+ LoyaltyEffectConfig,
496
+ TransactionalEffectConfig,
497
+ WebhookEffectConfig,
498
+ TagEffectConfig,
499
+ AppRecordEffectConfig,
500
+ SegmentEffectConfig,
501
+ InteractionEventContext, // Token interpolation context shape
302
502
  } from '@proveanything/smartlinks';
303
503
  ```
304
504
 
package/docs/overview.md CHANGED
@@ -43,6 +43,8 @@ Widgets and containers run in the parent's React tree (not iframes). Mobile Admi
43
43
 
44
44
  The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive documentation in `node_modules/@proveanything/smartlinks/docs/`. **Always read these files for detailed implementation guidance.**
45
45
 
46
+ > Product endpoints: use `products` (plural) for new integrations. The older `product` (singular) namespace remains for backward compatibility and is deprecated.
47
+
46
48
  > **Minimum SDK version: `1.4.1`** — Ensure `@proveanything/smartlinks` is at least this version. If not, update with `npm install @proveanything/smartlinks@latest`.
47
49
 
48
50
  | Topic | File | When to Use |