ab-ecosystem-cpass 1.0.0 → 1.0.6

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.
Files changed (86) hide show
  1. package/README.md +121 -21
  2. package/dist/client.d.ts +38 -0
  3. package/dist/client.js +49 -5
  4. package/dist/client.js.map +1 -1
  5. package/dist/http.d.ts +15 -0
  6. package/dist/http.js +21 -1
  7. package/dist/http.js.map +1 -1
  8. package/dist/index.d.ts +11 -0
  9. package/dist/index.js +24 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/internal/bytes.d.ts +38 -0
  12. package/dist/internal/bytes.js +84 -0
  13. package/dist/internal/bytes.js.map +1 -0
  14. package/dist/internal/device.d.ts +24 -0
  15. package/dist/internal/device.js +91 -0
  16. package/dist/internal/device.js.map +1 -0
  17. package/dist/internal/md5.d.ts +4 -0
  18. package/dist/internal/md5.js +109 -0
  19. package/dist/internal/md5.js.map +1 -0
  20. package/dist/internal/receiver-file.d.ts +106 -0
  21. package/dist/internal/receiver-file.js +128 -0
  22. package/dist/internal/receiver-file.js.map +1 -0
  23. package/dist/realtime/client.d.ts +85 -0
  24. package/dist/realtime/client.js +126 -0
  25. package/dist/realtime/client.js.map +1 -0
  26. package/dist/realtime/events.d.ts +147 -0
  27. package/dist/realtime/events.js +3 -0
  28. package/dist/realtime/events.js.map +1 -0
  29. package/dist/realtime/inbox.d.ts +173 -0
  30. package/dist/realtime/inbox.js +263 -0
  31. package/dist/realtime/inbox.js.map +1 -0
  32. package/dist/realtime/index.d.ts +16 -0
  33. package/dist/realtime/index.js +37 -0
  34. package/dist/realtime/index.js.map +1 -0
  35. package/dist/resources/audience/contacts.d.ts +19 -3
  36. package/dist/resources/audience/contacts.js +19 -3
  37. package/dist/resources/audience/contacts.js.map +1 -1
  38. package/dist/resources/audience/inbox-media.d.ts +92 -0
  39. package/dist/resources/audience/inbox-media.js +127 -0
  40. package/dist/resources/audience/inbox-media.js.map +1 -0
  41. package/dist/resources/audience/notifications.d.ts +171 -0
  42. package/dist/resources/audience/notifications.js +215 -0
  43. package/dist/resources/audience/notifications.js.map +1 -0
  44. package/dist/resources/audience/scheduled-messages.d.ts +234 -0
  45. package/dist/resources/audience/scheduled-messages.js +259 -0
  46. package/dist/resources/audience/scheduled-messages.js.map +1 -0
  47. package/dist/resources/campaign-audience.d.ts +132 -0
  48. package/dist/resources/campaign-audience.js +224 -0
  49. package/dist/resources/campaign-audience.js.map +1 -0
  50. package/dist/resources/campaign-report-exports.d.ts +14 -0
  51. package/dist/resources/campaign-report-exports.js +28 -0
  52. package/dist/resources/campaign-report-exports.js.map +1 -0
  53. package/dist/resources/campaign-reports.d.ts +15 -0
  54. package/dist/resources/campaign-reports.js +25 -0
  55. package/dist/resources/campaign-reports.js.map +1 -0
  56. package/dist/resources/campaigns.d.ts +239 -0
  57. package/dist/resources/campaigns.js +300 -0
  58. package/dist/resources/campaigns.js.map +1 -0
  59. package/dist/types/audience.d.ts +93 -1
  60. package/dist/types/campaign.d.ts +229 -0
  61. package/dist/types/campaign.js +3 -0
  62. package/dist/types/campaign.js.map +1 -0
  63. package/dist/types/contact-filter.d.ts +3 -2
  64. package/dist/types/contact-filter.js +3 -2
  65. package/dist/types/contact-filter.js.map +1 -1
  66. package/dist/types/inbox.d.ts +391 -0
  67. package/dist/types/inbox.js +16 -0
  68. package/dist/types/inbox.js.map +1 -0
  69. package/dist/types/index.d.ts +3 -0
  70. package/dist/types/index.js +5 -0
  71. package/dist/types/index.js.map +1 -1
  72. package/dist/types/scheduled-message.d.ts +221 -0
  73. package/dist/types/scheduled-message.js +16 -0
  74. package/dist/types/scheduled-message.js.map +1 -0
  75. package/docs/audience/contacts.md +34 -4
  76. package/docs/audience/notifications.md +263 -0
  77. package/docs/audience/scheduled-messages.md +557 -0
  78. package/docs/campaign/campaigns.md +489 -0
  79. package/docs/contact-filter.md +2 -1
  80. package/docs/inbox/chats.md +356 -0
  81. package/docs/inbox/connection.md +171 -0
  82. package/docs/inbox/media.md +202 -0
  83. package/docs/inbox/messages.md +338 -0
  84. package/docs/inbox/referrals.md +72 -0
  85. package/docs/inbox/sessions.md +74 -0
  86. package/package.json +28 -2
