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 +8 -1
- package/dist/resources/campaigns.d.ts +33 -3
- package/dist/resources/campaigns.js +36 -2
- package/dist/resources/campaigns.js.map +1 -1
- package/dist/types/campaign.d.ts +28 -2
- package/docs/audience/notifications.md +11 -0
- package/docs/campaign/campaigns.md +96 -9
- package/package.json +1 -1
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":";;;AACA,sCAAwC;AACxC,wDAA4E;AAC5E,6DAA4G;AAC5G,2DAK6B;
|
|
1
|
+
{"version":3,"file":"campaigns.js","sourceRoot":"","sources":["../../src/resources/campaigns.ts"],"names":[],"mappings":"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"}
|
package/dist/types/campaign.d.ts
CHANGED
|
@@ -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
|
-
| [`
|
|
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
|
|
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` |
|
|
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
|
|
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
|