@aglyn/plugins-email 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 (144) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +58 -0
  4. package/src/index.d.ts +35 -0
  5. package/src/index.js +35 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/campaign-design-create-widget.d.ts +21 -0
  8. package/src/lib/components/campaign-design-create-widget.js +61 -0
  9. package/src/lib/components/campaign-design-create-widget.js.map +1 -0
  10. package/src/lib/components/campaign-sender-editor-widget.d.ts +18 -0
  11. package/src/lib/components/campaign-sender-editor-widget.js +39 -0
  12. package/src/lib/components/campaign-sender-editor-widget.js.map +1 -0
  13. package/src/lib/components/campaign-topic-options-widget.d.ts +19 -0
  14. package/src/lib/components/campaign-topic-options-widget.js +48 -0
  15. package/src/lib/components/campaign-topic-options-widget.js.map +1 -0
  16. package/src/lib/components/campaign-topic-select.d.ts +32 -0
  17. package/src/lib/components/campaign-topic-select.js +87 -0
  18. package/src/lib/components/campaign-topic-select.js.map +1 -0
  19. package/src/lib/components/dynamic-list-rule-fields.d.ts +173 -0
  20. package/src/lib/components/dynamic-list-rule-fields.js +1473 -0
  21. package/src/lib/components/dynamic-list-rule-fields.js.map +1 -0
  22. package/src/lib/components/email-blocks.d.ts +111 -0
  23. package/src/lib/components/email-blocks.js +875 -0
  24. package/src/lib/components/email-blocks.js.map +1 -0
  25. package/src/lib/components/email-design-preview.d.ts +62 -0
  26. package/src/lib/components/email-design-preview.js +174 -0
  27. package/src/lib/components/email-design-preview.js.map +1 -0
  28. package/src/lib/components/email-screens-card.d.ts +42 -0
  29. package/src/lib/components/email-screens-card.js +277 -0
  30. package/src/lib/components/email-screens-card.js.map +1 -0
  31. package/src/lib/components/email-template-detail.d.ts +48 -0
  32. package/src/lib/components/email-template-detail.js +681 -0
  33. package/src/lib/components/email-template-detail.js.map +1 -0
  34. package/src/lib/components/email-topic-detail.d.ts +32 -0
  35. package/src/lib/components/email-topic-detail.js +293 -0
  36. package/src/lib/components/email-topic-detail.js.map +1 -0
  37. package/src/lib/components/email-topics-card.d.ts +46 -0
  38. package/src/lib/components/email-topics-card.js +327 -0
  39. package/src/lib/components/email-topics-card.js.map +1 -0
  40. package/src/lib/components/email-zones.d.ts +28 -0
  41. package/src/lib/components/email-zones.js +20 -0
  42. package/src/lib/components/email-zones.js.map +1 -0
  43. package/src/lib/components/emails-console-page.d.ts +32 -0
  44. package/src/lib/components/emails-console-page.js +229 -0
  45. package/src/lib/components/emails-console-page.js.map +1 -0
  46. package/src/lib/components/emails-console-sections.d.ts +36 -0
  47. package/src/lib/components/emails-console-sections.js +108 -0
  48. package/src/lib/components/emails-console-sections.js.map +1 -0
  49. package/src/lib/components/list-detail-card.d.ts +47 -0
  50. package/src/lib/components/list-detail-card.js +273 -0
  51. package/src/lib/components/list-detail-card.js.map +1 -0
  52. package/src/lib/components/list-edit-card.d.ts +11 -0
  53. package/src/lib/components/list-edit-card.js +287 -0
  54. package/src/lib/components/list-edit-card.js.map +1 -0
  55. package/src/lib/components/list-import-drawer.d.ts +22 -0
  56. package/src/lib/components/list-import-drawer.js +662 -0
  57. package/src/lib/components/list-import-drawer.js.map +1 -0
  58. package/src/lib/components/list-members-panel.d.ts +94 -0
  59. package/src/lib/components/list-members-panel.js +686 -0
  60. package/src/lib/components/list-members-panel.js.map +1 -0
  61. package/src/lib/components/lists-card.d.ts +28 -0
  62. package/src/lib/components/lists-card.js +377 -0
  63. package/src/lib/components/lists-card.js.map +1 -0
  64. package/src/lib/components/sending-domain-detail.d.ts +26 -0
  65. package/src/lib/components/sending-domain-detail.js +496 -0
  66. package/src/lib/components/sending-domain-detail.js.map +1 -0
  67. package/src/lib/components/sending-domains-card.d.ts +33 -0
  68. package/src/lib/components/sending-domains-card.js +962 -0
  69. package/src/lib/components/sending-domains-card.js.map +1 -0
  70. package/src/lib/components/sending-sender-drawer.d.ts +94 -0
  71. package/src/lib/components/sending-sender-drawer.js +543 -0
  72. package/src/lib/components/sending-sender-drawer.js.map +1 -0
  73. package/src/lib/components/suppressions-card.d.ts +49 -0
  74. package/src/lib/components/suppressions-card.js +639 -0
  75. package/src/lib/components/suppressions-card.js.map +1 -0
  76. package/src/lib/components/use-org-email-topics.d.ts +79 -0
  77. package/src/lib/components/use-org-email-topics.js +111 -0
  78. package/src/lib/components/use-org-email-topics.js.map +1 -0
  79. package/src/lib/constants/bundle-common.d.ts +18 -0
  80. package/src/lib/constants/bundle-common.js +18 -0
  81. package/src/lib/constants/bundle-common.js.map +1 -0
  82. package/src/lib/hooks/use-org-company-options.d.ts +20 -0
  83. package/src/lib/hooks/use-org-company-options.js +138 -0
  84. package/src/lib/hooks/use-org-company-options.js.map +1 -0
  85. package/src/lib/hooks/use-org-contact-fields.d.ts +40 -0
  86. package/src/lib/hooks/use-org-contact-fields.js +91 -0
  87. package/src/lib/hooks/use-org-contact-fields.js.map +1 -0
  88. package/src/lib/hooks/use-org-contact-segments.d.ts +16 -0
  89. package/src/lib/hooks/use-org-contact-segments.js +55 -0
  90. package/src/lib/hooks/use-org-contact-segments.js.map +1 -0
  91. package/src/lib/hooks/use-org-crm-views.d.ts +8 -0
  92. package/src/lib/hooks/use-org-crm-views.js +74 -0
  93. package/src/lib/hooks/use-org-crm-views.js.map +1 -0
  94. package/src/lib/hooks/use-org-lists.d.ts +8 -0
  95. package/src/lib/hooks/use-org-lists.js +47 -0
  96. package/src/lib/hooks/use-org-lists.js.map +1 -0
  97. package/src/lib/model/email-design-document.d.ts +52 -0
  98. package/src/lib/model/email-design-document.js +62 -0
  99. package/src/lib/model/email-design-document.js.map +1 -0
  100. package/src/lib/model/index.d.ts +64 -0
  101. package/src/lib/model/index.js +71 -0
  102. package/src/lib/model/index.js.map +1 -0
  103. package/src/lib/model/sending-domain-status.d.ts +99 -0
  104. package/src/lib/model/sending-domain-status.js +196 -0
  105. package/src/lib/model/sending-domain-status.js.map +1 -0
  106. package/src/lib/model/template-provenance.d.ts +113 -0
  107. package/src/lib/model/template-provenance.js +107 -0
  108. package/src/lib/model/template-provenance.js.map +1 -0
  109. package/src/lib/model/template-report.d.ts +158 -0
  110. package/src/lib/model/template-report.js +249 -0
  111. package/src/lib/model/template-report.js.map +1 -0
  112. package/src/lib/plugin.d.ts +27 -0
  113. package/src/lib/plugin.js +163 -0
  114. package/src/lib/plugin.js.map +1 -0
  115. package/src/lib/server-console.d.ts +116 -0
  116. package/src/lib/server-console.js +422 -0
  117. package/src/lib/server-console.js.map +1 -0
  118. package/src/lib/server-email-drafts.d.ts +104 -0
  119. package/src/lib/server-email-drafts.js +381 -0
  120. package/src/lib/server-email-drafts.js.map +1 -0
  121. package/src/lib/server-list-gate.d.ts +183 -0
  122. package/src/lib/server-list-gate.js +365 -0
  123. package/src/lib/server-list-gate.js.map +1 -0
  124. package/src/lib/server-list-import.d.ts +199 -0
  125. package/src/lib/server-list-import.js +632 -0
  126. package/src/lib/server-list-import.js.map +1 -0
  127. package/src/lib/server-suppressions.d.ts +135 -0
  128. package/src/lib/server-suppressions.js +295 -0
  129. package/src/lib/server-suppressions.js.map +1 -0
  130. package/src/lib/server.d.ts +19 -0
  131. package/src/lib/server.js +834 -0
  132. package/src/lib/server.js.map +1 -0
  133. package/src/lib/site.d.ts +26 -0
  134. package/src/lib/site.js +81 -0
  135. package/src/lib/site.js.map +1 -0
  136. package/src/lib/unsubscribe-link.d.ts +311 -0
  137. package/src/lib/unsubscribe-link.js +398 -0
  138. package/src/lib/unsubscribe-link.js.map +1 -0
  139. package/src/lib/utils/create-email-screen.d.ts +59 -0
  140. package/src/lib/utils/create-email-screen.js +59 -0
  141. package/src/lib/utils/create-email-screen.js.map +1 -0
  142. package/src/lib/utils/generate-preset-id.d.ts +19 -0
  143. package/src/lib/utils/generate-preset-id.js +25 -0
  144. package/src/lib/utils/generate-preset-id.js.map +1 -0
