@aglyn/plugins-outreach 1.0.0-beta.197 → 1.0.0-beta.199

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 (63) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/compliance-section.d.ts +3 -1
  3. package/src/lib/components/compliance-section.js +110 -45
  4. package/src/lib/components/compliance-section.js.map +1 -1
  5. package/src/lib/components/enroll-dialog.d.ts +7 -1
  6. package/src/lib/components/enroll-dialog.js +47 -3
  7. package/src/lib/components/enroll-dialog.js.map +1 -1
  8. package/src/lib/components/outreach-ui.js +1 -0
  9. package/src/lib/components/outreach-ui.js.map +1 -1
  10. package/src/lib/components/sequence-editor.js +25 -24
  11. package/src/lib/components/sequence-editor.js.map +1 -1
  12. package/src/lib/engine/delivery-status.d.ts +8 -0
  13. package/src/lib/engine/delivery-status.js +10 -5
  14. package/src/lib/engine/delivery-status.js.map +1 -1
  15. package/src/lib/engine/enrollment-state.d.ts +2 -0
  16. package/src/lib/engine/enrollment-state.js +2 -2
  17. package/src/lib/engine/enrollment-state.js.map +1 -1
  18. package/src/lib/engine/gates.d.ts +28 -1
  19. package/src/lib/engine/gates.js +28 -1
  20. package/src/lib/engine/gates.js.map +1 -1
  21. package/src/lib/engine/mail-gateway.d.ts +97 -0
  22. package/src/lib/engine/mail-gateway.js +226 -0
  23. package/src/lib/engine/mail-gateway.js.map +1 -0
  24. package/src/lib/engine/mailbox-health.d.ts +3 -3
  25. package/src/lib/engine/mailbox-health.js +17 -11
  26. package/src/lib/engine/mailbox-health.js.map +1 -1
  27. package/src/lib/engine/thread-classification.d.ts +2 -0
  28. package/src/lib/engine/thread-classification.js +2 -1
  29. package/src/lib/engine/thread-classification.js.map +1 -1
  30. package/src/lib/enrollment/enroll-people.d.ts +6 -1
  31. package/src/lib/enrollment/enroll-people.js +18 -4
  32. package/src/lib/enrollment/enroll-people.js.map +1 -1
  33. package/src/lib/enrollment/gate-lookups.d.ts +7 -0
  34. package/src/lib/enrollment/gate-lookups.js +11 -2
  35. package/src/lib/enrollment/gate-lookups.js.map +1 -1
  36. package/src/lib/model/outreach-api.d.ts +14 -0
  37. package/src/lib/model/outreach-api.js.map +1 -1
  38. package/src/lib/model/outreach.types.d.ts +79 -3
  39. package/src/lib/model/outreach.types.js +21 -2
  40. package/src/lib/model/outreach.types.js.map +1 -1
  41. package/src/lib/routes/enroll-routes.js +23 -4
  42. package/src/lib/routes/enroll-routes.js.map +1 -1
  43. package/src/lib/routes/enrollment-routes.js +15 -1
  44. package/src/lib/routes/enrollment-routes.js.map +1 -1
  45. package/src/lib/routes/register-routes.js +3 -0
  46. package/src/lib/routes/register-routes.js.map +1 -1
  47. package/src/lib/routes/route-deps.d.ts +6 -0
  48. package/src/lib/routes/route-deps.js.map +1 -1
  49. package/src/lib/runtime/gateway-ledger.d.ts +46 -0
  50. package/src/lib/runtime/gateway-ledger.js +113 -0
  51. package/src/lib/runtime/gateway-ledger.js.map +1 -0
  52. package/src/lib/runtime/platform-runtime-deps.js +3 -0
  53. package/src/lib/runtime/platform-runtime-deps.js.map +1 -1
  54. package/src/lib/runtime/runtime-deps.d.ts +6 -0
  55. package/src/lib/runtime/runtime-deps.js.map +1 -1
  56. package/src/lib/runtime/send-job.js +48 -0
  57. package/src/lib/runtime/send-job.js.map +1 -1
  58. package/src/lib/runtime/sync-job.d.ts +2 -0
  59. package/src/lib/runtime/sync-job.js +40 -3
  60. package/src/lib/runtime/sync-job.js.map +1 -1
  61. package/src/lib/storage/domain-intel-store.d.ts +107 -0
  62. package/src/lib/storage/domain-intel-store.js +320 -0
  63. package/src/lib/storage/domain-intel-store.js.map +1 -0
