@aglyn/plugins-marketing 1.0.0-beta.143

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 (183) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +56 -0
  4. package/src/index.d.ts +19 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/announcement-bar-card.component.d.ts +20 -0
  8. package/src/lib/components/announcement-bar-card.component.js +292 -0
  9. package/src/lib/components/announcement-bar-card.component.js.map +1 -0
  10. package/src/lib/components/campaign-composer.d.ts +84 -0
  11. package/src/lib/components/campaign-composer.js +1643 -0
  12. package/src/lib/components/campaign-composer.js.map +1 -0
  13. package/src/lib/components/campaign-conversions-card.d.ts +22 -0
  14. package/src/lib/components/campaign-conversions-card.js +642 -0
  15. package/src/lib/components/campaign-conversions-card.js.map +1 -0
  16. package/src/lib/components/campaign-detail-card.d.ts +55 -0
  17. package/src/lib/components/campaign-detail-card.js +771 -0
  18. package/src/lib/components/campaign-detail-card.js.map +1 -0
  19. package/src/lib/components/campaign-edit-drawer.d.ts +60 -0
  20. package/src/lib/components/campaign-edit-drawer.js +252 -0
  21. package/src/lib/components/campaign-edit-drawer.js.map +1 -0
  22. package/src/lib/components/campaign-email-zones.d.ts +111 -0
  23. package/src/lib/components/campaign-email-zones.js +54 -0
  24. package/src/lib/components/campaign-email-zones.js.map +1 -0
  25. package/src/lib/components/campaign-glance-card.component.d.ts +18 -0
  26. package/src/lib/components/campaign-glance-card.component.js +151 -0
  27. package/src/lib/components/campaign-glance-card.component.js.map +1 -0
  28. package/src/lib/components/campaign-members-section.d.ts +87 -0
  29. package/src/lib/components/campaign-members-section.js +499 -0
  30. package/src/lib/components/campaign-members-section.js.map +1 -0
  31. package/src/lib/components/campaign-reach-sections.d.ts +137 -0
  32. package/src/lib/components/campaign-reach-sections.js +694 -0
  33. package/src/lib/components/campaign-reach-sections.js.map +1 -0
  34. package/src/lib/components/campaign-report-card.d.ts +91 -0
  35. package/src/lib/components/campaign-report-card.js +667 -0
  36. package/src/lib/components/campaign-report-card.js.map +1 -0
  37. package/src/lib/components/campaign-test-send-drawer.d.ts +45 -0
  38. package/src/lib/components/campaign-test-send-drawer.js +238 -0
  39. package/src/lib/components/campaign-test-send-drawer.js.map +1 -0
  40. package/src/lib/components/campaigns-card.d.ts +31 -0
  41. package/src/lib/components/campaigns-card.js +667 -0
  42. package/src/lib/components/campaigns-card.js.map +1 -0
  43. package/src/lib/components/conversion-attribution.component.d.ts +62 -0
  44. package/src/lib/components/conversion-attribution.component.js +127 -0
  45. package/src/lib/components/conversion-attribution.component.js.map +1 -0
  46. package/src/lib/components/email-compose-card.d.ts +12 -0
  47. package/src/lib/components/email-compose-card.js +157 -0
  48. package/src/lib/components/email-compose-card.js.map +1 -0
  49. package/src/lib/components/email-detail.d.ts +40 -0
  50. package/src/lib/components/email-detail.js +1180 -0
  51. package/src/lib/components/email-detail.js.map +1 -0
  52. package/src/lib/components/email-edit-drawer.d.ts +54 -0
  53. package/src/lib/components/email-edit-drawer.js +172 -0
  54. package/src/lib/components/email-edit-drawer.js.map +1 -0
  55. package/src/lib/components/email-messages-widget.d.ts +22 -0
  56. package/src/lib/components/email-messages-widget.js +49 -0
  57. package/src/lib/components/email-messages-widget.js.map +1 -0
  58. package/src/lib/components/email-recipients-card.d.ts +50 -0
  59. package/src/lib/components/email-recipients-card.js +338 -0
  60. package/src/lib/components/email-recipients-card.js.map +1 -0
  61. package/src/lib/components/emails-list-card.d.ts +36 -0
  62. package/src/lib/components/emails-list-card.js +544 -0
  63. package/src/lib/components/emails-list-card.js.map +1 -0
  64. package/src/lib/components/experiment-zones.d.ts +78 -0
  65. package/src/lib/components/experiment-zones.js +65 -0
  66. package/src/lib/components/experiment-zones.js.map +1 -0
  67. package/src/lib/components/host-experiments-card.component.d.ts +17 -0
  68. package/src/lib/components/host-experiments-card.component.js +887 -0
  69. package/src/lib/components/host-experiments-card.component.js.map +1 -0
  70. package/src/lib/components/host-marketing-summary-card.component.d.ts +15 -0
  71. package/src/lib/components/host-marketing-summary-card.component.js +161 -0
  72. package/src/lib/components/host-marketing-summary-card.component.js.map +1 -0
  73. package/src/lib/components/host-overlays-card.component.d.ts +19 -0
  74. package/src/lib/components/host-overlays-card.component.js +783 -0
  75. package/src/lib/components/host-overlays-card.component.js.map +1 -0
  76. package/src/lib/components/marketing-console-page.d.ts +28 -0
  77. package/src/lib/components/marketing-console-page.js +166 -0
  78. package/src/lib/components/marketing-console-page.js.map +1 -0
  79. package/src/lib/components/marketing-console-sections.d.ts +34 -0
  80. package/src/lib/components/marketing-console-sections.js +77 -0
  81. package/src/lib/components/marketing-console-sections.js.map +1 -0
  82. package/src/lib/components/overlay-stats-row.component.d.ts +15 -0
  83. package/src/lib/components/overlay-stats-row.component.js +78 -0
  84. package/src/lib/components/overlay-stats-row.component.js.map +1 -0
  85. package/src/lib/components/popup-card.component.d.ts +18 -0
  86. package/src/lib/components/popup-card.component.js +462 -0
  87. package/src/lib/components/popup-card.component.js.map +1 -0
  88. package/src/lib/components/record-attribution-widget.d.ts +35 -0
  89. package/src/lib/components/record-attribution-widget.js +33 -0
  90. package/src/lib/components/record-attribution-widget.js.map +1 -0
  91. package/src/lib/components/site-runtime.d.ts +9 -0
  92. package/src/lib/components/site-runtime.js +1081 -0
  93. package/src/lib/components/site-runtime.js.map +1 -0
  94. package/src/lib/components/use-campaign-send-api.d.ts +35 -0
  95. package/src/lib/components/use-campaign-send-api.js +99 -0
  96. package/src/lib/components/use-campaign-send-api.js.map +1 -0
  97. package/src/lib/components/use-campaign-topic-options.d.ts +20 -0
  98. package/src/lib/components/use-campaign-topic-options.js +44 -0
  99. package/src/lib/components/use-campaign-topic-options.js.map +1 -0
  100. package/src/lib/components/use-emails-hub-path.d.ts +39 -0
  101. package/src/lib/components/use-emails-hub-path.js +70 -0
  102. package/src/lib/components/use-emails-hub-path.js.map +1 -0
  103. package/src/lib/components/use-marketing-hub-path.d.ts +21 -0
  104. package/src/lib/components/use-marketing-hub-path.js +50 -0
  105. package/src/lib/components/use-marketing-hub-path.js.map +1 -0
  106. package/src/lib/components/use-overlay-copy-editor.d.ts +79 -0
  107. package/src/lib/components/use-overlay-copy-editor.js +110 -0
  108. package/src/lib/components/use-overlay-copy-editor.js.map +1 -0
  109. package/src/lib/constants/bundle-common.d.ts +8 -0
  110. package/src/lib/constants/bundle-common.js +9 -0
  111. package/src/lib/constants/bundle-common.js.map +1 -0
  112. package/src/lib/model/campaign-membership-figures.d.ts +109 -0
  113. package/src/lib/model/campaign-membership-figures.js +113 -0
  114. package/src/lib/model/campaign-membership-figures.js.map +1 -0
  115. package/src/lib/model/compile-client-automations.d.ts +65 -0
  116. package/src/lib/model/compile-client-automations.js +95 -0
  117. package/src/lib/model/compile-client-automations.js.map +1 -0
  118. package/src/lib/model/experiments.d.ts +130 -0
  119. package/src/lib/model/experiments.js +186 -0
  120. package/src/lib/model/experiments.js.map +1 -0
  121. package/src/lib/model/index.d.ts +26 -0
  122. package/src/lib/model/index.js +26 -0
  123. package/src/lib/model/index.js.map +1 -0
  124. package/src/lib/model/marketing-record-routes.d.ts +6 -0
  125. package/src/lib/model/marketing-record-routes.js +46 -0
  126. package/src/lib/model/marketing-record-routes.js.map +1 -0
  127. package/src/lib/model/overlay-copy.d.ts +62 -0
  128. package/src/lib/model/overlay-copy.js +78 -0
  129. package/src/lib/model/overlay-copy.js.map +1 -0
  130. package/src/lib/model/overlays.d.ts +113 -0
  131. package/src/lib/model/overlays.js +93 -0
  132. package/src/lib/model/overlays.js.map +1 -0
  133. package/src/lib/model/site-contract.d.ts +100 -0
  134. package/src/lib/model/site-contract.js +23 -0
  135. package/src/lib/model/site-contract.js.map +1 -0
  136. package/src/lib/plugin.d.ts +27 -0
  137. package/src/lib/plugin.js +168 -0
  138. package/src/lib/plugin.js.map +1 -0
  139. package/src/lib/preview-props.d.ts +31 -0
  140. package/src/lib/preview-props.js +54 -0
  141. package/src/lib/preview-props.js.map +1 -0
  142. package/src/lib/server/campaign-manage.d.ts +62 -0
  143. package/src/lib/server/campaign-manage.js +829 -0
  144. package/src/lib/server/campaign-manage.js.map +1 -0
  145. package/src/lib/server/campaign-process-scheduled.d.ts +26 -0
  146. package/src/lib/server/campaign-process-scheduled.js +198 -0
  147. package/src/lib/server/campaign-process-scheduled.js.map +1 -0
  148. package/src/lib/server/campaign-recipients.d.ts +55 -0
  149. package/src/lib/server/campaign-recipients.js +219 -0
  150. package/src/lib/server/campaign-recipients.js.map +1 -0
  151. package/src/lib/server/campaign-send.d.ts +487 -0
  152. package/src/lib/server/campaign-send.js +3157 -0
  153. package/src/lib/server/campaign-send.js.map +1 -0
  154. package/src/lib/server/email-campaign-reach.d.ts +174 -0
  155. package/src/lib/server/email-campaign-reach.js +254 -0
  156. package/src/lib/server/email-campaign-reach.js.map +1 -0
  157. package/src/lib/server/email-events.d.ts +27 -0
  158. package/src/lib/server/email-events.js +714 -0
  159. package/src/lib/server/email-events.js.map +1 -0
  160. package/src/lib/server/get-client-automations.d.ts +60 -0
  161. package/src/lib/server/get-client-automations.js +87 -0
  162. package/src/lib/server/get-client-automations.js.map +1 -0
  163. package/src/lib/server/get-overlays.d.ts +28 -0
  164. package/src/lib/server/get-overlays.js +65 -0
  165. package/src/lib/server/get-overlays.js.map +1 -0
  166. package/src/lib/server/get-screen-experiments.d.ts +42 -0
  167. package/src/lib/server/get-screen-experiments.js +82 -0
  168. package/src/lib/server/get-screen-experiments.js.map +1 -0
  169. package/src/lib/server/lists-materialize.d.ts +18 -0
  170. package/src/lib/server/lists-materialize.js +130 -0
  171. package/src/lib/server/lists-materialize.js.map +1 -0
  172. package/src/lib/server/marketing-figures.d.ts +37 -0
  173. package/src/lib/server/marketing-figures.js +248 -0
  174. package/src/lib/server/marketing-figures.js.map +1 -0
  175. package/src/lib/server/site-page-enricher.d.ts +26 -0
  176. package/src/lib/server/site-page-enricher.js +210 -0
  177. package/src/lib/server/site-page-enricher.js.map +1 -0
  178. package/src/lib/server.d.ts +20 -0
  179. package/src/lib/server.js +152 -0
  180. package/src/lib/server.js.map +1 -0
  181. package/src/lib/site.d.ts +22 -0
  182. package/src/lib/site.js +48 -0
  183. package/src/lib/site.js.map +1 -0
