ab-ecosystem-cpass 1.0.8 → 1.0.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/README.md CHANGED
@@ -271,6 +271,13 @@ await crm.campaigns.createGroup({
271
271
  });
272
272
  ```
273
273
 
274
+ **Creating a campaign does not send it.** Every create returns a draft:
275
+ `pending`, with no `scheduledAt`. `crm.campaigns.trigger(id)` sends it now (with
276
+ about a minute in which `cancel` still works), and
277
+ `crm.campaigns.trigger(id, { scheduledAt })` sends it then. A send time is
278
+ refused on `create` and `update`, because arming a campaign is also what records
279
+ who authorised it.
280
+
274
281
  Note that `campaign.receivers` is `0` on a campaign you just created: the
275
282
  audience is resolved when the campaign sends, not when it is built. The
276
283
  `receivers` on the result is the count written into the file, and it is the one
@@ -278,7 +285,7 @@ to show a user.
278
285
 
279
286
  | Resource | Page | Actions |
280
287
  | --- | --- | --- |
281
- | `crm.campaigns` | [campaigns](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/campaign/campaigns.md) | `list` `get` `create` `createBulk` `createGroup` `uploadReceivers` `update` `schedule` `cancel` `delete` |
288
+ | `crm.campaigns` | [campaigns](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/campaign/campaigns.md) | `list` `get` `create` `createBulk` `createGroup` `uploadReceivers` `update` **`trigger`** `schedule` `cancel` `delete` — creating does not send; `trigger` is what does |
282
289
  | `crm.campaignMessages` | [campaign reporting](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/campaign/campaign-messages.md) | `stats` `list` `iterate` `replies` — `/v1/campaigns/:id/report`, counted off the per-recipient rows |
283
290
 
284
291
  The audience helpers are exported too, so a UI can show "12,480 recipients, 3
@@ -2,7 +2,7 @@ import type { HttpClient } from '../http';
2
2
  import { InboxMedia } from './audience/inbox-media';
3
3
  import { type CampaignReceiver, type ReceiverEncoding } from '../internal/receiver-file';
4
4
  import { type BulkAudienceInput, type ResolvedAudience } from './campaign-audience';
5
- import type { Campaign, CampaignNumbersFilter, CreateCampaignInput, ListCampaignsQuery, UpdateCampaignInput } from '../types/campaign';
5
+ import type { Campaign, CampaignNumbersFilter, CreateCampaignInput, ListCampaignsQuery, TriggerCampaignInput, UpdateCampaignInput } from '../types/campaign';
6
6
  import type { Page } from '../types/common';
7
7
  /** Everything `createBulk` needs: a normal campaign, with an audience instead of `filters`. */
8
8
  export interface CreateBulkCampaignInput extends Omit<CreateCampaignInput, 'filters' | 'type'> {
@@ -102,6 +102,17 @@ export declare class Campaigns {
102
102
  /**
103
103
  * Creates a campaign from a payload you have already assembled.
104
104
  *
105
+ * **Creating does not send.** The campaign comes back `pending` with no
106
+ * `scheduledAt`: a draft you can keep editing, invisible to the sender until
107
+ * {@link trigger} arms it.
108
+ *
109
+ * ```ts
110
+ * const campaign = await crm.campaigns.create({ name: 'Diwali offer', messages });
111
+ * // ...show it for review...
112
+ * await crm.campaigns.trigger(campaign.id); // send now
113
+ * await crm.campaigns.trigger(campaign.id, { scheduledAt }); // or then
114
+ * ```
115
+ *
105
116
  * For a bulk recipient list, prefer {@link createBulk} -- it does the
106
117
  * encrypting and uploading that a file-backed audience requires, which is not
107
118
  * something a caller should have to reproduce.
@@ -129,9 +140,10 @@ export declare class Campaigns {
129
140
  * name: 'Renewal reminder',
130
141
  * audience: { csv: await file.text(), recipientColumn: 'phone' },
131
142
  * messages: [{ channel: { id: channelId }, message: template }],
132
- * trigger: 'schedule',
133
- * scheduledAt: '2026-11-01T04:30:00.000Z',
134
143
  * });
144
+ *
145
+ * // Neither of the above has been sent yet. Arm it when you are ready:
146
+ * await crm.campaigns.trigger(campaign.id, { scheduledAt: '2026-11-01T04:30:00.000Z' });
135
147
  * ```