@@ -0,0 +1,320 @@
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
+ * WHAT THE ORGANIZATION KNOWS ABOUT THE DOMAINS IT WRITES TO (AGL-3326).
18
+ *
19
+ * Two documents, both server-written:
20
+ *
21
+ * - `outreachDomainIntel/{domain}` — the domain's MX records and the mail
22
+ * gateway they name, looked up with the resolver the caller hands in and
23
+ * trusted for {@link OUTREACH_DOMAIN_INTEL_TTL_MS}; beside them, what this
24
+ * organization's mail met at the domain;
25
+ * - `outreachGatewayStats/{gateway}` — the same three counts per gateway,
26
+ * in total and by UTC day, which is the ledger the hold and the
27
+ * Check-people chip read.
28
+ *
29
+ * A read that could not be made answers `null` for the addresses it
30
+ * covered, which the gates refuse as "we couldn't check" — except that a
31
+ * stale answer outranks no answer: a domain whose MX was known last week
32
+ * and cannot be looked up today is read as it was, because a resolver
33
+ * timing out says nothing about the domain. A domain that resolves to no
34
+ * MX at all is `none`, and the gates block it.
35
+ *
36
+ * The ledger is written in a transaction over both documents so a run that
37
+ * records a block and a run that credits a delivery cannot lose each
38
+ * other's count, and the days map is pruned on every write so it never
39
+ * grows past the window it serves.
40
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
41
+ import { classifyOutreachMailGateway, OUTREACH_DOMAIN_INTEL_TTL_MS, OUTREACH_GATEWAY_CHIP_DAYS, OUTREACH_GATEWAY_WINDOW_DAYS, OUTREACH_MAIL_GATEWAYS, outreachGatewayDay, outreachGatewayWindow, pruneOutreachGatewayDays } from "../engine/mail-gateway.js";
42
+ import { normalizeOutreachDomain, outreachEmailDomain } from "../engine/do-not-contact-domain.js";
43
+ import { outreachOrgCollection } from "./outreach-records.js";
44
+ /** `getAll` takes this many references at most per call. */ const GET_ALL_CHUNK = 300;
45
+ /** The resolver's codes that mean "this domain has no MX", as opposed to "I could not ask". */ const NO_MX_CODES = new Set([
46
+ 'ENOTFOUND',
47
+ 'ENODATA',
48
+ 'NXDOMAIN',
49
+ 'NOTFOUND'
50
+ ]);
51
+ const count = (value)=>{
52
+ const number = Number(value);
53
+ return Number.isFinite(number) ? Math.max(0, Math.floor(number)) : 0;
54
+ };
55
+ const ms = (value)=>typeof value === 'number' && Number.isFinite(value) ? value : null;
56
+ const gatewayOf = (value)=>OUTREACH_MAIL_GATEWAYS.includes(value) ? value : null;
57
+ /** A stored domain-intel document in its model shape, or `null` for no document. */ export function readStoredOutreachDomainIntel(id, data) {
58
+ var _ms, _ms1;
59
+ if (!data) return null;
60
+ const gateway = gatewayOf(data['gateway']);
61
+ if (!gateway) return null;
62
+ return {
63
+ domain: typeof data['domain'] === 'string' && data['domain'] ? data['domain'] : id,
64
+ mx: Array.isArray(data['mx']) ? data['mx'].filter((entry)=>typeof entry === 'string') : [],
65
+ gateway,
66
+ resolvedAtMs: (_ms = ms(data['resolvedAtMs'])) != null ? _ms : 0,
67
+ sent: count(data['sent']),
68
+ delivered: count(data['delivered']),
69
+ blocked: count(data['blocked']),
70
+ lastBlockedAtMs: ms(data['lastBlockedAtMs']),
71
+ updatedAtMs: (_ms1 = ms(data['updatedAtMs'])) != null ? _ms1 : 0
72
+ };
73
+ }
74
+ /** A stored gateway ledger in its model shape, or `null` for no document. */ export function readStoredOutreachGatewayStats(id, data) {
75
+ var _gatewayOf, _ms;
76
+ if (!data) return null;
77
+ const gateway = (_gatewayOf = gatewayOf(data['gateway'])) != null ? _gatewayOf : gatewayOf(id);
78
+ if (!gateway) return null;
79
+ const days = {};
80
+ const raw = data['days'];
81
+ if (raw && typeof raw === 'object') {
82
+ for (const [day, counts] of Object.entries(raw)){
83
+ const entry = counts && typeof counts === 'object' ? counts : {};
84
+ days[day] = {
85
+ sent: count(entry['sent']),
86
+ delivered: count(entry['delivered']),
87
+ blocked: count(entry['blocked'])
88
+ };
89
+ }
90
+ }
91
+ return {
92
+ gateway,
93
+ sent: count(data['sent']),
94
+ delivered: count(data['delivered']),
95
+ blocked: count(data['blocked']),
96
+ lastBlockedAtMs: ms(data['lastBlockedAtMs']),
97
+ days,
98
+ updatedAtMs: (_ms = ms(data['updatedAtMs'])) != null ? _ms : 0
99
+ };
100
+ }
101
+ /**
102
+ * A domain's MX, resolved and classified — see the module note. A domain
103
+ * the resolver says has no MX answers `none`; a resolver that could not
104
+ * answer rejects, and the caller decides what a missing answer means.
105
+ */ export async function resolveOutreachDomainMx(resolveMx, domain) {
106
+ let records;
107
+ try {
108
+ records = await resolveMx(domain);
109
+ } catch (error) {
110
+ var _ref;
111
+ const code = String((_ref = error == null ? void 0 : error.code) != null ? _ref : '').toUpperCase();
112
+ if (NO_MX_CODES.has(code)) return {
113
+ mx: [],
114
+ gateway: 'none'
115
+ };
116
+ throw error;
117
+ }
118
+ const mx = [
119
+ ...records
120
+ ].filter((record)=>record && typeof record.exchange === 'string').sort((a, b)=>(Number(a.priority) || 0) - (Number(b.priority) || 0)).map((record)=>record.exchange.trim().toLowerCase().replace(/\.$/, ''))// RFC 7505's null MX — a lone "." — says the domain takes no mail.
121
+ .filter((exchange)=>exchange && exchange !== '.');
122
+ return {
123
+ mx,
124
+ gateway: classifyOutreachMailGateway(mx)
125
+ };
126
+ }
127
+ const intelCollection = (firestore, orgId)=>outreachOrgCollection(firestore, orgId, 'domainIntel');
128
+ const statsCollection = (firestore, orgId)=>outreachOrgCollection(firestore, orgId, 'gatewayStats');
129
+ /**
130
+ * The domain intel for every address, keyed by address — fresh from the
131
+ * store, or looked up and stored when missing or older than the TTL. An
132
+ * address with no domain, and one whose domain could not be looked up and
133
+ * was never known, answers `null`.
134
+ */ export async function readOutreachDomainIntel(firestore, orgId, emails, input) {
135
+ var _known_get;
136
+ const answers = new Map();
137
+ const domainOf = new Map();
138
+ for (const email of emails){
139
+ const domain = outreachEmailDomain(email);
140
+ if (domain) domainOf.set(email, domain);
141
+ else answers.set(email, null);
142
+ }
143
+ const domains = [
144
+ ...new Set(domainOf.values())
145
+ ];
146
+ if (!domains.length) return answers;
147
+ const collection = intelCollection(firestore, orgId);
148
+ const known = new Map();
149
+ try {
150
+ for(let start = 0; start < domains.length; start += GET_ALL_CHUNK){
151
+ const chunk = domains.slice(start, start + GET_ALL_CHUNK);
152
+ const snapshots = await firestore.getAll(...chunk.map((domain)=>collection.doc(domain)));
153
+ chunk.forEach((domain, index)=>{
154
+ const snapshot = snapshots[index];
155
+ known.set(domain, readStoredOutreachDomainIntel(domain, (snapshot == null ? void 0 : snapshot.exists) ? snapshot.data() : undefined));
156
+ });
157
+ }
158
+ } catch (error) {
159
+ console.error('[outreach] domain intel lookup failed; reading as unchecked', error);
160
+ for (const email of domainOf.keys())answers.set(email, null);
161
+ return answers;
162
+ }
163
+ await Promise.all(domains.map(async (domain)=>{
164
+ var _known_get, _ref, _ref1, _ref2, _ref3;
165
+ const stored = (_known_get = known.get(domain)) != null ? _known_get : null;
166
+ if (stored && stored.resolvedAtMs + OUTREACH_DOMAIN_INTEL_TTL_MS > input.nowMs) return;
167
+ let resolved;
168
+ try {
169
+ resolved = await resolveOutreachDomainMx(input.resolveMx, domain);
170
+ } catch (error) {
171
+ // A resolver that could not answer: the stale answer stands, and a
172
+ // domain never looked up is unchecked.
173
+ console.error(`[outreach] the MX of ${domain} could not be looked up`, error);
174
+ return;
175
+ }
176
+ const fresh = {
177
+ domain,
178
+ mx: resolved.mx,
179
+ gateway: resolved.gateway,
180
+ resolvedAtMs: input.nowMs,
181
+ sent: (_ref = stored == null ? void 0 : stored.sent) != null ? _ref : 0,
182
+ delivered: (_ref1 = stored == null ? void 0 : stored.delivered) != null ? _ref1 : 0,
183
+ blocked: (_ref2 = stored == null ? void 0 : stored.blocked) != null ? _ref2 : 0,
184
+ lastBlockedAtMs: (_ref3 = stored == null ? void 0 : stored.lastBlockedAtMs) != null ? _ref3 : null,
185
+ updatedAtMs: input.nowMs
186
+ };
187
+ known.set(domain, fresh);
188
+ try {
189
+ await collection.doc(domain).set({
190
+ domain,
191
+ mx: fresh.mx,
192
+ gateway: fresh.gateway,
193
+ resolvedAtMs: fresh.resolvedAtMs,
194
+ updatedAtMs: fresh.updatedAtMs
195
+ }, {
196
+ merge: true
197
+ });
198
+ } catch (error) {
199
+ // The cache is a convenience; the answer is already in hand.
200
+ console.error(`[outreach] the MX of ${domain} could not be cached`, error);
201
+ }
202
+ }));
203
+ for (const [email, domain] of domainOf)answers.set(email, (_known_get = known.get(domain)) != null ? _known_get : null);
204
+ return answers;
205
+ }
206
+ /** The ledger of each gateway named, keyed by gateway; a gateway with no ledger yet answers `null`. */ export async function readOutreachGatewayStats(firestore, orgId, gateways) {
207
+ const answers = new Map();
208
+ const wanted = [
209
+ ...new Set(gateways)
210
+ ];
211
+ if (!wanted.length) return answers;
212
+ const collection = statsCollection(firestore, orgId);
213
+ const snapshots = await firestore.getAll(...wanted.map((gateway)=>collection.doc(gateway)));
214
+ wanted.forEach((gateway, index)=>{
215
+ const snapshot = snapshots[index];
216
+ answers.set(gateway, readStoredOutreachGatewayStats(gateway, (snapshot == null ? void 0 : snapshot.exists) ? snapshot.data() : undefined));
217
+ });
218
+ return answers;
219
+ }
220
+ /** What the gates and the chip read for one domain: its gateway and that gateway's recent record here. */ export function outreachGatewayStandingOf(intel, stats, nowMs) {
221
+ const week = outreachGatewayWindow(stats == null ? void 0 : stats.days, nowMs, OUTREACH_GATEWAY_CHIP_DAYS);
222
+ const month = outreachGatewayWindow(stats == null ? void 0 : stats.days, nowMs, OUTREACH_GATEWAY_WINDOW_DAYS);
223
+ return {
224
+ gateway: intel.gateway,
225
+ blocked7: week.blocked,
226
+ delivered7: week.delivered,
227
+ blocked30: month.blocked,
228
+ delivered30: month.delivered
229
+ };
230
+ }
231
+ /**
232
+ * The gateway standing of every address, keyed by address: the domain
233
+ * intel read (and refreshed) above, joined to the ledger of each gateway it
234
+ * names. `null` for an address whose domain could not be checked.
235
+ */ export async function readOutreachGatewayStandings(firestore, orgId, emails, input) {
236
+ const intel = await readOutreachDomainIntel(firestore, orgId, emails, input);
237
+ const gateways = [
238
+ ...intel.values()
239
+ ].filter((entry)=>entry !== null).map((entry)=>entry.gateway);
240
+ let stats;
241
+ try {
242
+ stats = await readOutreachGatewayStats(firestore, orgId, gateways);
243
+ } catch (error) {
244
+ console.error('[outreach] gateway ledger lookup failed; reading as unchecked', error);
245
+ return new Map(emails.map((email)=>[
246
+ email,
247
+ null
248
+ ]));
249
+ }
250
+ const answers = new Map();
251
+ for (const email of emails){
252
+ var _intel_get, _stats_get;
253
+ const entry = (_intel_get = intel.get(email)) != null ? _intel_get : null;
254
+ answers.set(email, entry ? outreachGatewayStandingOf(entry, (_stats_get = stats.get(entry.gateway)) != null ? _stats_get : null, input.nowMs) : null);
255
+ }
256
+ return answers;
257
+ }
258
+ /**
259
+ * Counts an outcome on the domain's intel and on the gateway's ledger — see
260
+ * the module note. Never throws: the send or the bounce it records has
261
+ * already happened, and a lost count understates a gateway while a thrown
262
+ * one loses the step that called.
263
+ */ export async function recordOutreachGatewayOutcome(firestore, orgId, input) {
264
+ var _input_count;
265
+ const domain = normalizeOutreachDomain(input.domain);
266
+ const by = count((_input_count = input.count) != null ? _input_count : 1);
267
+ if (!domain || !by) return;
268
+ const intelRef = intelCollection(firestore, orgId).doc(domain);
269
+ const statsRef = statsCollection(firestore, orgId).doc(input.gateway);
270
+ const day = outreachGatewayDay(input.atMs);
271
+ try {
272
+ await firestore.runTransaction(async (transaction)=>{
273
+ var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _days_day, _ref7, _ref8, _ref9, _ref10;
274
+ const [intelSnapshot, statsSnapshot] = await Promise.all([
275
+ transaction.get(intelRef),
276
+ transaction.get(statsRef)
277
+ ]);
278
+ const intel = readStoredOutreachDomainIntel(domain, intelSnapshot.exists ? intelSnapshot.data() : undefined);
279
+ const stats = readStoredOutreachGatewayStats(input.gateway, statsSnapshot.exists ? statsSnapshot.data() : undefined);
280
+ const blockedAt = input.outcome === 'blocked' ? input.atMs : null;
281
+ const nextIntel = {
282
+ domain,
283
+ // A domain never looked up — enrolled before the intel existed —
284
+ // is written with what the bounce said, and looked up on its next read.
285
+ mx: (_ref = intel == null ? void 0 : intel.mx) != null ? _ref : [],
286
+ gateway: (_ref1 = intel == null ? void 0 : intel.gateway) != null ? _ref1 : input.gateway,
287
+ resolvedAtMs: (_ref2 = intel == null ? void 0 : intel.resolvedAtMs) != null ? _ref2 : 0,
288
+ sent: ((_ref3 = intel == null ? void 0 : intel.sent) != null ? _ref3 : 0) + (input.outcome === 'sent' ? by : 0),
289
+ delivered: ((_ref4 = intel == null ? void 0 : intel.delivered) != null ? _ref4 : 0) + (input.outcome === 'delivered' ? by : 0),
290
+ blocked: ((_ref5 = intel == null ? void 0 : intel.blocked) != null ? _ref5 : 0) + (input.outcome === 'blocked' ? by : 0),
291
+ lastBlockedAtMs: Math.max((_ref6 = intel == null ? void 0 : intel.lastBlockedAtMs) != null ? _ref6 : 0, blockedAt != null ? blockedAt : 0) || null,
292
+ updatedAtMs: input.atMs
293
+ };
294
+ const days = pruneOutreachGatewayDays(stats == null ? void 0 : stats.days, input.atMs, OUTREACH_GATEWAY_WINDOW_DAYS);
295
+ const today = (_days_day = days[day]) != null ? _days_day : {
296
+ sent: 0,
297
+ delivered: 0,
298
+ blocked: 0
299
+ };
300
+ days[day] = _extends({}, today, {
301
+ [input.outcome]: today[input.outcome] + by
302
+ });
303
+ const nextStats = {
304
+ gateway: input.gateway,
305
+ sent: ((_ref7 = stats == null ? void 0 : stats.sent) != null ? _ref7 : 0) + (input.outcome === 'sent' ? by : 0),
306
+ delivered: ((_ref8 = stats == null ? void 0 : stats.delivered) != null ? _ref8 : 0) + (input.outcome === 'delivered' ? by : 0),
307
+ blocked: ((_ref9 = stats == null ? void 0 : stats.blocked) != null ? _ref9 : 0) + (input.outcome === 'blocked' ? by : 0),
308
+ lastBlockedAtMs: Math.max((_ref10 = stats == null ? void 0 : stats.lastBlockedAtMs) != null ? _ref10 : 0, blockedAt != null ? blockedAt : 0) || null,
309
+ days,
310
+ updatedAtMs: input.atMs
311
+ };
312
+ transaction.set(intelRef, nextIntel);
313
+ transaction.set(statsRef, nextStats);
314
+ });
315
+ } catch (error) {
316
+ console.error(`[outreach] the gateway ledger could not count a ${input.outcome} at ${domain}`, error);
317
+ }
318
+ }
319
+
320
+ //# sourceMappingURL=domain-intel-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/storage/domain-intel-store.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * WHAT THE ORGANIZATION KNOWS ABOUT THE DOMAINS IT WRITES TO (AGL-3326).\n *\n * Two documents, both server-written:\n *\n * - `outreachDomainIntel/{domain}` — the domain's MX records and the mail\n * gateway they name, looked up with the resolver the caller hands in and\n * trusted for {@link OUTREACH_DOMAIN_INTEL_TTL_MS}; beside them, what this\n * organization's mail met at the domain;\n * - `outreachGatewayStats/{gateway}` — the same three counts per gateway,\n * in total and by UTC day, which is the ledger the hold and the\n * Check-people chip read.\n *\n * A read that could not be made answers `null` for the addresses it\n * covered, which the gates refuse as \"we couldn't check\" — except that a\n * stale answer outranks no answer: a domain whose MX was known last week\n * and cannot be looked up today is read as it was, because a resolver\n * timing out says nothing about the domain. A domain that resolves to no\n * MX at all is `none`, and the gates block it.\n *\n * The ledger is written in a transaction over both documents so a run that\n * records a block and a run that credits a delivery cannot lose each\n * other's count, and the days map is pruned on every write so it never\n * grows past the window it serves.\n */\n\nimport type { OutreachGatewayStanding, OutreachMailGateway } from '../engine/mail-gateway'\nimport {\n classifyOutreachMailGateway,\n OUTREACH_DOMAIN_INTEL_TTL_MS,\n OUTREACH_GATEWAY_CHIP_DAYS,\n OUTREACH_GATEWAY_WINDOW_DAYS,\n OUTREACH_MAIL_GATEWAYS,\n outreachGatewayDay,\n outreachGatewayWindow,\n pruneOutreachGatewayDays,\n} from '../engine/mail-gateway'\nimport { normalizeOutreachDomain, outreachEmailDomain } from '../engine/do-not-contact-domain'\nimport type { OutreachDomainIntel, OutreachGatewayStats } from '../model/outreach.types'\nimport { outreachOrgCollection } from './outreach-records'\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** One MX record as Node's resolver answers it. */\nexport interface OutreachMxRecord {\n exchange: string\n priority: number\n}\n\n/**\n * `dns.promises.resolveMx`, or a stand-in: the platform hands in Node's,\n * the specs hand in a table. Rejects the way Node does — `code`\n * `ENOTFOUND` or `ENODATA` for a domain with no MX — and any other\n * rejection is a resolver that could not answer.\n */\nexport type OutreachResolveMx = (domain: string) => Promise<ReadonlyArray<OutreachMxRecord>>\n\n/** What a domain-intel read needs beside the store. */\nexport interface OutreachDomainIntelReadInput {\n resolveMx: OutreachResolveMx\n nowMs: number\n}\n\n/** `getAll` takes this many references at most per call. */\nconst GET_ALL_CHUNK = 300\n\n/** The resolver's codes that mean \"this domain has no MX\", as opposed to \"I could not ask\". */\nconst NO_MX_CODES = new Set(['ENOTFOUND', 'ENODATA', 'NXDOMAIN', 'NOTFOUND'])\n\nconst count = (value: unknown): number => {\n const number = Number(value)\n return Number.isFinite(number) ? Math.max(0, Math.floor(number)) : 0\n}\nconst ms = (value: unknown): number | null =>\n typeof value === 'number' && Number.isFinite(value) ? value : null\nconst gatewayOf = (value: unknown): OutreachMailGateway | null =>\n (OUTREACH_MAIL_GATEWAYS as readonly unknown[]).includes(value) ? (value as OutreachMailGateway) : null\n\n/** A stored domain-intel document in its model shape, or `null` for no document. */\nexport function readStoredOutreachDomainIntel(\n id: string,\n data: Record<string, unknown> | undefined,\n): OutreachDomainIntel | null {\n if (!data) return null\n const gateway = gatewayOf(data['gateway'])\n if (!gateway) return null\n return {\n domain: typeof data['domain'] === 'string' && data['domain'] ? data['domain'] : id,\n mx: Array.isArray(data['mx']) ? data['mx'].filter((entry): entry is string => typeof entry === 'string') : [],\n gateway,\n resolvedAtMs: ms(data['resolvedAtMs']) ?? 0,\n sent: count(data['sent']),\n delivered: count(data['delivered']),\n blocked: count(data['blocked']),\n lastBlockedAtMs: ms(data['lastBlockedAtMs']),\n updatedAtMs: ms(data['updatedAtMs']) ?? 0,\n }\n}\n\n/** A stored gateway ledger in its model shape, or `null` for no document. */\nexport function readStoredOutreachGatewayStats(\n id: string,\n data: Record<string, unknown> | undefined,\n): OutreachGatewayStats | null {\n if (!data) return null\n const gateway = gatewayOf(data['gateway']) ?? gatewayOf(id)\n if (!gateway) return null\n const days: OutreachGatewayStats['days'] = {}\n const raw = data['days']\n if (raw && typeof raw === 'object') {\n for (const [day, counts] of Object.entries(raw as Record<string, unknown>)) {\n const entry = (counts && typeof counts === 'object' ? counts : {}) as Record<string, unknown>\n days[day] = { sent: count(entry['sent']), delivered: count(entry['delivered']), blocked: count(entry['blocked']) }\n }\n }\n return {\n gateway,\n sent: count(data['sent']),\n delivered: count(data['delivered']),\n blocked: count(data['blocked']),\n lastBlockedAtMs: ms(data['lastBlockedAtMs']),\n days,\n updatedAtMs: ms(data['updatedAtMs']) ?? 0,\n }\n}\n\n/**\n * A domain's MX, resolved and classified — see the module note. A domain\n * the resolver says has no MX answers `none`; a resolver that could not\n * answer rejects, and the caller decides what a missing answer means.\n */\nexport async function resolveOutreachDomainMx(\n resolveMx: OutreachResolveMx,\n domain: string,\n): Promise<{ mx: string[]; gateway: OutreachMailGateway }> {\n let records: ReadonlyArray<OutreachMxRecord>\n try {\n records = await resolveMx(domain)\n } catch (error) {\n const code = String((error as { code?: unknown })?.code ?? '').toUpperCase()\n if (NO_MX_CODES.has(code)) return { mx: [], gateway: 'none' }\n throw error\n }\n const mx = [...records]\n .filter((record) => record && typeof record.exchange === 'string')\n .sort((a, b) => (Number(a.priority) || 0) - (Number(b.priority) || 0))\n .map((record) => record.exchange.trim().toLowerCase().replace(/\\.$/, ''))\n // RFC 7505's null MX — a lone \".\" — says the domain takes no mail.\n .filter((exchange) => exchange && exchange !== '.')\n return { mx, gateway: classifyOutreachMailGateway(mx) }\n}\n\nconst intelCollection = (firestore: Firestore, orgId: string) =>\n outreachOrgCollection(firestore, orgId, 'domainIntel')\nconst statsCollection = (firestore: Firestore, orgId: string) =>\n outreachOrgCollection(firestore, orgId, 'gatewayStats')\n\n/**\n * The domain intel for every address, keyed by address — fresh from the\n * store, or looked up and stored when missing or older than the TTL. An\n * address with no domain, and one whose domain could not be looked up and\n * was never known, answers `null`.\n */\nexport async function readOutreachDomainIntel(\n firestore: Firestore,\n orgId: string,\n emails: readonly string[],\n input: OutreachDomainIntelReadInput,\n): Promise<Map<string, OutreachDomainIntel | null>> {\n const answers = new Map<string, OutreachDomainIntel | null>()\n const domainOf = new Map<string, string>()\n for (const email of emails) {\n const domain = outreachEmailDomain(email)\n if (domain) domainOf.set(email, domain)\n else answers.set(email, null)\n }\n const domains = [...new Set(domainOf.values())]\n if (!domains.length) return answers\n const collection = intelCollection(firestore, orgId)\n const known = new Map<string, OutreachDomainIntel | null>()\n try {\n for (let start = 0; start < domains.length; start += GET_ALL_CHUNK) {\n const chunk = domains.slice(start, start + GET_ALL_CHUNK)\n const snapshots = await firestore.getAll(...chunk.map((domain) => collection.doc(domain)))\n chunk.forEach((domain, index) => {\n const snapshot = snapshots[index]\n known.set(domain, readStoredOutreachDomainIntel(domain, snapshot?.exists ? snapshot.data() : undefined))\n })\n }\n } catch (error) {\n console.error('[outreach] domain intel lookup failed; reading as unchecked', error)\n for (const email of domainOf.keys()) answers.set(email, null)\n return answers\n }\n await Promise.all(\n domains.map(async (domain) => {\n const stored = known.get(domain) ?? null\n if (stored && stored.resolvedAtMs + OUTREACH_DOMAIN_INTEL_TTL_MS > input.nowMs) return\n let resolved: { mx: string[]; gateway: OutreachMailGateway }\n try {\n resolved = await resolveOutreachDomainMx(input.resolveMx, domain)\n } catch (error) {\n // A resolver that could not answer: the stale answer stands, and a\n // domain never looked up is unchecked.\n console.error(`[outreach] the MX of ${domain} could not be looked up`, error)\n return\n }\n const fresh: OutreachDomainIntel = {\n domain,\n mx: resolved.mx,\n gateway: resolved.gateway,\n resolvedAtMs: input.nowMs,\n sent: stored?.sent ?? 0,\n delivered: stored?.delivered ?? 0,\n blocked: stored?.blocked ?? 0,\n lastBlockedAtMs: stored?.lastBlockedAtMs ?? null,\n updatedAtMs: input.nowMs,\n }\n known.set(domain, fresh)\n try {\n await collection.doc(domain).set(\n { domain, mx: fresh.mx, gateway: fresh.gateway, resolvedAtMs: fresh.resolvedAtMs, updatedAtMs: fresh.updatedAtMs },\n { merge: true },\n )\n } catch (error) {\n // The cache is a convenience; the answer is already in hand.\n console.error(`[outreach] the MX of ${domain} could not be cached`, error)\n }\n }),\n )\n for (const [email, domain] of domainOf) answers.set(email, known.get(domain) ?? null)\n return answers\n}\n\n/** The ledger of each gateway named, keyed by gateway; a gateway with no ledger yet answers `null`. */\nexport async function readOutreachGatewayStats(\n firestore: Firestore,\n orgId: string,\n gateways: readonly OutreachMailGateway[],\n): Promise<Map<OutreachMailGateway, OutreachGatewayStats | null>> {\n const answers = new Map<OutreachMailGateway, OutreachGatewayStats | null>()\n const wanted = [...new Set(gateways)]\n if (!wanted.length) return answers\n const collection = statsCollection(firestore, orgId)\n const snapshots = await firestore.getAll(...wanted.map((gateway) => collection.doc(gateway)))\n wanted.forEach((gateway, index) => {\n const snapshot = snapshots[index]\n answers.set(gateway, readStoredOutreachGatewayStats(gateway, snapshot?.exists ? snapshot.data() : undefined))\n })\n return answers\n}\n\n/** What the gates and the chip read for one domain: its gateway and that gateway's recent record here. */\nexport function outreachGatewayStandingOf(\n intel: OutreachDomainIntel,\n stats: OutreachGatewayStats | null,\n nowMs: number,\n): OutreachGatewayStanding {\n const week = outreachGatewayWindow(stats?.days, nowMs, OUTREACH_GATEWAY_CHIP_DAYS)\n const month = outreachGatewayWindow(stats?.days, nowMs, OUTREACH_GATEWAY_WINDOW_DAYS)\n return {\n gateway: intel.gateway,\n blocked7: week.blocked,\n delivered7: week.delivered,\n blocked30: month.blocked,\n delivered30: month.delivered,\n }\n}\n\n/**\n * The gateway standing of every address, keyed by address: the domain\n * intel read (and refreshed) above, joined to the ledger of each gateway it\n * names. `null` for an address whose domain could not be checked.\n */\nexport async function readOutreachGatewayStandings(\n firestore: Firestore,\n orgId: string,\n emails: readonly string[],\n input: OutreachDomainIntelReadInput,\n): Promise<Map<string, OutreachGatewayStanding | null>> {\n const intel = await readOutreachDomainIntel(firestore, orgId, emails, input)\n const gateways = [...intel.values()]\n .filter((entry): entry is OutreachDomainIntel => entry !== null)\n .map((entry) => entry.gateway)\n let stats: Map<OutreachMailGateway, OutreachGatewayStats | null>\n try {\n stats = await readOutreachGatewayStats(firestore, orgId, gateways)\n } catch (error) {\n console.error('[outreach] gateway ledger lookup failed; reading as unchecked', error)\n return new Map(emails.map((email) => [email, null]))\n }\n const answers = new Map<string, OutreachGatewayStanding | null>()\n for (const email of emails) {\n const entry = intel.get(email) ?? null\n answers.set(email, entry ? outreachGatewayStandingOf(entry, stats.get(entry.gateway) ?? null, input.nowMs) : null)\n }\n return answers\n}\n\n/** What a send met at a gateway, as the ledger counts it. */\nexport type OutreachGatewayOutcome = 'sent' | 'delivered' | 'blocked'\n\n/**\n * Counts an outcome on the domain's intel and on the gateway's ledger — see\n * the module note. Never throws: the send or the bounce it records has\n * already happened, and a lost count understates a gateway while a thrown\n * one loses the step that called.\n */\nexport async function recordOutreachGatewayOutcome(\n firestore: Firestore,\n orgId: string,\n input: { domain: string; gateway: OutreachMailGateway; outcome: OutreachGatewayOutcome; count?: number; atMs: number },\n): Promise<void> {\n const domain = normalizeOutreachDomain(input.domain)\n const by = count(input.count ?? 1)\n if (!domain || !by) return\n const intelRef = intelCollection(firestore, orgId).doc(domain)\n const statsRef = statsCollection(firestore, orgId).doc(input.gateway)\n const day = outreachGatewayDay(input.atMs)\n try {\n await firestore.runTransaction(async (transaction) => {\n const [intelSnapshot, statsSnapshot] = await Promise.all([transaction.get(intelRef), transaction.get(statsRef)])\n const intel = readStoredOutreachDomainIntel(domain, intelSnapshot.exists ? intelSnapshot.data() : undefined)\n const stats = readStoredOutreachGatewayStats(input.gateway, statsSnapshot.exists ? statsSnapshot.data() : undefined)\n const blockedAt = input.outcome === 'blocked' ? input.atMs : null\n const nextIntel: OutreachDomainIntel = {\n domain,\n // A domain never looked up — enrolled before the intel existed —\n // is written with what the bounce said, and looked up on its next read.\n mx: intel?.mx ?? [],\n gateway: intel?.gateway ?? input.gateway,\n resolvedAtMs: intel?.resolvedAtMs ?? 0,\n sent: (intel?.sent ?? 0) + (input.outcome === 'sent' ? by : 0),\n delivered: (intel?.delivered ?? 0) + (input.outcome === 'delivered' ? by : 0),\n blocked: (intel?.blocked ?? 0) + (input.outcome === 'blocked' ? by : 0),\n lastBlockedAtMs: Math.max(intel?.lastBlockedAtMs ?? 0, blockedAt ?? 0) || null,\n updatedAtMs: input.atMs,\n }\n const days = pruneOutreachGatewayDays(stats?.days, input.atMs, OUTREACH_GATEWAY_WINDOW_DAYS)\n const today = days[day] ?? { sent: 0, delivered: 0, blocked: 0 }\n days[day] = { ...today, [input.outcome]: today[input.outcome] + by }\n const nextStats: OutreachGatewayStats = {\n gateway: input.gateway,\n sent: (stats?.sent ?? 0) + (input.outcome === 'sent' ? by : 0),\n delivered: (stats?.delivered ?? 0) + (input.outcome === 'delivered' ? by : 0),\n blocked: (stats?.blocked ?? 0) + (input.outcome === 'blocked' ? by : 0),\n lastBlockedAtMs: Math.max(stats?.lastBlockedAtMs ?? 0, blockedAt ?? 0) || null,\n days,\n updatedAtMs: input.atMs,\n }\n transaction.set(intelRef, nextIntel)\n transaction.set(statsRef, nextStats)\n })\n } catch (error) {\n console.error(`[outreach] the gateway ledger could not count a ${input.outcome} at ${domain}`, error)\n }\n}\n"],"names":["classifyOutreachMailGateway","OUTREACH_DOMAIN_INTEL_TTL_MS","OUTREACH_GATEWAY_CHIP_DAYS","OUTREACH_GATEWAY_WINDOW_DAYS","OUTREACH_MAIL_GATEWAYS","outreachGatewayDay","outreachGatewayWindow","pruneOutreachGatewayDays","normalizeOutreachDomain","outreachEmailDomain","outreachOrgCollection","GET_ALL_CHUNK","NO_MX_CODES","Set","count","value","number","Number","isFinite","Math","max","floor","ms","gatewayOf","includes","readStoredOutreachDomainIntel","id","data","gateway","domain","mx","Array","isArray","filter","entry","resolvedAtMs","sent","delivered","blocked","lastBlockedAtMs","updatedAtMs","readStoredOutreachGatewayStats","days","raw","day","counts","Object","entries","resolveOutreachDomainMx","resolveMx","records","error","code","String","toUpperCase","has","record","exchange","sort","a","b","priority","map","trim","toLowerCase","replace","intelCollection","firestore","orgId","statsCollection","readOutreachDomainIntel","emails","input","known","answers","Map","domainOf","email","set","domains","values","length","collection","start","chunk","slice","snapshots","getAll","doc","forEach","index","snapshot","exists","undefined","console","keys","Promise","all","stored","get","nowMs","resolved","fresh","merge","readOutreachGatewayStats","gateways","wanted","outreachGatewayStandingOf","intel","stats","week","month","blocked7","delivered7","blocked30","delivered30","readOutreachGatewayStandings","recordOutreachGatewayOutcome","by","intelRef","statsRef","atMs","runTransaction","transaction","intelSnapshot","statsSnapshot","blockedAt","outcome","nextIntel","today","nextStats"],"mappings":"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"}