@@ -0,0 +1,487 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type PluginApiHandler } from '@aglyn/aglyn/server';
18
+ import { type SendingIdentitySource } from '@aglyn/shared-util-email';
19
+ /**
20
+ * Stable doc id for a suppression entry (emails are PII — hash them).
21
+ *
22
+ * A WRITER's derivation now — `email-events.ts` files bounces and complaints
23
+ * under it. The send path reads through `emailSuppressionKey`, which hashes
24
+ * the same trimmed, lowercased form and additionally refuses to guess an id
25
+ * for a value that is not an address.
26
+ */
27
+ export declare function suppressionId(email: string): string;
28
+ /** HMAC for unsubscribe links; env-gated on the shared secret. */
29
+ export declare function unsubscribeSignature(hostId: string, email: string, secret: string, campaignId?: string, topicId?: string): string;
30
+ /** Send failures carry the HTTP status the API route should answer. */
31
+ export declare class CampaignSendError extends Error {
32
+ readonly status: number;
33
+ constructor(message: string, status: number);
34
+ }
35
+ /**
36
+ * The platform send rate had no room for this campaign THIS HOUR (AGL-2409).
37
+ *
38
+ * A subclass rather than a status code the caller has to recognise, because
39
+ * the difference it carries is not "which HTTP number" — it is **retry, do not
40
+ * fail**. A scheduled campaign that trips the hourly ceiling must go back to
41
+ * `scheduled` and be picked up by the next 15-minute run; marking it `failed`,
42
+ * which is what every other `CampaignSendError` correctly does, would turn a
43
+ * ramp into a lost campaign that a merchant has to notice and re-create.
44
+ *
45
+ * Thrown ONLY before anything has been sent, so a caller that retries cannot
46
+ * double-send.
47
+ */
48
+ export declare class CampaignSendDeferredError extends CampaignSendError {
49
+ /** When the hourly window rolls. */
50
+ readonly retryAtMs: number;
51
+ constructor(message: string,
52
+ /** When the hourly window rolls. */
53
+ retryAtMs: number);
54
+ }
55
+ export interface CampaignSendOptions {
56
+ hostId: string;
57
+ subject: string;
58
+ /**
59
+ * The MESSAGE of a plain-text email — what recipients read, and what the
60
+ * HTML part is synthesized from.
61
+ *
62
+ * Not read on a designed send, where the nodes are the message. The route
63
+ * refuses a request carrying both rather than accepting one and dropping it,
64
+ * which is what it used to do.
65
+ */
66
+ body: string;
67
+ audience: string;
68
+ segmentId?: string;
69
+ listId?: string;
70
+ /**
71
+ * The stream this campaign belongs to, chosen in the composer.
72
+ *
73
+ * Resolved to {@link DEFAULT_CAMPAIGN_TOPIC_ID} when absent, so every send
74
+ * belongs to some topic: a campaign with none would mint an unsubscribe link
75
+ * the preference page can render but not place, offering the recipient a
76
+ * catalog without saying which entry the message in front of them was.
77
+ */
78
+ topicId?: string;
79
+ /**
80
+ * Resolve the audience and return the count WITHOUT sending anything
81
+ * (AGL-2178). Returns before the first write, so it mints no campaign
82
+ * id and touches no counter.
83
+ */
84
+ dryRun?: boolean;
85
+ emails?: string[];
86
+ campaignId?: string;
87
+ experimentId?: string;
88
+ /**
89
+ * Designed email template (AGL-349): screen id of a besigner email
90
+ * document, which is what makes this a DESIGNED send.
91
+ *
92
+ * The nodes produce both parts an inbox receives — the HTML and a plain-text
93
+ * rendering of the same design — so `body` has no job here and is not read.
94
+ * `plainText` below is the one thing an author may substitute.
95
+ */
96
+ templateScreenId?: string;
97
+ /**
98
+ * The author's own PLAIN-TEXT PART for a designed email.
99
+ *
100
+ * Empty means the design generates it, which is the default and what every
101
+ * designed campaign did before this existed. Read only on the designed
102
+ * path: a plain-text campaign's text part IS its body, and a second string
103
+ * claiming to be the text part beside it would be the two-sources problem
104
+ * again under a new name.
105
+ */
106
+ plainText?: string;
107
+ /**
108
+ * The sender's DISPLAY NAME for this campaign, overriding the org's
109
+ * branding default.
110
+ *
111
+ * A display name and nothing more: `applyFromName` keeps the verified
112
+ * address it is applied to, so this cannot move the mail onto a domain the
113
+ * org has not proved. The route strips control characters before it gets
114
+ * here, because the value is merchant-typed and lands in a header.
115
+ */
116
+ fromName?: string;
117
+ /** Where replies go, when it is not the sending address. */
118
+ replyTo?: string;
119
+ /**
120
+ * WHICH OF THE SITE'S SENDERS THIS EMAIL LEAVES AS —
121
+ * `hosts/{hostId}/senders/{senderId}`.
122
+ *
123
+ * The mailbox, and only the mailbox. A sender also carries a display name
124
+ * and a reply address, and those stay per-send fields resolved from
125
+ * {@link fromName} and {@link replyTo}: the composer seeds them from the
126
+ * chosen sender so a merchant sees what will go out, and the send records
127
+ * what was submitted, which is what keeps the stored report and the message
128
+ * one answer rather than two.
129
+ *
130
+ * ## An id, which is not the input path that was closed
131
+ *
132
+ * `req.body.sendingIdentity` is read by nothing, and a per-send local part
133
+ * would reopen it: a mailbox is where a bounce returns and where a client
134
+ * ignoring `Reply-To:` answers, so one that exists in a single campaign's
135
+ * headers is an address nobody serves. A sender id names a mailbox that was
136
+ * configured once, validated once and stored on this site under an
137
+ * `org.settings` gate. The set of addresses this field can reach is the set
138
+ * an org admin already approved.
139
+ *
140
+ * Absent means the site's DEFAULT sender, which is the projection the host
141
+ * document has always carried. Naming a sender this site does not hold is
142
+ * REFUSED — see the resolution below — rather than falling back to it.
143
+ */
144
+ senderId?: string;
145
+ /**
146
+ * The CAMPAIGN this send belongs to — `hosts/{hostId}/emailCampaigns/{id}`.
147
+ *
148
+ * Not the send's own id, which is what `campaignId` means here and what
149
+ * every delivered unsubscribe link carries as `cid`. Absent on a send
150
+ * composed outside a campaign, and on every send that predates containers;
151
+ * the campaigns list adopts those as a campaign of one at read time rather
152
+ * than rewriting them.
153
+ */
154
+ emailCampaignId?: string;
155
+ /**
156
+ * The preview line inboxes show after the subject. Overrides a designed
157
+ * template's own, and gives a plain-text campaign one at all.
158
+ */
159
+ preheader?: string;
160
+ /**
161
+ * What the merchant called this email, for the record rather than for the
162
+ * recipient.
163
+ *
164
+ * Never leaves the console — it is not the subject and reaches no header,
165
+ * so it is stored as typed apart from a length cap. Absent on every send
166
+ * composed from a campaign, where the campaign is what carries the name.
167
+ */
168
+ displayName?: string;
169
+ /** Test sends (AGL-349) skip the campaign record and stats. */
170
+ recordCampaign?: boolean;
171
+ /**
172
+ * SEND THIS EMAIL AGAIN, TO PEOPLE IT HAS NOT REACHED.
173
+ *
174
+ * Set with a `campaignId` naming a send that has already gone out. The send
175
+ * is not copied and no second document is made: this send addresses the
176
+ * same audience under the same id, minus everyone the earlier sends
177
+ * reached, and ADDS to the counters already on the record.
178
+ *
179
+ * ## Why the same document, rather than a new send
180
+ *
181
+ * Two reasons, and both are properties a copy would break.
182
+ *
183
+ * The unsubscribe link is the first. Every message this email has already
184
+ * delivered carries `cid={campaignId}` inside its own HMAC, and those
185
+ * messages sit in inboxes forever. A follow-up under a new id would be a
186
+ * second `cid` for one email — two opt-out scopes for one mailing, and an
187
+ * unsubscribe rate split across two records neither of which is the answer.
188
+ *
189
+ * The report is the second. Opens and clicks are attributed by the
190
+ * `campaignId` tag on the delivered message, so a copy would collect the
191
+ * follow-up's engagement on a document whose `sent` counts only the
192
+ * follow-up. Keeping one document keeps every rate over a denominator that
193
+ * covers the same mail the numerator does — see the additive write at the
194
+ * bottom of this function for the arithmetic that holds it.
195
+ *
196
+ * ## What it does NOT relax
197
+ *
198
+ * Nothing. The follow-up runs the whole of this function: the consent
199
+ * split, both suppression lists, the topic filter, the monthly pre-check
200
+ * and reservation, the platform and per-workspace hourly ceilings, and the
201
+ * per-message governor. Its only additions are the subtraction below and
202
+ * the shape of the write at the end.
203
+ */
204
+ followUp?: boolean;
205
+ /**
206
+ * THE NEXT BATCH OF AN EMAIL THAT IS STILL GOING OUT.
207
+ *
208
+ * Set by the scheduled-campaign processor, never by a request. An audience
209
+ * larger than {@link EMAIL_MAX_RECIPIENTS_PER_SEND} is delivered across
210
+ * several invocations; each one addresses the people the earlier ones did
211
+ * not, and the campaign is written back as `scheduled` until nothing is
212
+ * left. See the batch plan at the bottom of this function.
213
+ *
214
+ * ## Why it is not {@link followUp}
215
+ *
216
+ * They share the subtraction and the additive write, and they differ on the
217
+ * two things that matter. A follow-up is a MERCHANT's act on an email that
218
+ * has finished — it subtracts who was reached, and somebody suppressed at
219
+ * the time who has since been released is entitled to get it. A batch is
220
+ * one email still in flight — it subtracts everyone the email has SETTLED,
221
+ * reached or refused, because a refused address sitting at the head of a
222
+ * stable order would consume a slot in every remaining batch and, in
223
+ * enough numbers, stop the campaign dead having mailed nobody.
224
+ *
225
+ * They also record differently. A follow-up measures a NEW population, so
226
+ * the audience size and the consent split add. A batch measures a slice of
227
+ * the population the first batch already measured, so those figures are
228
+ * left exactly as the first batch wrote them.
229
+ */
230
+ continuation?: boolean;
231
+ /**
232
+ * ONE address the caller has been proved to be entitled to proof to, for
233
+ * the composer's test send. Exempts exactly this address from the
234
+ * marketing-consent rule.
235
+ *
236
+ * The route decides entitlement and this file decides what the exemption
237
+ * can do. See {@link eligibleProofAddress} for the first half and the
238
+ * carve-out at the consent join for the second — including the two
239
+ * properties that keep the exemption one address wide however the caller
240
+ * arrived at it.
241
+ */
242
+ proofFor?: string;
243
+ /**
244
+ * Test send only: render every message as if it were addressed to this
245
+ * person, while DELIVERING to the recipient resolved in the ordinary way.
246
+ *
247
+ * What a merge tag resolves to is the whole question a proof answers. A
248
+ * test rendered against the tester shows `{{firstName|there}}` falling back
249
+ * for an audience whose contacts all have names, which reports the merge
250
+ * tags as broken when they work — and, more expensively, hides the reverse.
251
+ *
252
+ * It changes the RENDER and nothing else. The unsubscribe link, the
253
+ * `List-Unsubscribe` header and the suppression key are all minted from the
254
+ * delivery address, deliberately: a proof carrying the persona's opt-out
255
+ * link would let the person testing the email unsubscribe a real contact by
256
+ * clicking a link in their own inbox.
257
+ */
258
+ proofPersona?: {
259
+ email: string;
260
+ name?: string;
261
+ };
262
+ /** Recorded as `sentBy`; the scheduler passes the scheduling user. */
263
+ senderUid: string;
264
+ }
265
+ /**
266
+ * Campaign delivery core (AGL-161, extracted for AGL-272): resolves the
267
+ * audience server-side, drops suppressed addresses, enforces the plan's
268
+ * monthly send cap, personalizes merge tags per recipient, and delivers
269
+ * through Resend with a signed unsubscribe link. Shared by the
270
+ * authenticated send route and the scheduled-campaign processor. The
271
+ * caller owns authorization.
272
+ */
273
+ export interface CampaignSendResult {
274
+ campaignId: string;
275
+ /** Addresses this send ADDRESSED — the audience after the per-send cap. */
276
+ recipients: number;
277
+ sent: number;
278
+ /**
279
+ * The whole audience, deduplicated and validated, BEFORE the per-send cap.
280
+ *
281
+ * Reported separately from `recipients` because the two differ whenever an
282
+ * audience is larger than one send may carry, and a merchant who is only
283
+ * shown the smaller number has no way to learn that the rest were never
284
+ * mailed. `recipients` of 500 against an `audienceSize` of 3,000 is the
285
+ * whole point of the field.
286
+ *
287
+ * Named for the SIZE because `audience` on the options and on the stored
288
+ * campaign is the audience KIND — `'leads'`, `'list'` — and one word
289
+ * meaning both a name and a count on the same send path is how the two get
290
+ * read into each other.
291
+ */
292
+ audienceSize: number;
293
+ /**
294
+ * `audienceSize` is a FLOOR, not a total: the resolution stopped at its
295
+ * read ceiling with documents still unread. Absent means it is exact.
296
+ */
297
+ audienceTruncated?: boolean;
298
+ /**
299
+ * Dry run only (AGL-2178): recipients that will actually be mailed —
300
+ * after the consent join, the per-send cap and both suppression lists.
301
+ */
302
+ sendable?: number;
303
+ /** Dry run only: of `audienceSize`, how many carry a recorded consent basis. */
304
+ consented?: number;
305
+ /**
306
+ * Dry run only: of `consented`, how many hold a basis an OPERATOR asserted
307
+ * on their behalf rather than one they gave — a backfill over seed data,
308
+ * say. A subset of `consented` and not a fourth population.
309
+ */
310
+ consentedByOperator?: number;
311
+ /**
312
+ * Dry run only: of `audienceSize`, how many are reachable only because
313
+ * consent enforcement is not retroactive. This is the population a strict
314
+ * policy would remove.
315
+ */
316
+ grandfathered?: number;
317
+ /** Dry run only: how many of `audienceSize` the consent rule refused. */
318
+ consentWithheld?: number;
319
+ /**
320
+ * Dry run only: how many of `recipients` are suppressed or off-topic.
321
+ *
322
+ * Not netted with {@link cadenceHeld}: leaving this site and asking it for
323
+ * less mail are different statements, and only one of them is a list a
324
+ * merchant has to rebuild.
325
+ */
326
+ suppressed?: number;
327
+ /**
328
+ * Dry run only: how many of `recipients` asked this site for mail less
329
+ * often than this send would arrive.
330
+ *
331
+ * They stay on every audience they were on and are mailed by the next
332
+ * campaign that falls outside their interval — the filter refuses a SEND,
333
+ * never a person.
334
+ */
335
+ cadenceHeld?: number;
336
+ /** Dry run only: which sending identity this campaign would leave on. */
337
+ identity?: string;
338
+ /**
339
+ * Dry run only: which KIND of identity this campaign would leave on.
340
+ *
341
+ * The union from the resolver rather than a local copy of it, so a new arm
342
+ * cannot be added there and silently narrowed here. `'shared'` never reaches
343
+ * a real campaign — the resolution above declares `purpose: 'marketing'`, and
344
+ * a pooled identity refuses before this is assembled — but the type says what
345
+ * the resolver can return, not what this path expects to see.
346
+ */
347
+ identitySource?: SendingIdentitySource | null;
348
+ dryRun?: boolean;
349
+ /** Recipients the hourly governor refused mid-batch (AGL-2409). */
350
+ deferred?: number;
351
+ /**
352
+ * Follow-up only: people in the audience this email had already reached, and
353
+ * which this send therefore did not address.
354
+ *
355
+ * Reported rather than netted away, because it is the number that explains
356
+ * the other ones. A follow-up over a 3,000-person list that addresses 40
357
+ * people has not failed — 2,960 of them already have the email — and
358
+ * without this figure the merchant cannot tell that from a broken audience.
359
+ */
360
+ alreadyReached?: number;
361
+ /** Whether this send added to an existing email rather than starting one. */
362
+ followUp?: boolean;
363
+ /**
364
+ * People this email has resolved and not yet addressed.
365
+ *
366
+ * Non-zero on a send that is still going out, and the figure that makes
367
+ * "reached 500 of 3,000" a sentence rather than a truncation. Zero when the
368
+ * email is finished.
369
+ */
370
+ remaining?: number;
371
+ /** True when another batch of this email will run on its own. */
372
+ resuming?: boolean;
373
+ /** When the next batch may go, ms. Present only while {@link resuming}. */
374
+ nextAtMs?: number;
375
+ /** Batches this email has run, including this one. */
376
+ batch?: number;
377
+ }
378
+ export declare function performCampaignSend(options: CampaignSendOptions): Promise<CampaignSendResult>;
379
+ /**
380
+ * The stored configuration of a send, as the options that would mail it.
381
+ *
382
+ * Every field comes off the RECORD and none of it off the request — the
383
+ * caller names a site and an email id and nothing else. That is not tidiness:
384
+ * `campaignId` addresses an existing document, so a caller who could also
385
+ * supply the body and the audience could put arbitrary copy on somebody
386
+ * else's send id, keep its `cid` and its report, and mail it.
387
+ *
388
+ * Both callers rest on that. A follow-up mails the email that is already
389
+ * there; `sendNow` mails a draft or a scheduled email ahead of its time, and
390
+ * the copy it delivers has to be the copy that was composed and previewed
391
+ * rather than whatever a request body happens to carry.
392
+ *
393
+ * Refuses a send carrying neither a template nor a body, which has no message
394
+ * whichever caller asked.
395
+ */
396
+ /** One address a test send is allowed to be delivered to. */
397
+ export interface ProofRecipient {
398
+ email: string;
399
+ /** A person's name where the membership record carries one. */
400
+ label: string;
401
+ /** True for the caller's own account address. */
402
+ self: boolean;
403
+ }
404
+ /**
405
+ * THE ADDRESSES A TEST SEND MAY BE DELIVERED TO.
406
+ *
407
+ * The caller's own account address, plus every account holder on the org that
408
+ * owns this site. Exported because the composer has to OFFER this set — a
409
+ * free-text box beside a rule enforced on the server is a box whose every
410
+ * wrong answer is a refusal the person could not have predicted.
411
+ *
412
+ * ## Why membership, and not "any address the merchant types"
413
+ *
414
+ * A test send is exempt from the marketing-consent rule (see the proof
415
+ * carve-out in `performCampaignSend`). An exemption that could be pointed at
416
+ * any address would not be a test-send feature, it would be a way to mail
417
+ * anybody without consent and call it a test. Membership is the narrowest
418
+ * boundary that still answers what the button is for: proofing a draft to the
419
+ * people who work on it.
420
+ *
421
+ * ## What is NOT here
422
+ *
423
+ * `siteMembers`, `leads` and contacts. They are the tenant's audience, and
424
+ * the audience is precisely the population the consent rule protects. A
425
+ * contact can be chosen as the PERSONA a proof renders as — see
426
+ * {@link resolveProofPersona} — which reaches nobody.
427
+ *
428
+ * ## What this does not relax
429
+ *
430
+ * Everything else. The send still runs both suppression lists, so an address
431
+ * that bounced or complained is refused however senior its owner; and a
432
+ * stored `declined` on the address still refuses, because a refusal is the
433
+ * one thing no policy may mail.
434
+ */
435
+ export declare function proofRecipientsForHost(options: {
436
+ hostId: string;
437
+ callerEmail: string;
438
+ }): Promise<ProofRecipient[]>;
439
+ /** One person a proof can be rendered as. Reaches nobody. */
440
+ export interface ProofPersona {
441
+ email: string;
442
+ name: string;
443
+ /** Which audience the record came from, so the drawer can say. */
444
+ source: 'lead' | 'member' | 'contact';
445
+ }
446
+ /**
447
+ * A SAMPLE of the people this site's mail is addressed to, for the picker.
448
+ *
449
+ * A sample and not a search: the question the drawer asks is "show me this
450
+ * email as somebody real", and twenty names from each source answers it for
451
+ * the cost of three small reads. A contact picker with a query behind it is a
452
+ * different feature, and it would put a text input in front of the org's
453
+ * whole contact list on a surface whose job is to prove one email.
454
+ *
455
+ * Ordered by document id in every source, because a `limit` with no `orderBy`
456
+ * is a random sample in doc-id order that a client `sort` then makes LOOK
457
+ * newest-first. Naming the order keeps the picker stable between openings —
458
+ * and on the org path it is also the order the automatic index for the scope
459
+ * filter can actually serve.
460
+ */
461
+ export declare function proofPersonasForHost(hostId: string): Promise<ProofPersona[]>;
462
+ /**
463
+ * Campaign API (AGL-161/272): `action` picks the operation.
464
+ *
465
+ * Every one of them requires a site admin or editor.
466
+ *
467
+ * ## The ones that mail something
468
+ *
469
+ * `send` (the default) delivers copy carried in the request. `sendNow` mails
470
+ * a draft or a scheduled email ahead of its time, and `followUp` mails an
471
+ * already-sent one to the people it has not reached; both of those take every
472
+ * field off the RECORD rather than the request. `test` delivers to the caller
473
+ * alone and records nothing.
474
+ *
475
+ * ## The ones that only write
476
+ *
477
+ * `draft` stores an email that has not been sent, `schedule` stores one with
478
+ * a `sendAtMs` for the processor to deliver, `update` corrects the merchant's
479
+ * own name for an email at any point in its life, and `cancel` withdraws a
480
+ * scheduled one. None of them reserves allowance or moves a meter.
481
+ *
482
+ * ## The ones that answer a question
483
+ *
484
+ * `preview` resolves the audience and reports the counts, `renderPreview`
485
+ * renders the composed message. Neither writes.
486
+ */
487
+ export declare const campaignSendHandler: PluginApiHandler;