@@ -0,0 +1,199 @@
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
+ /**
18
+ * BRINGING AN EXISTING LIST IN — the four routes an import is made of.
19
+ *
20
+ * `docs/specs/email-competitive-gaps.md` G5: export works, import does not
21
+ * exist, and every customer arriving from another product has a list and no
22
+ * way to bring it. P4 is the condition attached to closing that: a bulk
23
+ * import is the fastest way to destroy a shared sending domain, so it ships
24
+ * WITH its controls.
25
+ *
26
+ * ## Every imported address goes through the checks a typed one does
27
+ *
28
+ * Not a similar set — the same functions. {@link resolveAddresses} is the
29
+ * resolution `email/list-members-preview` runs, `assignmentBasis` is the
30
+ * policy `email/list-members-add` applies, and `enrollListMember` is the one
31
+ * writer of the membership collection. A second bulk path with its own idea of
32
+ * suppression and its own idea of consent is exactly the defect class this
33
+ * register has a P1 entry for, and that entry is closed.
34
+ *
35
+ * What this module adds is everything ABOVE that gate: reading a file,
36
+ * screening it, holding the operator's attestation, and metering the work out
37
+ * over as many requests as it takes.
38
+ *
39
+ * ## Four routes, because an import is four separate acts
40
+ *
41
+ * - `email/list-import-preview` — reads the file and says what is in it.
42
+ * Writes nothing, enrolls nobody, and resolves a BOUNDED SAMPLE through the
43
+ * consent gate so the numbers the operator attests against are real numbers
44
+ * from real records rather than a promise.
45
+ * - `email/list-import-start` — records the attestation and stages the
46
+ * addresses. Still enrolls nobody: the act of saying "I have permission for
47
+ * these people" is separated from the act of adding them so that the
48
+ * attestation has a moment of its own.
49
+ * - `email/list-import-run` — enrolls up to {@link LIST_IMPORT_RUN_BUDGET}
50
+ * addresses from the cursor and moves it. Called until it answers
51
+ * `complete`.
52
+ * - `email/list-import-status` — the unfinished import on a list, if there is
53
+ * one, so a merchant who closed the tab is not left with a half-added
54
+ * audience and no way to see it.
55
+ *
56
+ * ## A budget and a cursor, not one request and not one transaction
57
+ *
58
+ * The shape `dynamic-list-materialize.ts` already uses: a per-run bound on
59
+ * work, a cursor recording where the run stopped, and a next run that resumes
60
+ * rather than restarts. A 50,000-address file is not a request that times out
61
+ * halfway with no record of what it did; it is 500 bounded requests over one
62
+ * durable job, and stopping in the middle of it leaves the addresses already
63
+ * enrolled enrolled and the rest staged.
64
+ *
65
+ * The run budget is deliberately {@link LIST_MEMBER_BATCH_MAX} — the same
66
+ * number of addresses one hand-typed add already resolves in one request — so
67
+ * an import run costs exactly what an add costs and no new cost profile is
68
+ * introduced to discover in production.
69
+ *
70
+ * ## ⛔ Nothing here is a capacity limit
71
+ *
72
+ * {@link LIST_IMPORT_MAX_ADDRESSES} refuses a FILE before anything is written.
73
+ * It never trims a staged import, never drops an address to fit, and never
74
+ * removes anybody already on the list. A ceiling in this product is enforced
75
+ * at the reduction, and the reduction here is refusing the upload — which the
76
+ * operator sees, can argue with, and can act on by splitting the file.
77
+ *
78
+ * ## Who the attester is, and why a resumer does not become one
79
+ *
80
+ * The attestation is one person's claim about where a file came from. It is
81
+ * recorded on the job with the account that made it, and every run reads the
82
+ * basis from THAT account, not from whoever pressed Resume. A colleague who
83
+ * finishes somebody else's import has asserted nothing, and the consent
84
+ * records the run writes must not say they did.
85
+ */
86
+ import { type PluginApiHandler } from '@aglyn/aglyn/server';
87
+ /** `source` stamped on every membership an import writes. */
88
+ export declare const CONSOLE_IMPORT_SOURCE = "console:list-import";
89
+ /** Where a list's import jobs live: `orgs/{orgId}/lists/{listId}/imports`. */
90
+ export declare const LIST_IMPORTS_SUBCOLLECTION = "imports";
91
+ /**
92
+ * Addresses one run enrolls before answering and handing back the cursor.
93
+ *
94
+ * The same number as {@link LIST_MEMBER_BATCH_MAX} on purpose — see the module
95
+ * note. It is a bound on WORK: it can never refuse a person, and the addresses
96
+ * it does not reach in this run are reached by the next one.
97
+ */
98
+ export declare const LIST_IMPORT_RUN_BUDGET = 100;
99
+ /**
100
+ * Staged addresses per chunk document.
101
+ *
102
+ * Comfortably inside Firestore's one-megabyte document limit at the widths a
103
+ * contact file actually carries, and a multiple of the run budget so a run
104
+ * reads exactly one chunk. Reading two would be the common case at any size
105
+ * that is not a multiple, which is a round trip paid on every run to save
106
+ * nothing.
107
+ */
108
+ export declare const LIST_IMPORT_CHUNK_SIZE = 500;
109
+ /**
110
+ * `POST email/list-import-preview` — what is in this file.
111
+ *
112
+ * Reads only. It answers three separate questions and keeps them separate,
113
+ * because collapsing them is how an import gets attested to on a number that
114
+ * is not the number:
115
+ *
116
+ * - what the FILE contains: usable addresses, unusable lines, duplicates
117
+ * collapsed, and the columns it carries;
118
+ * - what the SCREENING found, which decides nothing and is shown anyway;
119
+ * - what the CONSENT GATE says about a bounded sample of the addresses.
120
+ *
121
+ * The sample is the honest shape rather than a shortcut. Resolving fifty
122
+ * thousand addresses against the contacts collection and both suppression
123
+ * lists is the same scan the import itself performs, so a preview that did it
124
+ * would be the import minus the writes — twice the cost, and a request that
125
+ * times out on exactly the files this feature exists for. So the sample size
126
+ * is reported beside the total and the run reports the real figures as they
127
+ * become true, which is the same distinction `email/list-rule-preview` draws
128
+ * between `matched` and the batch it hands back.
129
+ */
130
+ export declare const emailListImportPreviewHandler: PluginApiHandler;
131
+ /**
132
+ * `POST email/list-import-start` — record the attestation, stage the file.
133
+ *
134
+ * Body: `{ hostId, listId, text, attestConsent }`. Enrolls nobody. It writes
135
+ * the job document that every subsequent run reads, and the chunks holding
136
+ * the addresses, and then stops — so the moment the operator makes their
137
+ * claim is a moment of its own, with a record of who made it and when, rather
138
+ * than a flag riding along on the request that also did the work.
139
+ *
140
+ * `attestConsent` is the operator STATING they have these people's
141
+ * permission. It is not a way to name a basis: the basis is derived per
142
+ * address at run time from that person's own record, exactly as the
143
+ * one-address add path derives it, and this flag can only ever produce the
144
+ * attributable kind.
145
+ */
146
+ export declare const emailListImportStartHandler: PluginApiHandler;
147
+ /**
148
+ * `POST email/list-import-run` — enroll the next batch.
149
+ *
150
+ * Body: `{ hostId, listId, importId }`. Answers `complete` when the cursor
151
+ * has reached the total, so the caller's loop is "call until complete" and
152
+ * nothing has to guess how many runs a file needs.
153
+ *
154
+ * ## The cursor moves for every address, enrolled or refused
155
+ *
156
+ * A refusal is a finished address. Advancing only on success would put a
157
+ * suppressed address at the head of the queue forever and turn the import
158
+ * into a loop that never terminates on exactly the files that most need to
159
+ * terminate.
160
+ *
161
+ * ## The counters are incremented, not recomputed
162
+ *
163
+ * `FieldValue.increment` rather than a read-modify-write, so two runs racing
164
+ * on one job — a merchant with the drawer open in two tabs — cannot lose a
165
+ * batch's worth of tally. The cursor is written as an absolute value because
166
+ * it is the position the NEXT run reads from, and two racing runs that both
167
+ * incremented it would skip a batch rather than repeat one; repeating is safe
168
+ * (`enrollListMember` is keyed by the person), skipping is not.
169
+ */
170
+ export declare const emailListImportRunHandler: PluginApiHandler;
171
+ /**
172
+ * `POST email/list-import-status` — the import on this list, if there is one.
173
+ *
174
+ * Reached when the import drawer opens, and at no other time. It exists
175
+ * because a browser is not a durable thing: a merchant who closed the tab
176
+ * during a large import has an audience that is part-way filled and, without
177
+ * this, no way to see that or to finish it. What they must never be offered
178
+ * instead is a fresh import of the same file, which would re-run the whole
179
+ * gate over addresses already enrolled.
180
+ *
181
+ * Ordered on `createdAt`, which every job document written by
182
+ * `email/list-import-start` carries — a `limit()` with no `orderBy` answers in
183
+ * document-id order, and the ids come from `createResourceUid()`, so the
184
+ * "latest" import would be an arbitrary one.
185
+ */
186
+ export declare const emailListImportStatusHandler: PluginApiHandler;
187
+ /**
188
+ * Import route registration.
189
+ *
190
+ * Reached by a person pressing a button in a browser, like the rest of the
191
+ * console half, so none of these is on the machine-path exemption list in
192
+ * `plugin-api-rate-limit.ts` — with one consequence worth stating: the RUN
193
+ * route is called repeatedly by design, once per {@link
194
+ * LIST_IMPORT_RUN_BUDGET} addresses, so the visitor limiter's per-(site, IP)
195
+ * budget is the ceiling on how fast a large import can proceed. That is the
196
+ * correct ceiling for a path that enrolls people into a marketing audience,
197
+ * and it degrades into a slower import rather than a failed one.
198
+ */
199
+ export declare function registerEmailListImportApi(): void;