@@ -0,0 +1,171 @@
1
+ import type { Device, ListDevicesQuery, Paginated, SubscribeNotificationsInput } from '../../types';
2
+ import type { HttpClient } from '../../http';
3
+ /**
4
+ * Push notification subscriptions: which browsers and apps a user wants
5
+ * notifications delivered to.
6
+ *
7
+ * ```ts
8
+ * // after the browser has granted permission and Firebase has a token
9
+ * await crm.notifications.subscribe({ token });
10
+ *
11
+ * // logout, or the user turning notifications off
12
+ * await crm.notifications.unsubscribe(token);
13
+ *
14
+ * // what the user sees in "notification settings"
15
+ * const { data } = await crm.notifications.devices();
16
+ * ```
17
+ *
18
+ * ## What this does and does not cover
19
+ *
20
+ * A push subscription has two halves and only one of them is an API call:
21
+ *
22
+ * 1. **In the browser** -- ask for permission (`Notification.requestPermission()`),
23
+ * then get a registration token for this instance
24
+ * (`getToken(messaging, { vapidKey })` from `firebase/messaging`). This SDK
25
+ * deliberately does none of that: it would mean a hard dependency on
26
+ * `firebase/*`, in a package that also runs on Node, to wrap two calls whose
27
+ * arguments come from your own Firebase project.
28
+ * 2. **Here** -- hand that token to the platform so the send path can reach it,
29
+ * which is what {@link subscribe} does, and withdraw it on logout, which is
30
+ * {@link unsubscribe}.
31
+ *
32
+ * ## Where a subscription lives
33
+ *
34
+ * On the platform, against the acting user, in this product -- not in the core
35
+ * platform. Core owns identity and the agency's Firebase credentials; messages
36
+ * arrive here, so the tokens and the sending live here too, and core is not on
37
+ * the delivery path. Nothing in the subscription call names the Firebase project:
38
+ * the platform resolves it from the client the call arrives on and stamps it onto
39
+ * the row, because a token is only sendable through the project that minted it
40
+ * and nothing in the token says which.
41
+ */
42
+ export declare class Notifications {
43
+ private readonly http;
44
+ constructor(http: HttpClient);
45
+ /**
46
+ * Subscribes this browser or app instance to notifications.
47
+ *
48
+ * ```http
49
+ * POST /v1/audience/devices
50
+ * authorization: Bearer <ecosystem token>
51
+ * client-id: tpc_8Kd3mXp7qR2wL9zB
52
+ * content-type: application/json
53
+ *
54
+ * {
55
+ * "token": "fNx8...:APA91bH...",
56
+ * "platform": "web",
57
+ * "device_label": "Chrome on Windows",
58
+ * "tpc_id": "tpc_8Kd3mXp7qR2wL9zB"
59
+ * }
60
+ * ```
61
+ *
62
+ * Answers `201` with the stored {@link Device}, token omitted. Of those four
63
+ * fields the SDK fills in two for you: `platform` defaults to `'web'` when it
64
+ * can see a DOM, and `device_label` to something like `Chrome on Windows` from
65
+ * the user agent.
66
+ *
67
+ * ## Call it on every app load, not once
68
+ *
69
+ * It is an idempotent upsert keyed on the token, so re-sending one the platform
70
+ * already holds costs a single indexed update. That is the intended usage,
71
+ * because two things change underneath you:
72
+ *
73
+ * - FCM rotates registration tokens on its own schedule, and the old one stops
74
+ * being delivered to without anyone being told.
75
+ * - A device unseen for 60 days is pruned, and "seen" means a successful send
76
+ * or a call to this method. A user who receives no notifications for two
77
+ * months would otherwise fall off the list.
78
+ *
79
+ * Re-registering a token that is already on file against **another** user
80
+ * moves it rather than duplicating it: one browser has one person signed in,
81
+ * and the previous owner's notifications must stop reaching it the moment
82
+ * someone else logs in there.
83
+ *
84
+ * ## Identity
85
+ *
86
+ * The user comes from the verified token this client was built with, and there
87
+ * is no field that could say otherwise. `tpc_id` is the one tenant field
88
+ * accepted, because it is not an identity claim -- it says which reseller
89
+ * frontend is registering, which decides whose Firebase project and which deep
90
+ * link host apply. It is cross-checked against the verified `client-id`, so it
91
+ * can confirm the surface but never move the device to another tenant: a value
92
+ * that contradicts the token is refused with `403` rather than ignored.
93
+ *
94
+ * ## Failures worth handling
95
+ *
96
+ * - `400 validation_failed` -- the token is missing or implausible, or
97
+ * `platform` could not be defaulted (see {@link SubscribeNotificationsInput}).
98
+ * - `403 forbidden` -- `tpc_id` names a different client than the token was
99
+ * issued for.
100
+ * - `503 push_not_configured` -- the agency has no Firebase notification
101
+ * resource set up. Nothing the caller can fix; notifications are simply off
102
+ * for this account.
103
+ * - `503 push_unavailable` -- the platform could not reach core to read the
104
+ * configuration. Transient.
105
+ *
106
+ * Both `503`s deserve the same handling as a successful call: try again on the
107
+ * next app load, which the advice above means you are doing anyway.
108
+ *
109
+ * Scope: `devices:write`.
110
+ */
111
+ subscribe(input: SubscribeNotificationsInput): Promise<Device>;
112
+ /**
113
+ * Stops notifications reaching one device. What logout calls.
114
+ *
115
+ * ```http
116
+ * POST /v1/audience/devices/unregister
117
+ * { "token": "fNx8...:APA91bH..." }
118
+ * ```
119
+ *
120
+ * `204`, no body, whether or not a row went -- the caller is asking for a
121
+ * guarantee about the future, and a token that is already gone provides it. So
122
+ * this is safe to call unconditionally on logout without first checking whether
123
+ * the device was ever subscribed.
124
+ *
125
+ * Scoped to the caller's own devices. A token identifies a browser rather than
126
+ * a person, so by the time one user logs out the row may already belong to
127
+ * whoever logged in after them; an unscoped delete would cut off the person now
128
+ * holding it.
129
+ *
130
+ * A `POST` with the token in the body, not `DELETE /devices/:token`: a
131
+ * registration token is hundreds of characters and may contain `:` and `/`, and
132
+ * putting one in a path would invite proxy length limits and write a live send
133
+ * credential into every access log on the way.
134
+ *
135
+ * ## Also delete it on the device
136
+ *
137
+ * This revokes the platform's permission to send to the token. If the user is
138
+ * turning notifications *off* -- rather than logging out -- also call
139
+ * `deleteToken(messaging)` in the browser, otherwise the next `getToken()`
140
+ * hands back the same token and the subscription can come back by accident.
141
+ *
142
+ * Scope: `devices:write`.
143
+ */
144
+ unsubscribe(token: string): Promise<void>;
145
+ /**
146
+ * The caller's own subscribed devices, newest contact first.
147
+ *
148
+ * ```http
149
+ * GET /v1/audience/devices?page=1&rows=25
150
+ * ```
151
+ *
152
+ * Offset paginated, `rows` 1..100 and default 25, sorted by `lastSeenAt`
153
+ * descending. This is what a "notification settings" screen lists, and the 20
154
+ * most recently seen devices are also the only ones a notification is sent to,
155
+ * so a longer list is itself the answer to "why did my old laptop stop
156
+ * buzzing".
157
+ *
158
+ * Only ever the acting user's own devices -- there is no account-wide view and
159
+ * no device id in the path. A device is one person's browser, and the only
160
+ * party with a reason to see or revoke it is that person.
161
+ *
162
+ * Tokens are not included, so this cannot tell you whether *this* browser is
163
+ * subscribed. Decide that locally: the browser knows its own
164
+ * `Notification.permission` and its own token, and {@link subscribe} being
165
+ * idempotent means the cheap answer to "am I still subscribed" is to subscribe
166
+ * again.
167
+ *
168
+ * Scope: `devices:read`.
169
+ */
170
+ devices(query?: Partial<ListDevicesQuery>): Promise<Paginated<Device>>;
171
+ }
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Notifications = void 0;
4
+ const errors_1 = require("../../errors");
5
+ const device_1 = require("../../internal/device");
6
+ const paths_1 = require("../paths");
7
+ const BASE = `${paths_1.AUDIENCE}/devices`;
8
+ /** The bounds the server applies, checked here so a bad token fails without a round trip. */
9
+ const TOKEN_LENGTH = { min: 20, max: 4096 };
10
+ /** What the server accepts as a label, so a long one is trimmed rather than refused. */
11
+ const MAX_LABEL = 200;
12
+ /**
13
+ * Push notification subscriptions: which browsers and apps a user wants
14
+ * notifications delivered to.
15
+ *
16
+ * ```ts
17
+ * // after the browser has granted permission and Firebase has a token
18
+ * await crm.notifications.subscribe({ token });
19
+ *
20
+ * // logout, or the user turning notifications off
21
+ * await crm.notifications.unsubscribe(token);
22
+ *
23
+ * // what the user sees in "notification settings"
24
+ * const { data } = await crm.notifications.devices();
25
+ * ```
26
+ *
27
+ * ## What this does and does not cover
28
+ *
29
+ * A push subscription has two halves and only one of them is an API call:
30
+ *
31
+ * 1. **In the browser** -- ask for permission (`Notification.requestPermission()`),
32
+ * then get a registration token for this instance
33
+ * (`getToken(messaging, { vapidKey })` from `firebase/messaging`). This SDK
34
+ * deliberately does none of that: it would mean a hard dependency on
35
+ * `firebase/*`, in a package that also runs on Node, to wrap two calls whose
36
+ * arguments come from your own Firebase project.
37
+ * 2. **Here** -- hand that token to the platform so the send path can reach it,
38
+ * which is what {@link subscribe} does, and withdraw it on logout, which is
39
+ * {@link unsubscribe}.
40
+ *
41
+ * ## Where a subscription lives
42
+ *
43
+ * On the platform, against the acting user, in this product -- not in the core
44
+ * platform. Core owns identity and the agency's Firebase credentials; messages
45
+ * arrive here, so the tokens and the sending live here too, and core is not on
46
+ * the delivery path. Nothing in the subscription call names the Firebase project:
47
+ * the platform resolves it from the client the call arrives on and stamps it onto
48
+ * the row, because a token is only sendable through the project that minted it
49
+ * and nothing in the token says which.
50
+ */
51
+ class Notifications {
52
+ http;
53
+ constructor(http) {
54
+ this.http = http;
55
+ }
56
+ /**
57
+ * Subscribes this browser or app instance to notifications.
58
+ *
59
+ * ```http
60
+ * POST /v1/audience/devices
61
+ * authorization: Bearer <ecosystem token>
62
+ * client-id: tpc_8Kd3mXp7qR2wL9zB
63
+ * content-type: application/json
64
+ *
65
+ * {
66
+ * "token": "fNx8...:APA91bH...",
67
+ * "platform": "web",
68
+ * "device_label": "Chrome on Windows",
69
+ * "tpc_id": "tpc_8Kd3mXp7qR2wL9zB"
70
+ * }
71
+ * ```
72
+ *
73
+ * Answers `201` with the stored {@link Device}, token omitted. Of those four
74
+ * fields the SDK fills in two for you: `platform` defaults to `'web'` when it
75
+ * can see a DOM, and `device_label` to something like `Chrome on Windows` from
76
+ * the user agent.
77
+ *
78
+ * ## Call it on every app load, not once
79
+ *
80
+ * It is an idempotent upsert keyed on the token, so re-sending one the platform
81
+ * already holds costs a single indexed update. That is the intended usage,
82
+ * because two things change underneath you:
83
+ *
84
+ * - FCM rotates registration tokens on its own schedule, and the old one stops
85
+ * being delivered to without anyone being told.
86
+ * - A device unseen for 60 days is pruned, and "seen" means a successful send
87
+ * or a call to this method. A user who receives no notifications for two
88
+ * months would otherwise fall off the list.
89
+ *
90
+ * Re-registering a token that is already on file against **another** user
91
+ * moves it rather than duplicating it: one browser has one person signed in,
92
+ * and the previous owner's notifications must stop reaching it the moment
93
+ * someone else logs in there.
94
+ *
95
+ * ## Identity
96
+ *
97
+ * The user comes from the verified token this client was built with, and there
98
+ * is no field that could say otherwise. `tpc_id` is the one tenant field
99
+ * accepted, because it is not an identity claim -- it says which reseller
100
+ * frontend is registering, which decides whose Firebase project and which deep
101
+ * link host apply. It is cross-checked against the verified `client-id`, so it
102
+ * can confirm the surface but never move the device to another tenant: a value
103
+ * that contradicts the token is refused with `403` rather than ignored.
104
+ *
105
+ * ## Failures worth handling
106
+ *
107
+ * - `400 validation_failed` -- the token is missing or implausible, or
108
+ * `platform` could not be defaulted (see {@link SubscribeNotificationsInput}).
109
+ * - `403 forbidden` -- `tpc_id` names a different client than the token was
110
+ * issued for.
111
+ * - `503 push_not_configured` -- the agency has no Firebase notification
112
+ * resource set up. Nothing the caller can fix; notifications are simply off
113
+ * for this account.
114
+ * - `503 push_unavailable` -- the platform could not reach core to read the
115
+ * configuration. Transient.
116
+ *
117
+ * Both `503`s deserve the same handling as a successful call: try again on the
118
+ * next app load, which the advice above means you are doing anyway.
119
+ *
120
+ * Scope: `devices:write`.
121
+ */
122
+ subscribe(input) {
123
+ const token = (input.token ?? '').trim();
124
+ if (token.length < TOKEN_LENGTH.min || token.length > TOKEN_LENGTH.max) {
125
+ throw new errors_1.CrmApiError(400, 'validation_failed', `token must be a Firebase registration token of ${TOKEN_LENGTH.min}..${TOKEN_LENGTH.max} characters`);
126
+ }
127
+ const platform = input.platform ?? (0, device_1.detectPlatform)();
128
+ if (!platform) {
129
+ // Not defaulted to 'web' outside a browser on purpose: a mobile runtime
130
+ // registered as web gets the webpush half of the payload, which FCM
131
+ // accepts and the device never displays.
132
+ throw new errors_1.CrmApiError(400, 'validation_failed', 'platform is required outside a browser: pass "android", "ios", or "web" explicitly');
133
+ }
134
+ const label = (input.device_label ?? (0, device_1.describeDevice)() ?? '').trim().slice(0, MAX_LABEL);
135
+ return this.http.request('POST', BASE, {
136
+ body: {
137
+ token,
138
+ platform,
139
+ // Both omitted when empty rather than sent as '': the server treats an
140
+ // absent optional and an empty one the same way, and a body carrying only
141
+ // what it means is easier to read in a log.
142
+ ...(label ? { device_label: label } : {}),
143
+ ...(input.tpc_id ? { tpc_id: input.tpc_id.trim() } : {}),
144
+ },
145
+ });
146
+ }
147
+ /**
148
+ * Stops notifications reaching one device. What logout calls.
149
+ *
150
+ * ```http
151
+ * POST /v1/audience/devices/unregister
152
+ * { "token": "fNx8...:APA91bH..." }
153
+ * ```
154
+ *
155
+ * `204`, no body, whether or not a row went -- the caller is asking for a
156
+ * guarantee about the future, and a token that is already gone provides it. So
157
+ * this is safe to call unconditionally on logout without first checking whether
158
+ * the device was ever subscribed.
159
+ *
160
+ * Scoped to the caller's own devices. A token identifies a browser rather than
161
+ * a person, so by the time one user logs out the row may already belong to
162
+ * whoever logged in after them; an unscoped delete would cut off the person now
163
+ * holding it.
164
+ *
165
+ * A `POST` with the token in the body, not `DELETE /devices/:token`: a
166
+ * registration token is hundreds of characters and may contain `:` and `/`, and
167
+ * putting one in a path would invite proxy length limits and write a live send
168
+ * credential into every access log on the way.
169
+ *
170
+ * ## Also delete it on the device
171
+ *
172
+ * This revokes the platform's permission to send to the token. If the user is
173
+ * turning notifications *off* -- rather than logging out -- also call
174
+ * `deleteToken(messaging)` in the browser, otherwise the next `getToken()`
175
+ * hands back the same token and the subscription can come back by accident.
176
+ *
177
+ * Scope: `devices:write`.
178
+ */
179
+ unsubscribe(token) {
180
+ const value = (token ?? '').trim();
181
+ if (!value)
182
+ throw new errors_1.CrmApiError(400, 'validation_failed', 'token is required');
183
+ return this.http.request('POST', `${BASE}/unregister`, { body: { token: value } });
184
+ }
185
+ /**
186
+ * The caller's own subscribed devices, newest contact first.
187
+ *
188
+ * ```http
189
+ * GET /v1/audience/devices?page=1&rows=25
190
+ * ```
191
+ *
192
+ * Offset paginated, `rows` 1..100 and default 25, sorted by `lastSeenAt`
193
+ * descending. This is what a "notification settings" screen lists, and the 20
194
+ * most recently seen devices are also the only ones a notification is sent to,
195
+ * so a longer list is itself the answer to "why did my old laptop stop
196
+ * buzzing".
197
+ *
198
+ * Only ever the acting user's own devices -- there is no account-wide view and
199
+ * no device id in the path. A device is one person's browser, and the only
200
+ * party with a reason to see or revoke it is that person.
201
+ *
202
+ * Tokens are not included, so this cannot tell you whether *this* browser is
203
+ * subscribed. Decide that locally: the browser knows its own
204
+ * `Notification.permission` and its own token, and {@link subscribe} being
205
+ * idempotent means the cheap answer to "am I still subscribed" is to subscribe
206
+ * again.
207
+ *
208
+ * Scope: `devices:read`.
209
+ */
210
+ devices(query = {}) {
211
+ return this.http.request('GET', BASE, { query });
212
+ }
213
+ }
214
+ exports.Notifications = Notifications;
215
+ //# sourceMappingURL=notifications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../src/resources/audience/notifications.ts"],"names":[],"mappings":";;;AAEA,yCAA2C;AAC3C,kDAAuE;AACvE,oCAAoC;AAEpC,MAAM,IAAI,GAAG,GAAG,gBAAQ,UAAU,CAAC;AAEnC,6FAA6F;AAC7F,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AAE5C,wFAAwF;AACxF,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAa,aAAa;IACK;IAA7B,YAA6B,IAAgB;QAAhB,SAAI,GAAJ,IAAI,CAAY;IAAG,CAAC;IAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiEG;IACH,SAAS,CAAC,KAAkC;QAC1C,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAEzC,IAAI,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC,GAAG,IAAI,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC,GAAG,EAAE,CAAC;YACvE,MAAM,IAAI,oBAAW,CACnB,GAAG,EACH,mBAAmB,EACnB,kDAAkD,YAAY,CAAC,GAAG,KAAK,YAAY,CAAC,GAAG,aAAa,CACrG,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,IAAA,uBAAc,GAAE,CAAC;QAEpD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,wEAAwE;YACxE,oEAAoE;YACpE,yCAAyC;YACzC,MAAM,IAAI,oBAAW,CACnB,GAAG,EACH,mBAAmB,EACnB,oFAAoF,CACrF,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,YAAY,IAAI,IAAA,uBAAc,GAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QAExF,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAS,MAAM,EAAE,IAAI,EAAE;YAC7C,IAAI,EAAE;gBACJ,KAAK;gBACL,QAAQ;gBACR,uEAAuE;gBACvE,0EAA0E;gBAC1E,4CAA4C;gBAC5C,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACzD;SACF,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,WAAW,CAAC,KAAa;QACvB,MAAM,KAAK,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAEnC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,oBAAW,CAAC,GAAG,EAAE,mBAAmB,EAAE,mBAAmB,CAAC,CAAC;QAEjF,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAO,MAAM,EAAE,GAAG,IAAI,aAAa,EAAE,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,OAAO,CAAC,QAAmC,EAAE;QAC3C,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAoB,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACtE,CAAC;CACF;AAhLD,sCAgLC"}
@@ -0,0 +1,234 @@
1
+ import type { CancelSeriesResult, CreateRepeatingScheduleInput, CreateScheduledMessageInput, CreateScheduledSeriesInput, ListScheduledMessagesQuery, Paginated, RescheduleMessageInput, ScheduledMessage, ScheduledMessageSeries } from '../../types';
2
+ import type { HttpClient } from '../../http';
3
+ /**
4
+ * Scheduled messages: a send with a time, and optionally a condition checked
5
+ * immediately before it goes out.
6
+ *
7
+ * ### What the condition is for
8
+ *
9
+ * A drip is easy to start and awkward to stop. Schedule five messages over two
10
+ * weeks, your lead converts after the third, and the last two still go out --
11
+ * now addressed to a customer, as a pitch. Cancelling them means somebody
12
+ * noticing in time.
13
+ *
14
+ * So each occurrence re-reads the contact a moment before sending and stands
15
+ * down if they no longer match. Keep a `stage` custom field on your contacts,
16
+ * condition the series on `stage equal to Lead`, and set `stage` to `Customer`
17
+ * when they convert: the rest of the sequence cancels itself. No cancellation
18
+ * call, no scheduled-message ids to track.
19
+ *
20
+ * ```ts
21
+ * const nurture = await crm.scheduledMessages.createSeries({
22
+ * title: 'Lead nurture',
23
+ * channel: channelId,
24
+ * contact: { id: contactId },
25
+ * timezone: 'Asia/Kolkata',
26
+ * starts_at: '2026-10-13T10:00:00',
27
+ * conditions: {
28
+ * conditions: [
29
+ * { field_id: 'stage', field_type: 'dropdown', condition: 'equal to', value: 'Lead' },
30
+ * ],
31
+ * },
32
+ * on_condition_fail: 'cancel_series',
33
+ * steps: [
34
+ * { after: { value: 0, unit: 'days' }, content: intro },
35
+ * { after: { value: 3, unit: 'days' }, content: caseStudy },
36
+ * { after: { value: 6, unit: 'days' }, content: pricing },
37
+ * ],
38
+ * });
39
+ *
40
+ * // Later, when the lead converts -- this is the whole of "stop the drip":
41
+ * await crm.contacts.update(contactId, { custom_field_hash: { stage: 'Customer' } });
42
+ * ```
43
+ *
44
+ * ### Two things that will catch you out
45
+ *
46
+ * **Templates, not free text.** WhatsApp only allows free-form messages within
47
+ * 24 hours of the contact's last inbound message, and a drip is by construction
48
+ * outside that window from the second message onward. Every step but the first
49
+ * has to be an approved template or the provider refuses it; `create` rejects
50
+ * free text past the window rather than letting you discover it on day four.
51
+ *
52
+ * **A series is expanded, not recurring.** `createSeries` writes one row per
53
+ * message straight away, so you can list them, move one, or cancel one without
54
+ * touching the others. There is no rule being evaluated later and nothing to
55
+ * recompute -- which is also why `until` has to be a real end rather than
56
+ * "forever".
57
+ *
58
+ * Scoping: scheduled messages belong to an account the same way contacts do --
59
+ * reads match the caller or the account they act for, writes land on the
60
+ * caller's own.
61
+ */
62
+ export declare class ScheduledMessages {
63
+ private readonly http;
64
+ constructor(http: HttpClient);
65
+ /**
66
+ * `GET /v1/audience/scheduled-messages` -- offset paginated, soonest first
67
+ * among `pending`, newest first among the rest. `search` matches the title.
68
+ *
69
+ * Narrow it with `contact` for one person's queue, `series` for one drip's
70
+ * occurrences, or `status` to answer "what is still waiting" (`pending`)
71
+ * versus "what stood down and why" (`skipped`).
72
+ *
73
+ * Scope: `messages:read`.
74
+ */
75
+ list(query?: Partial<ListScheduledMessagesQuery>): Promise<Paginated<ScheduledMessage>>;
76
+ /**
77
+ * `GET /v1/audience/scheduled-messages/:id` -- one occurrence, with its
78
+ * `result` if it has reached a terminal status.
79
+ *
80
+ * Scope: `messages:read`.
81
+ */
82
+ get(id: string): Promise<ScheduledMessage>;
83
+ /**
84
+ * `POST /v1/audience/scheduled-messages` -- 201 with the created occurrence.
85
+ *
86
+ * One message, one time. `series` on the response is `null` and `occurrence`
87
+ * is `1`.
88
+ *
89
+ * ```ts
90
+ * await crm.scheduledMessages.create({
91
+ * title: 'Pricing follow-up',
92
+ * channel: channelId,
93
+ * contact: { id: contactId },
94
+ * timezone: 'Asia/Kolkata',
95
+ * scheduled_at: '2026-10-15T10:00:00', // 10:00 IST
96
+ * content: { type: 'text', text: { body: 'Following up on the pricing.' } },
97
+ * conditions: {
98
+ * conditions: [
99
+ * { field_id: 'stage', field_type: 'dropdown', condition: 'equal to', value: 'Lead' },
100
+ * ],
101
+ * },
102
+ * });
103
+ * ```
104
+ *
105
+ * `on_condition_fail` defaults to `skip` here, which for a single message is
106
+ * the only reading that means anything -- there is no series to cancel.
107
+ *
108
+ * 422 if `scheduled_at` is in the past, if `timezone` is not an IANA zone, if
109
+ * the condition names a field type the comparison cannot apply to, or if the
110
+ * message is free text and the send falls outside the channel's reply window.
111
+ *
112
+ * Scope: `messages:write`.
113
+ */
114
+ create(input: CreateScheduledMessageInput): Promise<ScheduledMessage>;
115
+ /**
116
+ * `POST /v1/audience/scheduled-messages/series` -- 201 with the series and
117
+ * every occurrence it expanded into.
118
+ *
119
+ * For a sequence whose steps carry different copy. `after` is measured from
120
+ * `starts_at`, not from the previous step, so changing one delay leaves the
121
+ * others where they were.
122
+ *
123
+ * ```ts
124
+ * const series = await crm.scheduledMessages.createSeries({
125
+ * title: 'Lead nurture -- 2 weeks',
126
+ * channel: channelId,
127
+ * contact: { id: contactId },
128
+ * timezone: 'Asia/Kolkata',
129
+ * starts_at: '2026-10-13T10:00:00',
130
+ * conditions: { conditions: [stillALead] },
131
+ * steps: [
132
+ * { after: { value: 0, unit: 'days' }, content: intro },
133
+ * { after: { value: 3, unit: 'days' }, content: caseStudy },
134
+ * { after: { value: 6, unit: 'days' }, content: pricing },
135
+ * { after: { value: 9, unit: 'days' }, content: demo },
136
+ * { after: { value: 12, unit: 'days' }, content: lastCall },
137
+ * ],
138
+ * });
139
+ *
140
+ * series.occurrences.length; // 5
141
+ * ```
142
+ *
143
+ * A step may carry its own `conditions`, which are checked **in addition to**
144
+ * the series' -- both have to pass. Leave them out to inherit.
145
+ *
146
+ * `on_condition_fail` defaults to `cancel_series`.
147
+ *
148
+ * Scope: `messages:write`.
149
+ */
150
+ createSeries(input: CreateScheduledSeriesInput): Promise<ScheduledMessageSeries>;
151
+ /**
152
+ * `POST /v1/audience/scheduled-messages/series` -- the same route, for the
153
+ * case where every occurrence sends the same message.
154
+ *
155
+ * ```ts
156
+ * await crm.scheduledMessages.createRepeating({
157
+ * title: 'Weekly check-in',
158
+ * channel: channelId,
159
+ * contact: { id: contactId },
160
+ * timezone: 'Asia/Kolkata',
161
+ * starts_at: '2026-10-13T10:00:00',
162
+ * repeat: { every: 3, unit: 'days', until: '2026-10-27T10:00:00' },
163
+ * content: nudgeTemplate,
164
+ * conditions: { conditions: [stillALead] },
165
+ * });
166
+ * ```
167
+ *
168
+ * `until` is inclusive and has to be a real end: the expansion is written as
169
+ * rows, so an open-ended cadence has nothing to expand into. The result is the
170
+ * same `ScheduledMessageSeries` as `createSeries` -- once created, a repeating
171
+ * schedule and a stepped one are indistinguishable.
172
+ *
173
+ * 422 if the expansion would exceed 365 occurrences.
174
+ *
175
+ * Scope: `messages:write`.
176
+ */
177
+ createRepeating(input: CreateRepeatingScheduleInput): Promise<ScheduledMessageSeries>;
178
+ /**
179
+ * `PATCH /v1/audience/scheduled-messages/:id` -- moves one occurrence.
180
+ *
181
+ * Only a `pending` one. Rescheduling something already `sent`, `skipped` or
182
+ * `cancelled` is a 409 rather than a silent no-op -- legacy answered
183
+ * `{ success: true }` either way, which made a failed reschedule look like a
184
+ * successful one.
185
+ *
186
+ * Moving one occurrence does not move the rest of its series.
187
+ *
188
+ * ```ts
189
+ * await crm.scheduledMessages.reschedule(id, { scheduled_at: '2026-10-16T14:00:00' });
190
+ * ```
191
+ *
192
+ * Scope: `messages:write`.
193
+ */
194
+ reschedule(id: string, input: RescheduleMessageInput): Promise<ScheduledMessage>;
195
+ /**
196
+ * `DELETE /v1/audience/scheduled-messages/:id` -- 200 with the cancelled row.
197
+ *
198
+ * The row is kept rather than deleted, at `status: 'cancelled'`, so a schedule
199
+ * that did not send still shows why. Use `cancelSeries` to stop a whole drip;
200
+ * this stops one message and leaves the others queued.
201
+ *
202
+ * Scope: `messages:write`.
203
+ */
204
+ cancel(id: string): Promise<ScheduledMessage>;
205
+ /**
206
+ * `DELETE /v1/audience/scheduled-messages/series/:series` -- 200 with the
207
+ * number of occurrences stood down.
208
+ *
209
+ * Cancels every `pending` occurrence; anything already sent is untouched and
210
+ * not counted. Idempotent -- a second call cancels nothing and answers `0`.
211
+ *
212
+ * Usually you do not need this. If the series carries a condition, changing
213
+ * the contact so it no longer matches is the better way to stop a drip: it
214
+ * leaves the reason on the record, and it works for every contact in the same
215
+ * sequence rather than one series at a time.
216
+ *
217
+ * Scope: `messages:write`.
218
+ */
219
+ cancelSeries(series: string): Promise<CancelSeriesResult>;
220
+ /**
221
+ * `POST /v1/audience/scheduled-messages/:id/send-now` -- 200 with the row.
222
+ *
223
+ * Brings a `pending` occurrence forward to immediately. The condition is still
224
+ * checked, because the condition is the point -- a send-now on a contact who
225
+ * no longer matches comes back `skipped`, not `sent`.
226
+ *
227
+ * It does not duplicate: the occurrence is moved, not copied, so the original
228
+ * time no longer fires. Legacy's equivalent scheduled a second job without
229
+ * cancelling the first and delivered the message twice.
230
+ *
231
+ * Scope: `messages:write`.
232
+ */
233
+ sendNow(id: string): Promise<ScheduledMessage>;
234
+ }