136
148
  *
137
149
  * ## What it does, in order
@@ -233,6 +245,24 @@ export declare class Campaigns {
233
245
  */
234
246
  encoding?: ReceiverEncoding): Promise<CampaignNumbersFilter>;
235
247
  update(id: string, input: UpdateCampaignInput): Promise<Campaign>;
248
+ /**
249
+ * Arms a campaign -- the only call that makes one actually go out.
250
+ *
251
+ * ```ts
252
+ * await crm.campaigns.trigger(id); // now
253
+ * await crm.campaigns.trigger(id, { scheduledAt: isoInstant }); // then
254
+ * ```
255
+ *
256
+ * "Now" means a minute from now by default, which is the window `cancel()`
257
+ * works in. The server stamps `triggerInfo` -- the caller, their address, the
258
+ * moment -- as part of this call, so every send has one authorising act behind
259
+ * it; nothing you pass can set it.
260
+ *
261
+ * Only a `pending` campaign can be armed; anything else answers `409`.
262
+ * Triggering one that is armed but not yet picked up simply re-arms it.
263
+ */
264
+ trigger(id: string, input?: TriggerCampaignInput): Promise<Campaign>;
265
+ /** {@link trigger} with a required time, under the name the panel uses. */
236
266
  schedule(id: string, scheduledAt: string): Promise<Campaign>;
237
267
  cancel(id: string): Promise<Campaign>;
238
268
  delete(id: string): Promise<void>;
