ab-ecosystem-cpass 1.0.6 → 1.0.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.
- package/README.md +1 -2
- package/dist/client.d.ts +7 -6
- package/dist/client.js +8 -8
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +3 -5
- package/dist/index.js.map +1 -1
- package/dist/resources/campaign-messages.d.ts +150 -0
- package/dist/resources/campaign-messages.js +172 -0
- package/dist/resources/campaign-messages.js.map +1 -0
- package/dist/types/campaign.d.ts +131 -41
- package/docs/campaign/campaign-messages.md +273 -0
- package/docs/campaign/campaigns.md +8 -48
- package/package.json +1 -1
- package/dist/resources/campaign-report-exports.d.ts +0 -14
- package/dist/resources/campaign-report-exports.js +0 -28
- package/dist/resources/campaign-report-exports.js.map +0 -1
- package/dist/resources/campaign-reports.d.ts +0 -15
- package/dist/resources/campaign-reports.js +0 -25
- package/dist/resources/campaign-reports.js.map +0 -1
package/README.md
CHANGED
|
@@ -275,8 +275,7 @@ to show a user.
|
|
|
275
275
|
| Resource | Page | Actions |
|
|
276
276
|
| --- | --- | --- |
|
|
277
277
|
| `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` |
|
|
278
|
-
| `crm.
|
|
279
|
-
| `crm.campaignReportExports` | [campaigns](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/campaign/campaigns.md#campaign-reports) | `list` `get` `create` |
|
|
278
|
+
| `crm.campaignMessages` | [campaign messages](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/campaign/campaign-messages.md) | `stats` `list` `iterate` `replies` — reporting, counted off the per-recipient rows |
|
|
280
279
|
|
|
281
280
|
The audience helpers are exported too, so a UI can show "12,480 recipients, 3
|
|
282
281
|
duplicates dropped" before anything is uploaded:
|
package/dist/client.d.ts
CHANGED
|
@@ -18,8 +18,7 @@ import { Billing } from './resources/channels/billing';
|
|
|
18
18
|
import { Wallet } from './resources/channels/wallet';
|
|
19
19
|
import { Activity } from './resources/channels/activity';
|
|
20
20
|
import { Campaigns } from './resources/campaigns';
|
|
21
|
-
import {
|
|
22
|
-
import { CampaignReportExports } from './resources/campaign-report-exports';
|
|
21
|
+
import { CampaignMessages } from './resources/campaign-messages';
|
|
23
22
|
/**
|
|
24
23
|
* One client for the whole gateway. Construct it once and share it: it holds no
|
|
25
24
|
* per-request state, and every resource below runs over the same HTTP client,
|
|
@@ -91,9 +90,11 @@ export declare class CrmClient {
|
|
|
91
90
|
* has to get exactly right.
|
|
92
91
|
*/
|
|
93
92
|
readonly campaigns: Campaigns;
|
|
94
|
-
/**
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Campaign reporting: the funnel, the per-recipient rows behind it, and what
|
|
95
|
+
* people replied -- all counted off the message rows when asked for, so there
|
|
96
|
+
* is no roll-up to wait for and no export job to poll.
|
|
97
|
+
*/
|
|
98
|
+
readonly campaignMessages: CampaignMessages;
|
|
98
99
|
constructor(options: ClientOptions);
|
|
99
100
|
}
|
package/dist/client.js
CHANGED
|
@@ -24,8 +24,7 @@ const wallet_1 = require("./resources/channels/wallet");
|
|
|
24
24
|
const activity_1 = require("./resources/channels/activity");
|
|
25
25
|
// Campaign
|
|
26
26
|
const campaigns_1 = require("./resources/campaigns");
|
|
27
|
-
const
|
|
28
|
-
const campaign_report_exports_1 = require("./resources/campaign-report-exports");
|
|
27
|
+
const campaign_messages_1 = require("./resources/campaign-messages");
|
|
29
28
|
/**
|
|
30
29
|
* One client for the whole gateway. Construct it once and share it: it holds no
|
|
31
30
|
* per-request state, and every resource below runs over the same HTTP client,
|
|
@@ -100,10 +99,12 @@ class CrmClient {
|
|
|
100
99
|
* has to get exactly right.
|
|
101
100
|
*/
|
|
102
101
|
campaigns;
|
|
103
|
-
/**
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Campaign reporting: the funnel, the per-recipient rows behind it, and what
|
|
104
|
+
* people replied -- all counted off the message rows when asked for, so there
|
|
105
|
+
* is no roll-up to wait for and no export job to poll.
|
|
106
|
+
*/
|
|
107
|
+
campaignMessages;
|
|
107
108
|
constructor(options) {
|
|
108
109
|
const http = new http_1.HttpClient(options);
|
|
109
110
|
this.contacts = new contacts_1.Contacts(http);
|
|
@@ -128,8 +129,7 @@ class CrmClient {
|
|
|
128
129
|
// audience is uploaded through the media endpoint before the campaign is
|
|
129
130
|
// created.
|
|
130
131
|
this.campaigns = new campaigns_1.Campaigns(http);
|
|
131
|
-
this.
|
|
132
|
-
this.campaignReportExports = new campaign_report_exports_1.CampaignReportExports(http);
|
|
132
|
+
this.campaignMessages = new campaign_messages_1.CampaignMessages(http);
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
exports.CrmClient = CrmClient;
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;AAAA,iCAAwD;AAExD,WAAW;AACX,4DAAyD;AACzD,oEAAgE;AAChE,sEAAkE;AAClE,sEAAkE;AAClE,4DAAyD;AACzD,oEAAgE;AAChE,wEAA8D;AAC9D,wEAA8D;AAC9D,4DAAyD;AACzD,gFAA4E;AAC5E,kEAA8D;AAC9D,sEAAmE;AAEnE,WAAW;AACX,4DAAyD;AACzD,8DAA2D;AAC3D,4DAAgE;AAChE,0DAAuD;AACvD,wDAAqD;AACrD,4DAAyD;AAEzD,WAAW;AACX,qDAAkD;AAClD,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";;;AAAA,iCAAwD;AAExD,WAAW;AACX,4DAAyD;AACzD,oEAAgE;AAChE,sEAAkE;AAClE,sEAAkE;AAClE,4DAAyD;AACzD,oEAAgE;AAChE,wEAA8D;AAC9D,wEAA8D;AAC9D,4DAAyD;AACzD,gFAA4E;AAC5E,kEAA8D;AAC9D,sEAAmE;AAEnE,WAAW;AACX,4DAAyD;AACzD,8DAA2D;AAC3D,4DAAgE;AAChE,0DAAuD;AACvD,wDAAqD;AACrD,4DAAyD;AAEzD,WAAW;AACX,qDAAkD;AAClD,qEAAiE;AAEjE;;;;;;;;;;;GAWG;AACH,MAAa,SAAS;IACpB,4EAA4E;IAE5E,gDAAgD;IACvC,QAAQ,CAAW;IAC5B,oDAAoD;IAC3C,WAAW,CAAc;IAClC,kEAAkE;IACzD,YAAY,CAAe;IACpC,qDAAqD;IAC5C,YAAY,CAAe;IACpC,uCAAuC;IAC9B,QAAQ,CAAW;IAC5B,qFAAqF;IAC5E,WAAW,CAAc;IAClC,kEAAkE;IACzD,OAAO,CAAU;IAC1B,yDAAyD;IAChD,OAAO,CAAU;IAC1B,0DAA0D;IACjD,QAAQ,CAAW;IAC5B;;;OAGG;IACM,iBAAiB,CAAoB;IAE9C;;;;;;OAMG;IACM,UAAU,CAAa;IAEhC;;;;OAIG;IACM,aAAa,CAAgB;IAEtC,4EAA4E;IAE5E,gEAAgE;IACvD,QAAQ,CAAW;IAC5B,+CAA+C;IACtC,SAAS,CAAY;IAC9B,wEAAwE;IAC/D,eAAe,CAAkB;IAC1C,yDAAyD;IAChD,OAAO,CAAU;IAC1B,6BAA6B;IACpB,MAAM,CAAS;IACxB,6BAA6B;IACpB,QAAQ,CAAW;IAE5B,4EAA4E;IAE5E;;;;;;;OAOG;IACM,SAAS,CAAY;IAC9B;;;;OAIG;IACM,gBAAgB,CAAmB;IAE5C,YAAY,OAAsB;QAChC,MAAM,IAAI,GAAG,IAAI,iBAAU,CAAC,OAAO,CAAC,CAAC;QAErC,IAAI,CAAC,QAAQ,GAAG,IAAI,mBAAQ,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,WAAW,GAAG,IAAI,0BAAW,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,YAAY,GAAG,IAAI,4BAAY,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,YAAY,GAAG,IAAI,4BAAY,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,QAAQ,GAAG,IAAI,mBAAQ,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,WAAW,GAAG,IAAI,0BAAW,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,OAAO,GAAG,IAAI,wBAAO,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,IAAI,wBAAO,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,IAAI,mBAAQ,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,iBAAiB,GAAG,IAAI,sCAAiB,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,CAAC,UAAU,GAAG,IAAI,wBAAU,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,aAAa,GAAG,IAAI,6BAAa,CAAC,IAAI,CAAC,CAAC;QAE7C,IAAI,CAAC,QAAQ,GAAG,IAAI,mBAAQ,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,SAAS,GAAG,IAAI,qBAAS,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,eAAe,GAAG,IAAI,0BAAe,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC,OAAO,GAAG,IAAI,iBAAO,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,IAAI,eAAM,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,IAAI,mBAAQ,CAAC,IAAI,CAAC,CAAC;QAEnC,uEAAuE;QACvE,yEAAyE;QACzE,WAAW;QACX,IAAI,CAAC,SAAS,GAAG,IAAI,qBAAS,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,gBAAgB,GAAG,IAAI,oCAAgB,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;CACF;AAzGD,8BAyGC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,8 +15,7 @@ export { Webhooks } from './resources/audience/webhooks';
|
|
|
15
15
|
export { Notifications } from './resources/audience/notifications';
|
|
16
16
|
export { InboxMedia, type UploadMediaOptions, type UploadMediaSource, } from './resources/audience/inbox-media';
|
|
17
17
|
export { Campaigns, type CreateBulkCampaignInput, type BulkCampaignResult, type CreateGroupCampaignInput, type GroupBucket, type GroupBucketResult, type GroupCampaignResult, type ReceiverUploader, } from './resources/campaigns';
|
|
18
|
-
export {
|
|
19
|
-
export { CampaignReportExports } from './resources/campaign-report-exports';
|
|
18
|
+
export { CampaignMessages } from './resources/campaign-messages';
|
|
20
19
|
/**
|
|
21
20
|
* The audience helpers, exported because they are useful before a campaign
|
|
22
21
|
* exists: a UI can parse a CSV, show "12,480 recipients, 3 duplicates dropped",
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.Activity = exports.Wallet = exports.Billing = exports.ChannelWebhooks = exports.Templates = exports.Channels = exports.resolveAudience = exports.parseDelimited = exports.normaliseNumbers = exports.normaliseColumn = exports.buildReceivers = exports.MAX_BULK_RECEIVERS = exports.
|
|
17
|
+
exports.Activity = exports.Wallet = exports.Billing = exports.ChannelWebhooks = exports.Templates = exports.Channels = exports.resolveAudience = exports.parseDelimited = exports.normaliseNumbers = exports.normaliseColumn = exports.buildReceivers = exports.MAX_BULK_RECEIVERS = exports.CampaignMessages = exports.Campaigns = exports.InboxMedia = exports.Notifications = exports.Webhooks = exports.Exports = exports.Imports = exports.CrmTriggers = exports.Segments = exports.CustomFields = exports.ContactNotes = exports.ContactTags = exports.Contacts = exports.CHANNELS = exports.AUDIENCE = void 0;
|
|
18
18
|
__exportStar(require("./client"), exports);
|
|
19
19
|
__exportStar(require("./http"), exports);
|
|
20
20
|
__exportStar(require("./errors"), exports);
|
|
@@ -48,10 +48,8 @@ Object.defineProperty(exports, "InboxMedia", { enumerable: true, get: function (
|
|
|
48
48
|
// Campaign
|
|
49
49
|
var campaigns_1 = require("./resources/campaigns");
|
|
50
50
|
Object.defineProperty(exports, "Campaigns", { enumerable: true, get: function () { return campaigns_1.Campaigns; } });
|
|
51
|
-
var
|
|
52
|
-
Object.defineProperty(exports, "
|
|
53
|
-
var campaign_report_exports_1 = require("./resources/campaign-report-exports");
|
|
54
|
-
Object.defineProperty(exports, "CampaignReportExports", { enumerable: true, get: function () { return campaign_report_exports_1.CampaignReportExports; } });
|
|
51
|
+
var campaign_messages_1 = require("./resources/campaign-messages");
|
|
52
|
+
Object.defineProperty(exports, "CampaignMessages", { enumerable: true, get: function () { return campaign_messages_1.CampaignMessages; } });
|
|
55
53
|
/**
|
|
56
54
|
* The audience helpers, exported because they are useful before a campaign
|
|
57
55
|
* exists: a UI can parse a CSV, show "12,480 recipients, 3 duplicates dropped",
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,yCAAuB;AACvB,2CAAyB;AACzB,0CAAwB;AAExB,2CAAuD;AAA9C,iGAAA,QAAQ,OAAA;AAAE,iGAAA,QAAQ,OAAA;AAE3B,WAAW;AACX,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA;AACjB,kEAAgE;AAAvD,2GAAA,WAAW,OAAA;AACpB,oEAAkE;AAAzD,6GAAA,YAAY,OAAA;AACrB,oEAAkE;AAAzD,6GAAA,YAAY,OAAA;AACrB,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA;AACjB,kEAAgE;AAAvD,2GAAA,WAAW,OAAA;AACpB,sEAA8D;AAArD,yGAAA,OAAO,OAAA;AAChB,sEAA8D;AAArD,yGAAA,OAAO,OAAA;AAChB,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA;AACjB,oEAAmE;AAA1D,8GAAA,aAAa,OAAA;AACtB,gEAI0C;AAHxC,yGAAA,UAAU,OAAA;AAKZ,WAAW;AACX,mDAS+B;AAR7B,sGAAA,SAAS,OAAA;AASX,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,yCAAuB;AACvB,2CAAyB;AACzB,0CAAwB;AAExB,2CAAuD;AAA9C,iGAAA,QAAQ,OAAA;AAAE,iGAAA,QAAQ,OAAA;AAE3B,WAAW;AACX,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA;AACjB,kEAAgE;AAAvD,2GAAA,WAAW,OAAA;AACpB,oEAAkE;AAAzD,6GAAA,YAAY,OAAA;AACrB,oEAAkE;AAAzD,6GAAA,YAAY,OAAA;AACrB,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA;AACjB,kEAAgE;AAAvD,2GAAA,WAAW,OAAA;AACpB,sEAA8D;AAArD,yGAAA,OAAO,OAAA;AAChB,sEAA8D;AAArD,yGAAA,OAAO,OAAA;AAChB,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA;AACjB,oEAAmE;AAA1D,8GAAA,aAAa,OAAA;AACtB,gEAI0C;AAHxC,yGAAA,UAAU,OAAA;AAKZ,WAAW;AACX,mDAS+B;AAR7B,sGAAA,SAAS,OAAA;AASX,mEAAiE;AAAxD,qHAAA,gBAAgB,OAAA;AACzB;;;;GAIG;AACH,mEAUuC;AATrC,uHAAA,kBAAkB,OAAA;AAClB,mHAAA,cAAc,OAAA;AACd,oHAAA,eAAe,OAAA;AACf,qHAAA,gBAAgB,OAAA;AAChB,mHAAA,cAAc,OAAA;AACd,oHAAA,eAAe,OAAA;AAMjB,WAAW;AACX,0DAA8E;AAArE,oGAAA,QAAQ,OAAA;AACjB,4DAA2D;AAAlD,sGAAA,SAAS,OAAA;AAClB,0DAAgE;AAAvD,2GAAA,eAAe,OAAA;AACxB,wDAAuD;AAA9C,kGAAA,OAAO,OAAA;AAChB,sDAAqD;AAA5C,gGAAA,MAAM,OAAA;AACf,0DAAyD;AAAhD,oGAAA,QAAQ,OAAA"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { CampaignMessageRecord, CampaignMessageStats, CampaignMessageStatsQuery, CampaignRepliesQuery, CampaignReplyBreakdown, ListCampaignMessagesQuery } from '../types/campaign';
|
|
2
|
+
import type { Paginated } from '../types/common';
|
|
3
|
+
import type { HttpClient } from '../http';
|
|
4
|
+
/**
|
|
5
|
+
* Campaign reporting: what actually happened to each message.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const stats = await crm.campaignMessages.stats({ campaign });
|
|
9
|
+
* // { total: 12480, sent: 12410, delivered: 11980, read: 7420, replied: 310, failed: 70 }
|
|
10
|
+
*
|
|
11
|
+
* const { data } = await crm.campaignMessages.list({ campaign, status: 'failed' });
|
|
12
|
+
* data[0].error; // why that one did not go
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* ## Where these numbers come from
|
|
16
|
+
*
|
|
17
|
+
* One row per attempted send, written by the campaign manager as it hands each
|
|
18
|
+
* message to a channel, with its `status` moved forward by the channel's
|
|
19
|
+
* delivery webhooks -- which are processed outside the CRM entirely. The report
|
|
20
|
+
* is **counted off those rows when you ask for it**, not rolled up on a
|
|
21
|
+
* schedule, so there is no "generated at" to reason about and no staleness
|
|
22
|
+
* window: the numbers are as current as the webhooks that have arrived.
|
|
23
|
+
*
|
|
24
|
+
* The flip side is that a campaign still going out has moving numbers. Reading
|
|
25
|
+
* `stats` seconds after launching under-counts everything, and that is correct
|
|
26
|
+
* rather than a lag.
|
|
27
|
+
*
|
|
28
|
+
* ## The funnel
|
|
29
|
+
*
|
|
30
|
+
* `status` is a single field holding the furthest stage reached, so the counts
|
|
31
|
+
* are cumulative and nest: `replied ⊆ read ⊆ delivered ⊆ sent`. For a stage's
|
|
32
|
+
* exclusive count, subtract the next one -- `delivered - read` is "delivered but
|
|
33
|
+
* unread". `failed` is outside the funnel and never inside `sent`.
|
|
34
|
+
*
|
|
35
|
+
* ## There is no export endpoint
|
|
36
|
+
*
|
|
37
|
+
* An export is {@link list} written to a file, so {@link iterate} does the
|
|
38
|
+
* paging and the caller formats the rows. Nothing is queued, nothing has to be
|
|
39
|
+
* polled, and no generated file expires before someone downloads it.
|
|
40
|
+
*/
|
|
41
|
+
export declare class CampaignMessages {
|
|
42
|
+
private readonly http;
|
|
43
|
+
constructor(http: HttpClient);
|
|
44
|
+
/**
|
|
45
|
+
* The campaign's funnel counts.
|
|
46
|
+
*
|
|
47
|
+
* `GET /v1/campaign-messages/stats?campaign=<id>` · `campaigns:read` · **200**
|
|
48
|
+
*
|
|
49
|
+
* ### Query
|
|
50
|
+
*
|
|
51
|
+
* | Field | Type | Required | Notes |
|
|
52
|
+
* | --- | --- | --- | --- |
|
|
53
|
+
* | `campaign` | string | yes | The campaign id. |
|
|
54
|
+
* | `message` | number | no | One message of a multi-message campaign, zero-based. |
|
|
55
|
+
*
|
|
56
|
+
* ```ts
|
|
57
|
+
* const overall = await crm.campaignMessages.stats({ campaign });
|
|
58
|
+
* const second = await crm.campaignMessages.stats({ campaign, message: 1 });
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* No date window: a campaign is one send, so its whole life is the only
|
|
62
|
+
* meaningful window. A repeating campaign's runs are separate campaigns with
|
|
63
|
+
* their own ids.
|
|
64
|
+
*
|
|
65
|
+
* `404 not_found` if the campaign is not the caller's -- the same answer as a
|
|
66
|
+
* campaign that does not exist, so the error cannot be used to probe ids.
|
|
67
|
+
*/
|
|
68
|
+
stats(query: CampaignMessageStatsQuery): Promise<CampaignMessageStats>;
|
|
69
|
+
/**
|
|
70
|
+
* The rows behind the funnel. This is the report table.
|
|
71
|
+
*
|
|
72
|
+
* `GET /v1/campaign-messages?campaign=<id>` · `campaigns:read` · **200**
|
|
73
|
+
*
|
|
74
|
+
* ### Query
|
|
75
|
+
*
|
|
76
|
+
* | Field | Type | Default | Notes |
|
|
77
|
+
* | --- | --- | --- | --- |
|
|
78
|
+
* | `campaign` | string | — | Required. |
|
|
79
|
+
* | `message` | number | — | Zero-based index of one campaign message. |
|
|
80
|
+
* | `status` | `sent \| delivered \| read \| replied \| failed` | — | Cumulative; see below. Omit for every row. |
|
|
81
|
+
* | `exact` | boolean | `false` | Only rows sitting at `status` right now. |
|
|
82
|
+
* | `identifier` | string | — | Exact match on one recipient. Not a search. |
|
|
83
|
+
* | `reply_type` | string | — | Replied rows only; refused otherwise. |
|
|
84
|
+
* | `replied_within_hours` | number | — | Replied rows only; refused otherwise. |
|
|
85
|
+
* | `page` | number | `1` | 1-based. |
|
|
86
|
+
* | `rows` | number | `25` | 1–100. |
|
|
87
|
+
*
|
|
88
|
+
* Newest attempt first, ordered by when the row was written -- not by a stage
|
|
89
|
+
* timestamp, which would reorder the list as webhooks arrive and make paging
|
|
90
|
+
* skip and repeat rows.
|
|
91
|
+
*
|
|
92
|
+
* ```ts
|
|
93
|
+
* // the two queries a report screen actually makes
|
|
94
|
+
* const failures = await crm.campaignMessages.list({ campaign, status: 'failed', rows: 100 });
|
|
95
|
+
* const unread = await crm.campaignMessages.list({ campaign, status: 'delivered', exact: true });
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* `reply_type` and `replied_within_hours` are **refused** with
|
|
99
|
+
* `400 validation_failed` unless `status` is `replied`, rather than quietly
|
|
100
|
+
* matching nothing.
|
|
101
|
+
*/
|
|
102
|
+
list(query: ListCampaignMessagesQuery): Promise<Paginated<CampaignMessageRecord>>;
|
|
103
|
+
/**
|
|
104
|
+
* Every matching row, paged through for you. What an export is.
|
|
105
|
+
*
|
|
106
|
+
* ```ts
|
|
107
|
+
* for await (const row of crm.campaignMessages.iterate({ campaign })) {
|
|
108
|
+
* csv.write([row.identifier, row.name, row.status, row.error ?? '']);
|
|
109
|
+
* }
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* Pages at 100 rows unless told otherwise -- the server's maximum, and the
|
|
113
|
+
* fewest round trips. A campaign of a million recipients is ten thousand
|
|
114
|
+
* requests, so for exports that size prefer running it where the latency is
|
|
115
|
+
* low rather than from a browser.
|
|
116
|
+
*
|
|
117
|
+
* The collection is live: rows are written while a campaign is going out, and
|
|
118
|
+
* statuses change under an export that takes minutes. Offset paging over
|
|
119
|
+
* moving data can repeat or skip a row -- for a stable snapshot, export after
|
|
120
|
+
* the campaign completes.
|
|
121
|
+
*/
|
|
122
|
+
iterate(query: ListCampaignMessagesQuery): AsyncGenerator<CampaignMessageRecord>;
|
|
123
|
+
/**
|
|
124
|
+
* What people replied, grouped by the reply itself.
|
|
125
|
+
*
|
|
126
|
+
* `GET /v1/campaign-messages/replies?campaign=<id>` · `campaigns:read` · **200**
|
|
127
|
+
*
|
|
128
|
+
* ### Query
|
|
129
|
+
*
|
|
130
|
+
* | Field | Type | Default | Notes |
|
|
131
|
+
* | --- | --- | --- | --- |
|
|
132
|
+
* | `campaign` | string | — | Required. |
|
|
133
|
+
* | `message` | number | — | Zero-based index of one campaign message. |
|
|
134
|
+
* | `limit` | number | `50` | Distinct replies to return, 1–200. |
|
|
135
|
+
*
|
|
136
|
+
* ```ts
|
|
137
|
+
* const { groups, replied, distinct } = await crm.campaignMessages.replies({ campaign });
|
|
138
|
+
* groups; // [{ reply: 'Yes', replyType: 'button', count: 182 }, …]
|
|
139
|
+
* ```
|
|
140
|
+
*
|
|
141
|
+
* This is what a poll or a button message is sent to find out. Grouped on the
|
|
142
|
+
* reply **and** its type, so a tapped `Yes` and a typed `Yes` stay apart --
|
|
143
|
+
* they say different things about how engaged the recipient was.
|
|
144
|
+
*
|
|
145
|
+
* `distinct` against `groups.length` tells you whether the breakdown is
|
|
146
|
+
* complete. A campaign that asked an open question has as many groups as it
|
|
147
|
+
* has repliers, so a truncated answer there is normal rather than a problem.
|
|
148
|
+
*/
|
|
149
|
+
replies(query: CampaignRepliesQuery): Promise<CampaignReplyBreakdown>;
|
|
150
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CampaignMessages = void 0;
|
|
4
|
+
const BASE = '/v1/campaign-messages';
|
|
5
|
+
/**
|
|
6
|
+
* Campaign reporting: what actually happened to each message.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const stats = await crm.campaignMessages.stats({ campaign });
|
|
10
|
+
* // { total: 12480, sent: 12410, delivered: 11980, read: 7420, replied: 310, failed: 70 }
|
|
11
|
+
*
|
|
12
|
+
* const { data } = await crm.campaignMessages.list({ campaign, status: 'failed' });
|
|
13
|
+
* data[0].error; // why that one did not go
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* ## Where these numbers come from
|
|
17
|
+
*
|
|
18
|
+
* One row per attempted send, written by the campaign manager as it hands each
|
|
19
|
+
* message to a channel, with its `status` moved forward by the channel's
|
|
20
|
+
* delivery webhooks -- which are processed outside the CRM entirely. The report
|
|
21
|
+
* is **counted off those rows when you ask for it**, not rolled up on a
|
|
22
|
+
* schedule, so there is no "generated at" to reason about and no staleness
|
|
23
|
+
* window: the numbers are as current as the webhooks that have arrived.
|
|
24
|
+
*
|
|
25
|
+
* The flip side is that a campaign still going out has moving numbers. Reading
|
|
26
|
+
* `stats` seconds after launching under-counts everything, and that is correct
|
|
27
|
+
* rather than a lag.
|
|
28
|
+
*
|
|
29
|
+
* ## The funnel
|
|
30
|
+
*
|
|
31
|
+
* `status` is a single field holding the furthest stage reached, so the counts
|
|
32
|
+
* are cumulative and nest: `replied ⊆ read ⊆ delivered ⊆ sent`. For a stage's
|
|
33
|
+
* exclusive count, subtract the next one -- `delivered - read` is "delivered but
|
|
34
|
+
* unread". `failed` is outside the funnel and never inside `sent`.
|
|
35
|
+
*
|
|
36
|
+
* ## There is no export endpoint
|
|
37
|
+
*
|
|
38
|
+
* An export is {@link list} written to a file, so {@link iterate} does the
|
|
39
|
+
* paging and the caller formats the rows. Nothing is queued, nothing has to be
|
|
40
|
+
* polled, and no generated file expires before someone downloads it.
|
|
41
|
+
*/
|
|
42
|
+
class CampaignMessages {
|
|
43
|
+
http;
|
|
44
|
+
constructor(http) {
|
|
45
|
+
this.http = http;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The campaign's funnel counts.
|
|
49
|
+
*
|
|
50
|
+
* `GET /v1/campaign-messages/stats?campaign=<id>` · `campaigns:read` · **200**
|
|
51
|
+
*
|
|
52
|
+
* ### Query
|
|
53
|
+
*
|
|
54
|
+
* | Field | Type | Required | Notes |
|
|
55
|
+
* | --- | --- | --- | --- |
|
|
56
|
+
* | `campaign` | string | yes | The campaign id. |
|
|
57
|
+
* | `message` | number | no | One message of a multi-message campaign, zero-based. |
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
60
|
+
* const overall = await crm.campaignMessages.stats({ campaign });
|
|
61
|
+
* const second = await crm.campaignMessages.stats({ campaign, message: 1 });
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* No date window: a campaign is one send, so its whole life is the only
|
|
65
|
+
* meaningful window. A repeating campaign's runs are separate campaigns with
|
|
66
|
+
* their own ids.
|
|
67
|
+
*
|
|
68
|
+
* `404 not_found` if the campaign is not the caller's -- the same answer as a
|
|
69
|
+
* campaign that does not exist, so the error cannot be used to probe ids.
|
|
70
|
+
*/
|
|
71
|
+
stats(query) {
|
|
72
|
+
return this.http.request('GET', `${BASE}/stats`, { query });
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The rows behind the funnel. This is the report table.
|
|
76
|
+
*
|
|
77
|
+
* `GET /v1/campaign-messages?campaign=<id>` · `campaigns:read` · **200**
|
|
78
|
+
*
|
|
79
|
+
* ### Query
|
|
80
|
+
*
|
|
81
|
+
* | Field | Type | Default | Notes |
|
|
82
|
+
* | --- | --- | --- | --- |
|
|
83
|
+
* | `campaign` | string | — | Required. |
|
|
84
|
+
* | `message` | number | — | Zero-based index of one campaign message. |
|
|
85
|
+
* | `status` | `sent \| delivered \| read \| replied \| failed` | — | Cumulative; see below. Omit for every row. |
|
|
86
|
+
* | `exact` | boolean | `false` | Only rows sitting at `status` right now. |
|
|
87
|
+
* | `identifier` | string | — | Exact match on one recipient. Not a search. |
|
|
88
|
+
* | `reply_type` | string | — | Replied rows only; refused otherwise. |
|
|
89
|
+
* | `replied_within_hours` | number | — | Replied rows only; refused otherwise. |
|
|
90
|
+
* | `page` | number | `1` | 1-based. |
|
|
91
|
+
* | `rows` | number | `25` | 1–100. |
|
|
92
|
+
*
|
|
93
|
+
* Newest attempt first, ordered by when the row was written -- not by a stage
|
|
94
|
+
* timestamp, which would reorder the list as webhooks arrive and make paging
|
|
95
|
+
* skip and repeat rows.
|
|
96
|
+
*
|
|
97
|
+
* ```ts
|
|
98
|
+
* // the two queries a report screen actually makes
|
|
99
|
+
* const failures = await crm.campaignMessages.list({ campaign, status: 'failed', rows: 100 });
|
|
100
|
+
* const unread = await crm.campaignMessages.list({ campaign, status: 'delivered', exact: true });
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* `reply_type` and `replied_within_hours` are **refused** with
|
|
104
|
+
* `400 validation_failed` unless `status` is `replied`, rather than quietly
|
|
105
|
+
* matching nothing.
|
|
106
|
+
*/
|
|
107
|
+
list(query) {
|
|
108
|
+
return this.http.request('GET', BASE, { query });
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Every matching row, paged through for you. What an export is.
|
|
112
|
+
*
|
|
113
|
+
* ```ts
|
|
114
|
+
* for await (const row of crm.campaignMessages.iterate({ campaign })) {
|
|
115
|
+
* csv.write([row.identifier, row.name, row.status, row.error ?? '']);
|
|
116
|
+
* }
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* Pages at 100 rows unless told otherwise -- the server's maximum, and the
|
|
120
|
+
* fewest round trips. A campaign of a million recipients is ten thousand
|
|
121
|
+
* requests, so for exports that size prefer running it where the latency is
|
|
122
|
+
* low rather than from a browser.
|
|
123
|
+
*
|
|
124
|
+
* The collection is live: rows are written while a campaign is going out, and
|
|
125
|
+
* statuses change under an export that takes minutes. Offset paging over
|
|
126
|
+
* moving data can repeat or skip a row -- for a stable snapshot, export after
|
|
127
|
+
* the campaign completes.
|
|
128
|
+
*/
|
|
129
|
+
async *iterate(query) {
|
|
130
|
+
const rows = query.rows ?? 100;
|
|
131
|
+
let page = query.page ?? 1;
|
|
132
|
+
for (;;) {
|
|
133
|
+
const result = await this.list({ ...query, page, rows });
|
|
134
|
+
for (const record of result.data)
|
|
135
|
+
yield record;
|
|
136
|
+
if (result.data.length === 0 || page >= result.pagination.pages)
|
|
137
|
+
return;
|
|
138
|
+
page += 1;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* What people replied, grouped by the reply itself.
|
|
143
|
+
*
|
|
144
|
+
* `GET /v1/campaign-messages/replies?campaign=<id>` · `campaigns:read` · **200**
|
|
145
|
+
*
|
|
146
|
+
* ### Query
|
|
147
|
+
*
|
|
148
|
+
* | Field | Type | Default | Notes |
|
|
149
|
+
* | --- | --- | --- | --- |
|
|
150
|
+
* | `campaign` | string | — | Required. |
|
|
151
|
+
* | `message` | number | — | Zero-based index of one campaign message. |
|
|
152
|
+
* | `limit` | number | `50` | Distinct replies to return, 1–200. |
|
|
153
|
+
*
|
|
154
|
+
* ```ts
|
|
155
|
+
* const { groups, replied, distinct } = await crm.campaignMessages.replies({ campaign });
|
|
156
|
+
* groups; // [{ reply: 'Yes', replyType: 'button', count: 182 }, …]
|
|
157
|
+
* ```
|
|
158
|
+
*
|
|
159
|
+
* This is what a poll or a button message is sent to find out. Grouped on the
|
|
160
|
+
* reply **and** its type, so a tapped `Yes` and a typed `Yes` stay apart --
|
|
161
|
+
* they say different things about how engaged the recipient was.
|
|
162
|
+
*
|
|
163
|
+
* `distinct` against `groups.length` tells you whether the breakdown is
|
|
164
|
+
* complete. A campaign that asked an open question has as many groups as it
|
|
165
|
+
* has repliers, so a truncated answer there is normal rather than a problem.
|
|
166
|
+
*/
|
|
167
|
+
replies(query) {
|
|
168
|
+
return this.http.request('GET', `${BASE}/replies`, { query });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.CampaignMessages = CampaignMessages;
|
|
172
|
+
//# sourceMappingURL=campaign-messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"campaign-messages.js","sourceRoot":"","sources":["../../src/resources/campaign-messages.ts"],"names":[],"mappings":";;;AAWA,MAAM,IAAI,GAAG,uBAAuB,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAa,gBAAgB;IACE;IAA7B,YAA6B,IAAgB;QAAhB,SAAI,GAAJ,IAAI,CAAY;IAAG,CAAC;IAEjD;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,KAAK,CAAC,KAAgC;QACpC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAuB,KAAK,EAAE,GAAG,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACpF,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,IAAI,CAAC,KAAgC;QACnC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAmC,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACrF,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,KAAK,CAAC,CAAC,OAAO,CAAC,KAAgC;QAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC;QAC/B,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;QAE3B,SAAS,CAAC;YACR,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACzD,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,IAAI;gBAAE,MAAM,MAAM,CAAC;YAE/C,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK;gBAAE,OAAO;YACxE,IAAI,IAAI,CAAC,CAAC;QACZ,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,OAAO,CAAC,KAA2B;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAyB,KAAK,EAAE,GAAG,IAAI,UAAU,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACxF,CAAC;CACF;AAjID,4CAiIC"}
|
package/dist/types/campaign.d.ts
CHANGED
|
@@ -77,8 +77,8 @@ export interface CampaignRetargetSelectionFilter {
|
|
|
77
77
|
* rejects a mixture rather than guessing.
|
|
78
78
|
*/
|
|
79
79
|
export type CampaignFilter = CampaignNumbersFilter | CampaignRetargetStatusFilter | CampaignRetargetSelectionFilter | ContactFilterEntry;
|
|
80
|
-
/** Which of the
|
|
81
|
-
export type CampaignAudienceKind = 'all' | 'segment' | 'numbers' | 'retarget';
|
|
80
|
+
/** Which of the five modes a `filters` array expresses. */
|
|
81
|
+
export type CampaignAudienceKind = 'all' | 'segment' | 'numbers' | 'group' | 'retarget';
|
|
82
82
|
export interface CampaignMessage {
|
|
83
83
|
id?: string | number;
|
|
84
84
|
channel: {
|
|
@@ -180,50 +180,140 @@ export type ListCampaignsQuery = {
|
|
|
180
180
|
type?: string;
|
|
181
181
|
hidden?: boolean;
|
|
182
182
|
};
|
|
183
|
-
/**
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
export
|
|
183
|
+
/**
|
|
184
|
+
* How far one message got.
|
|
185
|
+
*
|
|
186
|
+
* A funnel held in **one field**, not a set of flags: a row carries the furthest
|
|
187
|
+
* state it has reached, so `read` also means delivered and sent. There is no
|
|
188
|
+
* `queued` -- a row only exists once the send has been attempted.
|
|
189
|
+
*
|
|
190
|
+
* `failed` sits off the funnel rather than at the end of it. A failed message
|
|
191
|
+
* was never delivered, so it is counted on its own and never inside `sent`.
|
|
192
|
+
*/
|
|
193
|
+
export type CampaignMessageStatus = 'sent' | 'delivered' | 'read' | 'replied' | 'failed';
|
|
194
|
+
/**
|
|
195
|
+
* One attempted send: one campaign message, to one recipient.
|
|
196
|
+
*
|
|
197
|
+
* **Not a {@link CampaignMessage}**, which is the confusingly similar
|
|
198
|
+
* thing: that is one of the messages a campaign is *defined* to send. This is a
|
|
199
|
+
* record of such a message having gone to one person, and there is one per
|
|
200
|
+
* recipient per message.
|
|
201
|
+
*
|
|
202
|
+
* Written by the campaign manager at send time; its `status` is moved forward by
|
|
203
|
+
* the channel's webhooks, which are processed outside the CRM. So a row is
|
|
204
|
+
* append-only from this SDK's side -- there is nothing here that writes one.
|
|
205
|
+
*/
|
|
206
|
+
export interface CampaignMessageRecord {
|
|
207
|
+
/**
|
|
208
|
+
* The row's own id.
|
|
209
|
+
*
|
|
210
|
+
* This is the handle a `selection` retarget takes: hand a list of these back
|
|
211
|
+
* and the campaign manager resolves them to recipients, which is how
|
|
212
|
+
* "re-send to everyone who did not read it" is built.
|
|
213
|
+
*/
|
|
194
214
|
id: string;
|
|
195
215
|
campaignId: string;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
216
|
+
/** Which message of the campaign this was, zero-based. */
|
|
217
|
+
messageIndex: number;
|
|
218
|
+
/** The provider's id for the message. `''` when the send failed. */
|
|
219
|
+
providerMessageId: string;
|
|
220
|
+
channelId: string;
|
|
221
|
+
/** The recipient's contact id. */
|
|
222
|
+
contactId: string;
|
|
223
|
+
/** The destination itself -- a WhatsApp number, a Telegram id, and so on. */
|
|
224
|
+
identifier: string;
|
|
225
|
+
/** The recipient's name at send time; `''` when the contact had none. */
|
|
226
|
+
name: string;
|
|
227
|
+
status: CampaignMessageStatus;
|
|
228
|
+
/** Set for each stage the row has reached, absent for the ones it has not. */
|
|
229
|
+
sentAt?: IsoDate;
|
|
230
|
+
deliveredAt?: IsoDate;
|
|
231
|
+
readAt?: IsoDate;
|
|
232
|
+
repliedAt?: IsoDate;
|
|
233
|
+
failedAt?: IsoDate;
|
|
234
|
+
/** What the recipient replied; `''` unless `status` is `replied`. */
|
|
235
|
+
reply: string;
|
|
236
|
+
/** The reply's kind -- `text`, `button`, … `''` unless replied. */
|
|
237
|
+
replyType: string;
|
|
238
|
+
/** Seconds between the send and the reply. `0` unless replied. */
|
|
239
|
+
repliedIn: number;
|
|
240
|
+
/** The provider's error, on failed rows only. */
|
|
241
|
+
error?: string;
|
|
242
|
+
/** When the send was attempted. */
|
|
199
243
|
createdAt: IsoDate;
|
|
200
|
-
updatedAt: IsoDate;
|
|
201
244
|
}
|
|
202
|
-
/**
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
245
|
+
/**
|
|
246
|
+
* The campaign report: the numbers behind the tiles.
|
|
247
|
+
*
|
|
248
|
+
* Every stage is **cumulative** -- `delivered` counts everything that got at
|
|
249
|
+
* least that far -- so `read` is a subset of `delivered`. Subtract for a stage's
|
|
250
|
+
* exclusive count: `delivered - read` is "delivered but unread".
|
|
251
|
+
*
|
|
252
|
+
* `total` is the only number that counts every row, failures included, and it is
|
|
253
|
+
* the number of attempted sends.
|
|
254
|
+
*/
|
|
255
|
+
export interface CampaignMessageStats {
|
|
256
|
+
total: number;
|
|
257
|
+
/** Reached the provider: sent, delivered, read or replied. */
|
|
258
|
+
sent: number;
|
|
259
|
+
/** Delivered, read or replied. */
|
|
260
|
+
delivered: number;
|
|
261
|
+
/** Read or replied. */
|
|
262
|
+
read: number;
|
|
263
|
+
replied: number;
|
|
264
|
+
/** Never part of `sent`. */
|
|
265
|
+
failed: number;
|
|
216
266
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
|
|
223
|
-
|
|
267
|
+
/** A type alias, for the same reason as {@link ListCampaignsQuery}. */
|
|
268
|
+
export type CampaignMessageStatsQuery = {
|
|
269
|
+
/** The campaign id. Required. */
|
|
270
|
+
campaign: string;
|
|
271
|
+
/** One message of a multi-message campaign, by zero-based index. */
|
|
272
|
+
message?: number;
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* `crm.campaignMessages.list()` -- offset paginated.
|
|
276
|
+
*
|
|
277
|
+
* `status` filters **cumulatively** by default, so it agrees with the stats:
|
|
278
|
+
* `status: 'delivered'` returns the same rows the `delivered` number counted.
|
|
279
|
+
* `exact: true` instead returns only rows sitting at that stage -- "delivered
|
|
280
|
+
* but never read" -- which is the drill-down for `delivered - read`.
|
|
281
|
+
*/
|
|
282
|
+
export type ListCampaignMessagesQuery = {
|
|
283
|
+
campaign: string;
|
|
284
|
+
message?: number;
|
|
285
|
+
status?: CampaignMessageStatus;
|
|
286
|
+
/** Only rows at `status` right now. No effect without `status`, or on `failed`. */
|
|
287
|
+
exact?: boolean;
|
|
288
|
+
/** Exact match on one recipient, e.g. a single WhatsApp number. Not a search. */
|
|
289
|
+
identifier?: string;
|
|
290
|
+
/** Replied rows only; the API refuses it otherwise. */
|
|
291
|
+
reply_type?: string;
|
|
292
|
+
/** Replied rows only; the API refuses it otherwise. */
|
|
293
|
+
replied_within_hours?: number;
|
|
294
|
+
page?: number;
|
|
295
|
+
/** 1..100, default 25. */
|
|
296
|
+
rows?: number;
|
|
297
|
+
};
|
|
298
|
+
/** One distinct reply, and how many people sent it. */
|
|
299
|
+
export interface CampaignReplyGroup {
|
|
300
|
+
reply: string;
|
|
301
|
+
replyType: string;
|
|
302
|
+
count: number;
|
|
303
|
+
}
|
|
304
|
+
/** What a poll or button message was answered with. */
|
|
305
|
+
export interface CampaignReplyBreakdown {
|
|
306
|
+
/** Most common first, capped by the query's `limit`. */
|
|
307
|
+
groups: CampaignReplyGroup[];
|
|
308
|
+
/** Replied rows in total, whether or not their group is listed. */
|
|
309
|
+
replied: number;
|
|
310
|
+
/** Distinct replies in total -- compare with `groups.length` to detect truncation. */
|
|
311
|
+
distinct: number;
|
|
224
312
|
}
|
|
225
313
|
/** A type alias, for the same reason as {@link ListCampaignsQuery}. */
|
|
226
|
-
export type
|
|
227
|
-
|
|
228
|
-
|
|
314
|
+
export type CampaignRepliesQuery = {
|
|
315
|
+
campaign: string;
|
|
316
|
+
message?: number;
|
|
317
|
+
/** Distinct replies to return, 1..200, default 50. */
|
|
318
|
+
limit?: number;
|
|
229
319
|
};
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Campaign messages (reporting)
|
|
2
|
+
|
|
3
|
+
`crm.campaignMessages` — `/v1/campaign-messages`
|
|
4
|
+
|
|
5
|
+
What actually happened to each message a campaign sent. One row per attempted
|
|
6
|
+
send: who it went to, how far it got, what came back, and why it failed.
|
|
7
|
+
|
|
8
|
+
| Action | Route | Scope |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| [`stats`](#stats) | `GET /v1/campaign-messages/stats` | `campaigns:read` |
|
|
11
|
+
| [`list`](#list) | `GET /v1/campaign-messages` | `campaigns:read` |
|
|
12
|
+
| [`iterate`](#iterate) | the same, paged for you | `campaigns:read` |
|
|
13
|
+
| [`replies`](#replies) | `GET /v1/campaign-messages/replies` | `campaigns:read` |
|
|
14
|
+
|
|
15
|
+
## Where these numbers come from
|
|
16
|
+
|
|
17
|
+
The campaign manager writes one row as it hands each message to a channel. The
|
|
18
|
+
channel's delivery webhooks — processed **outside the CRM** — then move that
|
|
19
|
+
row's `status` forward as the message is delivered, read and replied to.
|
|
20
|
+
|
|
21
|
+
So reporting is a **read of those rows, counted when you ask**. There is no
|
|
22
|
+
report collection, no roll-up job, and nothing to regenerate: no `generatedAt` to
|
|
23
|
+
reason about, and no window in which a stored number is quietly wrong because a
|
|
24
|
+
webhook has arrived but the roll-up has not run.
|
|
25
|
+
|
|
26
|
+
The flip side is that a campaign still going out has moving numbers. Reading
|
|
27
|
+
`stats` seconds after launch under-counts everything, and that is the truth
|
|
28
|
+
rather than a lag.
|
|
29
|
+
|
|
30
|
+
Nothing in this module writes. There is no endpoint to correct a status: the
|
|
31
|
+
service that owns the lifecycle is the one receiving the webhooks.
|
|
32
|
+
|
|
33
|
+
## The funnel
|
|
34
|
+
|
|
35
|
+
`status` is a **single field holding the furthest stage reached** — not a set of
|
|
36
|
+
flags. A row at `read` is also delivered and sent; a row at `replied` is all
|
|
37
|
+
three. So the counts nest:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
replied ⊆ read ⊆ delivered ⊆ sent failed ∥ (outside the funnel)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Which means:
|
|
44
|
+
|
|
45
|
+
- **Cumulative by default.** `delivered` is everything that got at least as far
|
|
46
|
+
as delivered — the number a "delivered" tile shows.
|
|
47
|
+
- **Subtract for a stage's own count.** `delivered - read` is "delivered but
|
|
48
|
+
unread"; `read - replied` is "read but silent".
|
|
49
|
+
- **`failed` is never inside `sent`.** A failed message was never sent anywhere;
|
|
50
|
+
it is counted on its own.
|
|
51
|
+
- **There is no `queued`.** A row exists only once a send has been attempted, so
|
|
52
|
+
"waiting to go out" is not a state this collection can have. The campaign's own
|
|
53
|
+
`status` is where that lives.
|
|
54
|
+
|
|
55
|
+
`total` counts every row, failures included, and is the number of attempted
|
|
56
|
+
sends.
|
|
57
|
+
|
|
58
|
+
## The row
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
interface CampaignMessageRecord {
|
|
62
|
+
id: string; // the row's own id — what a `selection` retarget takes
|
|
63
|
+
campaignId: string;
|
|
64
|
+
messageIndex: number; // which message of the campaign, zero-based
|
|
65
|
+
providerMessageId: string; // '' when the send failed
|
|
66
|
+
channelId: string;
|
|
67
|
+
contactId: string; // the recipient's contact id
|
|
68
|
+
identifier: string; // the destination: a WhatsApp number, a Telegram id…
|
|
69
|
+
name: string; // '' when the contact had no name
|
|
70
|
+
status: 'sent' | 'delivered' | 'read' | 'replied' | 'failed';
|
|
71
|
+
sentAt?: string; // set per stage reached, absent for stages not reached
|
|
72
|
+
deliveredAt?: string;
|
|
73
|
+
readAt?: string;
|
|
74
|
+
repliedAt?: string;
|
|
75
|
+
failedAt?: string;
|
|
76
|
+
reply: string; // '' unless replied
|
|
77
|
+
replyType: string; // 'text', 'button', … '' unless replied
|
|
78
|
+
repliedIn: number; // seconds between the send and the reply; 0 unless replied
|
|
79
|
+
error?: string; // failed rows only
|
|
80
|
+
createdAt: string; // when the send was attempted
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`id` is worth singling out: it is the handle a **`selection` retarget** campaign
|
|
85
|
+
takes. Collect the ids of the rows you want and the campaign manager resolves
|
|
86
|
+
them back to recipients, which is how "re-send to everyone who did not read it"
|
|
87
|
+
is built.
|
|
88
|
+
|
|
89
|
+
`CampaignMessageRecord` is not `CampaignMessage`, despite the names. A
|
|
90
|
+
`CampaignMessage` is one of the messages a campaign is *defined* to send — a
|
|
91
|
+
channel, a payload, its polls. A `CampaignMessageRecord` is a record of one of
|
|
92
|
+
those having gone to one person, and there is one per recipient per message.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## stats
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
crm.campaignMessages.stats(query: CampaignMessageStatsQuery): Promise<CampaignMessageStats>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`GET /v1/campaign-messages/stats` · `campaigns:read` · **200**
|
|
103
|
+
|
|
104
|
+
### Query
|
|
105
|
+
|
|
106
|
+
| Field | Type | Required | Notes |
|
|
107
|
+
| --- | --- | --- | --- |
|
|
108
|
+
| `campaign` | string | yes | The campaign id, as a 24-character ObjectId. |
|
|
109
|
+
| `message` | number | no | One message of a multi-message campaign, zero-based. |
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
const overall = await crm.campaignMessages.stats({ campaign });
|
|
113
|
+
// { total: 12480, sent: 12410, delivered: 11980, read: 7420, replied: 310, failed: 70 }
|
|
114
|
+
|
|
115
|
+
const second = await crm.campaignMessages.stats({ campaign, message: 1 });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**No date window**, deliberately. A campaign is a single send — its rows are
|
|
119
|
+
written over the minutes it takes to go out and then only change state — so "the
|
|
120
|
+
campaign's whole life" is the only window that means anything. A repeating
|
|
121
|
+
campaign's runs are separate campaigns, each with its own id.
|
|
122
|
+
|
|
123
|
+
One aggregation, not five counts. Legacy issued a `countDocuments` per stage,
|
|
124
|
+
which is five passes for numbers that all come from one grouping, and could
|
|
125
|
+
return a `read` higher than its own `delivered` when a webhook landed between
|
|
126
|
+
two of the passes.
|
|
127
|
+
|
|
128
|
+
`404 not_found` when the campaign is not the caller's — the same answer as a
|
|
129
|
+
campaign that does not exist, so the error cannot be used to probe whether
|
|
130
|
+
someone else's id is real.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## list
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
crm.campaignMessages.list(query: ListCampaignMessagesQuery): Promise<Paginated<CampaignMessageRecord>>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`GET /v1/campaign-messages` · `campaigns:read` · **200**
|
|
141
|
+
|
|
142
|
+
### Query
|
|
143
|
+
|
|
144
|
+
| Field | Type | Default | Notes |
|
|
145
|
+
| --- | --- | --- | --- |
|
|
146
|
+
| `campaign` | string | — | Required. |
|
|
147
|
+
| `message` | number | — | Zero-based index of one campaign message. |
|
|
148
|
+
| `status` | `sent` `delivered` `read` `replied` `failed` | — | Cumulative; see below. Omit for every row. |
|
|
149
|
+
| `exact` | boolean | `false` | Only rows sitting at `status` right now. |
|
|
150
|
+
| `identifier` | string | — | Exact match on one recipient. Not a search. |
|
|
151
|
+
| `reply_type` | string | — | Replied rows only; refused otherwise. |
|
|
152
|
+
| `replied_within_hours` | number | — | Replied rows only; refused otherwise. |
|
|
153
|
+
| `page` | number | `1` | 1-based. |
|
|
154
|
+
| `rows` | number | `25` | 1–100. |
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
// the two queries a report screen actually makes
|
|
158
|
+
const failures = await crm.campaignMessages.list({ campaign, status: 'failed', rows: 100 });
|
|
159
|
+
failures.data[0].error; // why that one did not go
|
|
160
|
+
|
|
161
|
+
const unread = await crm.campaignMessages.list({ campaign, status: 'delivered', exact: true });
|
|
162
|
+
unread.pagination.total; // equals stats.delivered - stats.read
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### `status` and `exact`
|
|
166
|
+
|
|
167
|
+
`status` filters **cumulatively**, so a drill-down returns exactly the rows the
|
|
168
|
+
matching stat counted: `status: 'delivered'` gives you the `delivered` number's
|
|
169
|
+
rows. `exact: true` switches to the single stage — "delivered but never read" —
|
|
170
|
+
which is the drill-down for `delivered - read`.
|
|
171
|
+
|
|
172
|
+
`exact` has no effect without `status`, and none on `failed`, which has nothing
|
|
173
|
+
beyond it.
|
|
174
|
+
|
|
175
|
+
### Reply filters
|
|
176
|
+
|
|
177
|
+
`reply_type` and `replied_within_hours` are **refused** with
|
|
178
|
+
`400 validation_failed` unless `status` is `replied`. They cannot match anything
|
|
179
|
+
on a non-replied row, and a filter that cannot match is a caller mistake worth
|
|
180
|
+
reporting rather than an empty page.
|
|
181
|
+
|
|
182
|
+
`replied_within_hours: 2` means the reply arrived within two hours of the send —
|
|
183
|
+
useful for separating people who answered immediately from people who came back
|
|
184
|
+
the next day.
|
|
185
|
+
|
|
186
|
+
### Ordering
|
|
187
|
+
|
|
188
|
+
Newest attempt first, by when the row was **written**, not by a stage timestamp.
|
|
189
|
+
Sorting on `deliveredAt` would reorder the list as webhooks arrive, and offset
|
|
190
|
+
paging over a list that reorders itself skips and repeats rows.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## iterate
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
crm.campaignMessages.iterate(query: ListCampaignMessagesQuery): AsyncGenerator<CampaignMessageRecord>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Every matching row, paged through for you. **This is what an export is** — there
|
|
201
|
+
is no export endpoint, nothing to queue, nothing to poll, and no generated file
|
|
202
|
+
that expires before someone downloads it.
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
for await (const row of crm.campaignMessages.iterate({ campaign })) {
|
|
206
|
+
csv.write([row.identifier, row.name, row.status, row.error ?? '']);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Pages at 100 rows unless you pass `rows` — the server's maximum, and so the
|
|
211
|
+
fewest round trips. A million recipients is ten thousand requests, so run an
|
|
212
|
+
export that size somewhere with low latency to the gateway rather than from a
|
|
213
|
+
browser tab.
|
|
214
|
+
|
|
215
|
+
The collection is live. Rows are written while a campaign goes out and statuses
|
|
216
|
+
change under an export that takes minutes, and offset paging over moving data can
|
|
217
|
+
repeat or miss a row. For a stable snapshot, export after the campaign completes.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## replies
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
crm.campaignMessages.replies(query: CampaignRepliesQuery): Promise<CampaignReplyBreakdown>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`GET /v1/campaign-messages/replies` · `campaigns:read` · **200**
|
|
228
|
+
|
|
229
|
+
### Query
|
|
230
|
+
|
|
231
|
+
| Field | Type | Default | Notes |
|
|
232
|
+
| --- | --- | --- | --- |
|
|
233
|
+
| `campaign` | string | — | Required. |
|
|
234
|
+
| `message` | number | — | Zero-based index of one campaign message. |
|
|
235
|
+
| `limit` | number | `50` | Distinct replies to return, 1–200. |
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
const { groups, replied, distinct } = await crm.campaignMessages.replies({ campaign });
|
|
239
|
+
|
|
240
|
+
groups; // [{ reply: 'Yes', replyType: 'button', count: 182 }, { reply: 'No', … }]
|
|
241
|
+
replied; // 310 — every replied row, whether or not its group is listed
|
|
242
|
+
distinct; // 4 — distinct replies in total
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
What a poll or a button message is sent to find out. Grouped on the reply **and**
|
|
246
|
+
its type, so a tapped `Yes` and a typed `Yes` stay apart — they say different
|
|
247
|
+
things about how engaged the recipient was. Most common first, ties broken by the
|
|
248
|
+
reply text so the order is stable between reads.
|
|
249
|
+
|
|
250
|
+
`distinct` against `groups.length` tells you whether the breakdown is complete.
|
|
251
|
+
A campaign that asked an open question has as many groups as it has repliers, so
|
|
252
|
+
a truncated answer there is normal rather than a problem — raise `limit`, or
|
|
253
|
+
treat the tail as "other".
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Operational note: the index this needs
|
|
258
|
+
|
|
259
|
+
Every read here matches on the campaign first, and the collection — which belongs
|
|
260
|
+
to the campaign manager — declares no index on it. Without one, each report is a
|
|
261
|
+
scan of every campaign message ever sent:
|
|
262
|
+
|
|
263
|
+
```js
|
|
264
|
+
db.newcampaignmessages.createIndex(
|
|
265
|
+
{ cid: 1, cmid: 1, status: 1 },
|
|
266
|
+
{ name: 'Campaign Report', background: true },
|
|
267
|
+
);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
One compound index serves all three reads. This service deliberately does **not**
|
|
271
|
+
create it: building an index on a multi-million row collection owned by another
|
|
272
|
+
service, as a side effect of a process starting, is not something that should
|
|
273
|
+
happen by accident.
|
|
@@ -434,56 +434,16 @@ await crm.campaigns.delete(id);
|
|
|
434
434
|
|
|
435
435
|
---
|
|
436
436
|
|
|
437
|
-
#
|
|
437
|
+
# Reporting
|
|
438
438
|
|
|
439
|
-
|
|
439
|
+
Campaign reporting lives on its own page: **[campaign messages](campaign-messages.md)**
|
|
440
|
+
— `crm.campaignMessages`.
|
|
440
441
|
|
|
441
|
-
|
|
442
|
+
There is no report collection and no export job. One row is written per attempted
|
|
443
|
+
send, its status is advanced by the channel's delivery webhooks, and every number
|
|
444
|
+
is counted off those rows when you ask for it:
|
|
442
445
|
|
|
443
446
|
```ts
|
|
444
|
-
const
|
|
445
|
-
|
|
447
|
+
const stats = await crm.campaignMessages.stats({ campaign: id });
|
|
448
|
+
// { total, sent, delivered, read, replied, failed }
|
|
446
449
|
```
|
|
447
|
-
|
|
448
|
-
| Action | Route |
|
|
449
|
-
| --- | --- |
|
|
450
|
-
| `list` | `GET /v1/campaign-reports` — cursor-paged, `campaignId` optional |
|
|
451
|
-
| `getByCampaign` | `GET /v1/campaign-reports/:campaignId` |
|
|
452
|
-
|
|
453
|
-
`getByCampaign` takes an optional window; without it the report covers the
|
|
454
|
-
campaign's whole life:
|
|
455
|
-
|
|
456
|
-
```ts
|
|
457
|
-
await crm.campaignReports.getByCampaign(campaignId, {
|
|
458
|
-
from: '2026-10-01T00:00:00.000Z',
|
|
459
|
-
to: '2026-10-31T23:59:59.000Z',
|
|
460
|
-
});
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
`generatedAt` is when the metrics were last rolled up, which may lag the
|
|
464
|
-
campaign — a report read seconds after a send will under-count.
|
|
465
|
-
|
|
466
|
-
## Report exports
|
|
467
|
-
|
|
468
|
-
`crm.campaignReportExports` — `/v1/campaign-report-exports`
|
|
469
|
-
|
|
470
|
-
A per-recipient extract, generated asynchronously because it is a file rather
|
|
471
|
-
than a few counters.
|
|
472
|
-
|
|
473
|
-
```ts
|
|
474
|
-
const job = await crm.campaignReportExports.create({ campaignId, format: 'xlsx' });
|
|
475
|
-
|
|
476
|
-
// Poll until it finishes, then download.
|
|
477
|
-
const done = await crm.campaignReportExports.get(job.id);
|
|
478
|
-
if (done.status === 'completed') window.location.href = done.fileUrl!;
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
| Field | Type | Notes |
|
|
482
|
-
| --- | --- | --- |
|
|
483
|
-
| `campaignId` | `string` | required |
|
|
484
|
-
| `format` | `csv` `xlsx` | default `csv` |
|
|
485
|
-
| `from` / `to` | ISO datetime | narrows the rows by period |
|
|
486
|
-
|
|
487
|
-
`status` moves `queued` → `processing` → `completed` or `failed`; `rowCount` is
|
|
488
|
-
filled in as it goes, and `errorMessage` explains a failure. **`fileUrl` expires**
|
|
489
|
-
— download it rather than storing the link.
|
package/package.json
CHANGED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { CampaignReportExport, CreateCampaignReportExportInput } from '../types/campaign';
|
|
2
|
-
import type { Page, PageQuery } from '../types/common';
|
|
3
|
-
import type { HttpClient } from '../http';
|
|
4
|
-
export declare class CampaignReportExports {
|
|
5
|
-
private readonly http;
|
|
6
|
-
constructor(http: HttpClient);
|
|
7
|
-
list(query?: Partial<PageQuery>): Promise<Page<CampaignReportExport>>;
|
|
8
|
-
/**
|
|
9
|
-
* One export. Poll this until `status` is `completed`, then read `fileUrl` --
|
|
10
|
-
* which expires, so download rather than storing the link.
|
|
11
|
-
*/
|
|
12
|
-
get(id: string): Promise<CampaignReportExport>;
|
|
13
|
-
create(input: CreateCampaignReportExportInput): Promise<CampaignReportExport>;
|
|
14
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CampaignReportExports = void 0;
|
|
4
|
-
class CampaignReportExports {
|
|
5
|
-
http;
|
|
6
|
-
constructor(http) {
|
|
7
|
-
this.http = http;
|
|
8
|
-
}
|
|
9
|
-
list(query = {}) {
|
|
10
|
-
return this.http.request('GET', '/v1/campaign-report-exports', {
|
|
11
|
-
query,
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* One export. Poll this until `status` is `completed`, then read `fileUrl` --
|
|
16
|
-
* which expires, so download rather than storing the link.
|
|
17
|
-
*/
|
|
18
|
-
get(id) {
|
|
19
|
-
return this.http.request('GET', `/v1/campaign-report-exports/${encodeURIComponent(id)}`);
|
|
20
|
-
}
|
|
21
|
-
create(input) {
|
|
22
|
-
return this.http.request('POST', '/v1/campaign-report-exports', {
|
|
23
|
-
body: input,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
exports.CampaignReportExports = CampaignReportExports;
|
|
28
|
-
//# sourceMappingURL=campaign-report-exports.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"campaign-report-exports.js","sourceRoot":"","sources":["../../src/resources/campaign-report-exports.ts"],"names":[],"mappings":";;;AAIA,MAAa,qBAAqB;IACH;IAA7B,YAA6B,IAAgB;QAAhB,SAAI,GAAJ,IAAI,CAAY;IAAG,CAAC;IAEjD,IAAI,CAAC,QAA4B,EAAE;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAA6B,KAAK,EAAE,6BAA6B,EAAE;YACzF,KAAK;SACN,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CACtB,KAAK,EACL,+BAA+B,kBAAkB,CAAC,EAAE,CAAC,EAAE,CACxD,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,KAAsC;QAC3C,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAuB,MAAM,EAAE,6BAA6B,EAAE;YACpF,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;IACL,CAAC;CACF;AAzBD,sDAyBC"}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { CampaignReport, GetCampaignReportQuery } from '../types/campaign';
|
|
2
|
-
import type { Page, PageQuery } from '../types/common';
|
|
3
|
-
import type { HttpClient } from '../http';
|
|
4
|
-
export declare class CampaignReports {
|
|
5
|
-
private readonly http;
|
|
6
|
-
constructor(http: HttpClient);
|
|
7
|
-
list(query?: Partial<PageQuery>): Promise<Page<CampaignReport>>;
|
|
8
|
-
/**
|
|
9
|
-
* The report for one campaign.
|
|
10
|
-
*
|
|
11
|
-
* `from`/`to` narrow the window the metrics are rolled up over; without them
|
|
12
|
-
* the report covers the campaign's whole life.
|
|
13
|
-
*/
|
|
14
|
-
getByCampaign(campaignId: string, query?: GetCampaignReportQuery): Promise<CampaignReport>;
|
|
15
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CampaignReports = void 0;
|
|
4
|
-
class CampaignReports {
|
|
5
|
-
http;
|
|
6
|
-
constructor(http) {
|
|
7
|
-
this.http = http;
|
|
8
|
-
}
|
|
9
|
-
list(query = {}) {
|
|
10
|
-
return this.http.request('GET', '/v1/campaign-reports', { query });
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* The report for one campaign.
|
|
14
|
-
*
|
|
15
|
-
* `from`/`to` narrow the window the metrics are rolled up over; without them
|
|
16
|
-
* the report covers the campaign's whole life.
|
|
17
|
-
*/
|
|
18
|
-
getByCampaign(campaignId, query = {}) {
|
|
19
|
-
return this.http.request('GET', `/v1/campaign-reports/${encodeURIComponent(campaignId)}`, {
|
|
20
|
-
query,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
exports.CampaignReports = CampaignReports;
|
|
25
|
-
//# sourceMappingURL=campaign-reports.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"campaign-reports.js","sourceRoot":"","sources":["../../src/resources/campaign-reports.ts"],"names":[],"mappings":";;;AAIA,MAAa,eAAe;IACG;IAA7B,YAA6B,IAAgB;QAAhB,SAAI,GAAJ,IAAI,CAAY;IAAG,CAAC;IAEjD,IAAI,CAAC,QAA4B,EAAE;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAuB,KAAK,EAAE,sBAAsB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED;;;;;OAKG;IACH,aAAa,CAAC,UAAkB,EAAE,QAAgC,EAAE;QAClE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAiB,KAAK,EAAE,wBAAwB,kBAAkB,CAAC,UAAU,CAAC,EAAE,EAAE;YACxG,KAAK;SACN,CAAC,CAAC;IACL,CAAC;CACF;AAlBD,0CAkBC"}
|