@@ -35,6 +35,17 @@ class Campaigns {
35
35
  /**
36
36
  * Creates a campaign from a payload you have already assembled.
37
37
  *
38
+ * **Creating does not send.** The campaign comes back `pending` with no
39
+ * `scheduledAt`: a draft you can keep editing, invisible to the sender until
40
+ * {@link trigger} arms it.
41
+ *
42
+ * ```ts
43
+ * const campaign = await crm.campaigns.create({ name: 'Diwali offer', messages });
44
+ * // ...show it for review...
45
+ * await crm.campaigns.trigger(campaign.id); // send now
46
+ * await crm.campaigns.trigger(campaign.id, { scheduledAt }); // or then
47
+ * ```
48
+ *
38
49
  * For a bulk recipient list, prefer {@link createBulk} -- it does the
39
50
  * encrypting and uploading that a file-backed audience requires, which is not
40
51
  * something a caller should have to reproduce.
@@ -64,9 +75,10 @@ class Campaigns {
64
75
  * name: 'Renewal reminder',
65
76
  * audience: { csv: await file.text(), recipientColumn: 'phone' },
66
77
  * messages: [{ channel: { id: channelId }, message: template }],
67
- * trigger: 'schedule',
68
- * scheduledAt: '2026-11-01T04:30:00.000Z',
69
78
  * });
79
+ *
80
+ * // Neither of the above has been sent yet. Arm it when you are ready:
81
+ * await crm.campaigns.trigger(campaign.id, { scheduledAt: '2026-11-01T04:30:00.000Z' });
70
82
  * ```
71
83
  *
72
84
  * ## What it does, in order
@@ -260,6 +272,28 @@ class Campaigns {
260
272
  update(id, input) {
261
273
  return this.http.request('PATCH', `/v1/campaigns/${encodeURIComponent(id)}`, { body: input });
262
274
  }
275
+ /**
276
+ * Arms a campaign -- the only call that makes one actually go out.
277
+ *
278
+ * ```ts
279
+ * await crm.campaigns.trigger(id); // now
280
+ * await crm.campaigns.trigger(id, { scheduledAt: isoInstant }); // then
281
+ * ```
282
+ *
283
+ * "Now" means a minute from now by default, which is the window `cancel()`
284
+ * works in. The server stamps `triggerInfo` -- the caller, their address, the
285
+ * moment -- as part of this call, so every send has one authorising act behind
286
+ * it; nothing you pass can set it.
287
+ *
288
+ * Only a `pending` campaign can be armed; anything else answers `409`.
289
+ * Triggering one that is armed but not yet picked up simply re-arms it.
290
+ */
291
+ trigger(id, input = {}) {
292
+ return this.http.request('POST', `/v1/campaigns/${encodeURIComponent(id)}/trigger`, {
293
+ body: input,
294
+ });
295
+ }
296
+ /** {@link trigger} with a required time, under the name the panel uses. */
263
297
  schedule(id, scheduledAt) {
264
298
  return this.http.request('POST', `/v1/campaigns/${encodeURIComponent(id)}/schedule`, {
265
299
  body: { scheduledAt },
@@ -1 +1 @@
1
- {"version":3,"file":"campaigns.js","sourceRoot":"","sources":["../../src/resources/campaigns.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"campaigns.js","sourceRoot":"","sources":["../../src/resources/campaigns.ts"],"names":[],"mappings":"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"}
@@ -130,6 +130,13 @@ export interface Campaign {
130
130
  parentBroadcastId?: string;
131
131
  scheduleId?: string;
132
132
  hidden: boolean;
133
+ /**
134
+ * When the campaign is armed to go out -- and whether it is armed at all.
135
+ *
136
+ * Absent on a campaign nobody has triggered yet, which is the state every
137
+ * campaign starts in. Set by `trigger()` or `schedule()`, never by `create()`
138
+ * or `update()`.
139
+ */
133
140
  scheduledAt?: IsoDate;
134
141
  processedAt?: IsoDate;
135
142
  completedAt?: IsoDate;
@@ -147,11 +154,22 @@ export interface Campaign {
147
154
  createdAt: IsoDate;
148
155
  updatedAt: IsoDate;
149
156
  }
157
+ /**
158
+ * A campaign to write. Writing one does not send it: see `campaigns.trigger()`.
159
+ *
160
+ * There is no `scheduledAt` here on purpose, and the API rejects one rather than
161
+ * ignoring it -- a send time belongs to the act of arming the campaign, which is
162
+ * also what records who armed it.
163
+ */
150
164
  export interface CreateCampaignInput {
151
165
  name: string;
152
166
  /** Defaults to `general`. `createBulk()` sets `bulk_campaign` itself. */
153
167
  type?: string;
154
168
  campaignType?: CampaignType;
169
+ /**
170
+ * How you expect it to go out. Advisory: `trigger()` rewrites it from what it
171
+ * is actually given, so this only matters to your own reading of the record.
172
+ */
155
173
  trigger?: CampaignTrigger;
156
174
  channelType?: string;
157
175
  messagingPattern?: string;
@@ -163,10 +181,18 @@ export interface CreateCampaignInput {
163
181
  repeat?: CampaignRepeat;
164
182
  parentBroadcastId?: string;
165
183
  hidden?: boolean;
166
- /** Required when `trigger` is `schedule`. */
167
- scheduledAt?: IsoDate;
168
184
  }
169
185
  export type UpdateCampaignInput = Partial<CreateCampaignInput>;
186
+ /**
187
+ * When to send. An empty object -- or no argument at all -- means now.
188
+ *
189
+ * "Now" is not instant: the campaign is armed a short way out (a minute by
190
+ * default) so there is a window in which `cancel()` still stops it.
191
+ */
192
+ export interface TriggerCampaignInput {
193
+ /** ISO-8601 with an offset, e.g. `2026-11-01T04:30:00.000Z`. */
194
+ scheduledAt?: IsoDate;
195
+ }
170
196
  /**
171
197
  * Declared as a type alias rather than an interface, and that is load-bearing:
172
198
  * only an alias of an object literal gets TypeScript's implicit index signature,
@@ -331,6 +331,17 @@ There is no presence check: an agent with the conversation open on screen still
331
331
  gets the push on their phone. That is the behaviour the panel has always had, and
332
332
  changing it is a product decision rather than a detail of delivery.
333
333
 
334
+ A person is matched under **either of their two ids**. A request carries the
335
+ ecosystem's `psuid`, which is what `subscribe()` files a device under; the inbox
336
+ trigger reads its recipient off a chat row the legacy stack wrote, so the id it
337
+ holds is a legacy one. Registration records both, and the lookup matches the one
338
+ id it is given against either field — so it resolves one person under two
339
+ names, and never returns a colleague's devices.
340
+
341
+ The practical consequence: a device registered before this existed is still
342
+ reachable by `psuid` exactly as before, and the next `subscribe()` call — which
343
+ the frontend makes on every app load — fills in the second id.
344
+
334
345
  ### Rendering it in a browser
335
346
 
336
347
  If the payload carries a `notification` block, FCM displays it and opens
@@ -13,12 +13,50 @@ One message sent to many people.
13
13
  | [`createGroup`](#creategroup) | uploads + `POST /v1/campaigns` | **one bucket per channel or template** |
14
14
  | [`uploadReceivers`](#uploadreceivers) | upload only | the file half of `createBulk` |
15
15
  | [`update`](#update) | `PATCH /v1/campaigns/:id` | before it runs |
16
- | [`schedule`](#schedule) | `POST /v1/campaigns/:id/schedule` | |
16
+ | [`trigger`](#trigger) | `POST /v1/campaigns/:id/trigger` | **the only call that sends** |
17
+ | [`schedule`](#schedule) | `POST /v1/campaigns/:id/schedule` | `trigger` with a required time |
17
18
  | [`cancel`](#cancel) | `POST /v1/campaigns/:id/cancel` | |
18
19
  | [`delete`](#delete) | `DELETE /v1/campaigns/:id` | |
19
20
 
20
21
  ---
21
22
 
23
+ ## Creating a campaign does not send it
24
+
25
+ Two steps, always:
26
+
27
+ ```ts
28
+ const campaign = await crm.campaigns.create({ name: 'Diwali offer', messages });
29
+ // nothing has gone out; `campaign.scheduledAt` is undefined
30
+
31
+ await crm.campaigns.trigger(campaign.id); // send now
32
+ await crm.campaigns.trigger(campaign.id, { scheduledAt: isoInstant }); // or at a time
33
+ ```
34
+
35
+ A created campaign is a **draft**: `pending`, with no `scheduledAt` and an empty
36
+ `triggerInfo`. You can `update` it, show it for review, and nothing will send it.
37
+ The sender cannot even see it — a campaign with no `scheduledAt` is not a
38
+ campaign with a moment.
39
+
40
+ `trigger` is what arms it, and is also what records *who* armed it: the server
41
+ stamps `triggerInfo` (the caller, their ip, the moment) as part of that call.
42
+ That is why a send time is **rejected** on `create` and `update` rather than
43
+ accepted — a campaign that could be armed by a write could go out with nobody
44
+ named against it.
45
+
46
+ | | `create` / `update` | `trigger` / `schedule` |
47
+ | --- | --- | --- |
48
+ | `scheduledAt` in the body | **`422`** | that is the point |
49
+ | writes `triggerInfo` | no | yes |
50
+ | campaign becomes sendable | no | yes |
51
+
52
+ **"Now" is not instant.** `trigger` with no time arms the campaign about a minute
53
+ out (`CAMPAIGN_IMMEDIATE_DELAY_SECONDS`), and [`cancel`](#cancel) still stops it
54
+ in that window. A second `trigger` on a campaign that is armed but not yet picked
55
+ up just re-arms it, so a double-click costs a fresh window rather than a second
56
+ send.
57
+
58
+ ---
59
+
22
60
  ## The audience is one field with five modes
23
61
 
24
62
  A campaign's recipients live in `filters`, and **the mode is decided by the shape
@@ -182,16 +220,18 @@ calls, so what you preview is what gets sent.
182
220
  ### Scheduling one
183
221
 
184
222
  ```ts
185
- await crm.campaigns.createBulk({
223
+ const { campaign } = await crm.campaigns.createBulk({
186
224
  name: 'Renewal reminder',
187
225
  audience: { csv, recipientColumn: 'phone' },
188
226
  messages: [{ channel: { id: channelId }, message: template }],
189
- trigger: 'schedule',
190
- scheduledAt: '2026-11-01T04:30:00.000Z',
191
227
  });
228
+
229
+ await crm.campaigns.trigger(campaign.id, { scheduledAt: '2026-11-01T04:30:00.000Z' });
192
230
  ```
193
231
 
194
- The file is uploaded immediately; only the send waits.
232
+ The file is uploaded during `createBulk`; the campaign itself does not go out
233
+ until you trigger it. Passing `scheduledAt` to `createBulk` is refused — see
234
+ [creating does not send](#creating-a-campaign-does-not-send-it).
195
235
 
196
236
  ### Two things to be careful about
197
237
 
@@ -390,14 +430,16 @@ const campaign = await crm.campaigns.get(id);
390
430
  For an audience you have already expressed as `filters` — every contact,
391
431
  a segment, or a re-target. For a bulk list use [`createBulk`](#createbulk).
392
432
 
433
+ Returns a draft. Call [`trigger`](#trigger) to send it.
434
+
393
435
  | Field | Type | Default |
394
436
  | --- | --- | --- |
395
437
  | `name` | `string` | required, 1–160 |
396
438
  | `messages` | `CampaignMessage[]` | required, at least one |
397
- | `type` | `string` | `general` |
439
+ | `type` | `string` | `general` — also decides the dispatch pacing, see [below](#trigger) |
398
440
  | `campaignType` | `general` `payment` | `general` |
399
- | `trigger` | `immediate` `schedule` | `immediate` |
400
- | `scheduledAt` | ISO datetime | required when `trigger` is `schedule` |
441
+ | `trigger` | `immediate` `schedule` | `immediate` — advisory; `trigger()` rewrites it |
442
+ | `scheduledAt` | — | **not accepted**; `422`. Pass it to [`trigger`](#trigger) |
401
443
  | `channelType` | `string` | `whatsapp` |
402
444
  | `messagingPattern` | `string` | `A2O` |
403
445
  | `filters` | `CampaignFilter[]` | `[]` — every contact |
@@ -412,7 +454,49 @@ a segment, or a re-target. For a bulk list use [`createBulk`](#createbulk).
412
454
  await crm.campaigns.update(id, { name: 'Diwali offer (revised)' });
413
455
  ```
414
456
 
415
- Every `create` field, all optional. Only meaningful before the campaign runs.
457
+ Every `create` field, all optional — `scheduledAt` included, which means it is
458
+ refused here too. Only meaningful while the campaign is `pending`.
459
+
460
+ ## `trigger`
461
+
462
+ ```ts
463
+ await crm.campaigns.trigger(id); // now
464
+ await crm.campaigns.trigger(id, { scheduledAt: '2026-11-01T04:30:00.000Z' });
465
+ ```
466
+
467
+ | | |
468
+ | --- | --- |
469
+ | Route | `POST /v1/campaigns/:id/trigger` |
470
+ | Body | `{}` or `{ scheduledAt }` — ISO-8601 with an offset |
471
+ | Scope | `campaigns:write` |
472
+ | `404` | no such campaign, or not this account's |
473
+ | `409` | the campaign is not `pending` — already sending, sent, failed or cancelled |
474
+ | `409` | `channel_webhook_unset`: a channel cannot report delivery back (see below) |
475
+
476
+ Writes `trigger`, `scheduledAt` and `triggerInfo`, and nothing else. A past
477
+ `scheduledAt` is accepted and means "now".
478
+
479
+ **The webhook preflight runs here.** Before the campaign is armed, each v2
480
+ WhatsApp channel's Meta subscription is checked and repaired if it has drifted. A
481
+ channel that still cannot deliver webhooks fails the call with `409
482
+ channel_webhook_unset` and the campaign stays a draft — because that failure is
483
+ otherwise invisible: an unsubscribed channel *sends perfectly* and reports
484
+ nothing, leaving every recipient stuck at `sent` forever.
485
+
486
+ ### How the campaign reaches the sender
487
+
488
+ Once armed, a poller picks the campaign up when its moment arrives and hands it
489
+ to the sending service. Which route it takes depends on `type`:
490
+
491
+ | `type` | route |
492
+ | --- | --- |
493
+ | `general` | straight to the sender |
494
+ | anything else (`bulk_campaign`, `group_campaign`, `retarget`, …) | through the account's pacing lane — `<user_id>_user_campaign`, shared with the legacy middleware — which releases one campaign per minute per account |
495
+
496
+ So an account that arms five bulk campaigns at once has them delivered to the
497
+ sender a minute apart, in the order they were armed, while a general campaign
498
+ goes immediately. This is invisible in the API — the only trace is when the
499
+ report starts filling.
416
500
 
417
501
  ## `schedule`
418
502
 
@@ -420,6 +504,9 @@ Every `create` field, all optional. Only meaningful before the campaign runs.
420
504
  await crm.campaigns.schedule(id, '2026-11-01T04:30:00.000Z');
421
505
  ```
422
506
 
507
+ The same call as `trigger` with a required time. Kept because it is the name the
508
+ panel uses; new code can use either.
509
+
423
510
  ## `cancel`
424
511
 
425
512
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ab-ecosystem-cpass",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "JavaScript/TypeScript SDK for the CPaaS CRM gateway: audience and channels",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",