@aglyn/plugins-outreach 1.0.0-beta.264 → 1.0.0-beta.268
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +15 -15
- package/src/lib/engine/mailbox-notice.d.ts +9 -0
- package/src/lib/engine/mailbox-notice.js +23 -4
- package/src/lib/engine/mailbox-notice.js.map +1 -1
- package/src/lib/runtime/platform-runtime-deps.js +5 -0
- package/src/lib/runtime/platform-runtime-deps.js.map +1 -1
- package/src/lib/runtime/sync-job.js +19 -9
- package/src/lib/runtime/sync-job.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-outreach",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.268",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,24 +25,24 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-next": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
38
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
39
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
40
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.268",
|
|
29
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.268",
|
|
30
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.268",
|
|
31
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.268",
|
|
32
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.268",
|
|
33
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.268",
|
|
34
|
+
"@aglyn/shared-util-email": "1.0.0-beta.268",
|
|
35
|
+
"@aglyn/shared-util-http": "1.0.0-beta.268",
|
|
36
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.268",
|
|
37
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.268",
|
|
38
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.268",
|
|
39
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.268",
|
|
40
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.268",
|
|
41
41
|
"@swc/helpers": "0.5.23"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@mui/material": "^9.3.1",
|
|
45
|
-
"firebase": "^
|
|
45
|
+
"firebase": "^13.0.0",
|
|
46
46
|
"firebase-admin": "^14.3.0",
|
|
47
47
|
"next": "^16.3.3",
|
|
48
48
|
"react": "^19.2.8"
|
|
@@ -35,6 +35,15 @@ export interface OutreachMailboxNotice {
|
|
|
35
35
|
export interface OutreachMailboxNoticeEmail {
|
|
36
36
|
subject: string;
|
|
37
37
|
text: string;
|
|
38
|
+
/** What happened and what to do, without the link: the designed email's body. */
|
|
39
|
+
lead: string;
|
|
40
|
+
/** "Label: value" lines for the designed email's details panel. */
|
|
41
|
+
details: string;
|
|
42
|
+
/** The Mailboxes page, when there is one to link. */
|
|
43
|
+
url: string | null;
|
|
44
|
+
urlLabel: string;
|
|
45
|
+
/** Why this reader is getting it. */
|
|
46
|
+
reason: string;
|
|
38
47
|
}
|
|
39
48
|
/** The mailbox as the notice names it: `Avery Quinn <avery@example.com>`, the send-as beside it when it differs. */
|
|
40
49
|
export declare function outreachMailboxNoticeName(mailbox: OutreachMailboxNotice['mailbox']): string;
|
|
@@ -52,16 +52,35 @@ function waitingSentence(waiting) {
|
|
|
52
52
|
const subject = paused ? `Sequences paused your mailbox ${notice.mailbox.email}` : `Your mailbox ${notice.mailbox.email} needs reconnecting`;
|
|
53
53
|
const action = paused ? 'Re-check the addresses in the sequences it sends, then open Mailboxes and select Resume.' : 'Open Mailboxes and select Reconnect to connect it again.';
|
|
54
54
|
const where = options.mailboxesUrl ? `${action}\n\n${options.mailboxesUrl}` : `${action} Mailboxes is under Sequences in your workspace’s console.`;
|
|
55
|
+
const reason = 'You are getting this because the mailbox is yours, or you manage the organization it belongs to.';
|
|
56
|
+
const opening = paused ? `Sequences paused the mailbox ${name}.` : `The mailbox ${name} needs reconnecting.`;
|
|
57
|
+
const message = String((_notice_message = notice.message) != null ? _notice_message : '').trim();
|
|
55
58
|
const text = [
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
opening,
|
|
60
|
+
message,
|
|
58
61
|
waitingSentence(notice.waiting),
|
|
59
62
|
where,
|
|
60
|
-
|
|
63
|
+
reason
|
|
61
64
|
].filter(Boolean).join('\n\n');
|
|
65
|
+
const waiting = Math.max(0, Math.floor(Number(notice.waiting) || 0));
|
|
66
|
+
const details = [
|
|
67
|
+
`Mailbox: ${notice.mailbox.email}`,
|
|
68
|
+
`Status: ${paused ? 'Paused by Sequences' : 'Needs reconnecting'}`,
|
|
69
|
+
`Enrollments waiting: ${waiting.toLocaleString('en-US')}`
|
|
70
|
+
].join('\n');
|
|
71
|
+
const lead = [
|
|
72
|
+
message || opening,
|
|
73
|
+
waitingSentence(notice.waiting),
|
|
74
|
+
action
|
|
75
|
+
].join(' ');
|
|
62
76
|
return {
|
|
63
77
|
subject,
|
|
64
|
-
text
|
|
78
|
+
text,
|
|
79
|
+
lead,
|
|
80
|
+
details,
|
|
81
|
+
url: options.mailboxesUrl,
|
|
82
|
+
urlLabel: paused ? 'Open Mailboxes to resume' : 'Open Mailboxes to reconnect',
|
|
83
|
+
reason
|
|
65
84
|
};
|
|
66
85
|
}
|
|
67
86
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/engine/mailbox-notice.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 MAILBOX'S OWNER IS TOLD (AGL-3244).\n *\n * A mailbox that pauses itself, or that Google stops accepting, holds every\n * enrollment on it until a person acts — and the person is the member whose\n * mailbox it is, who is not watching the Mailboxes page. So the moment the\n * runtime judges it, they are emailed: the engine's own sentence for the\n * pause, the mailbox by name, how many enrollments are waiting on it, and\n * the link to the page where Resume and Reconnect live.\n *\n * Plain text, one screen long, and the reason in the first line: it is read\n * on a phone between meetings.\n *==========================================*/\n\nexport type OutreachMailboxNoticeKind = 'auto_pause' | 'reconnect_required'\n\nexport interface OutreachMailboxNotice {\n kind: OutreachMailboxNoticeKind\n mailbox: {\n /** The account's own address. */\n email: string\n /** The address mail goes out as, when it is not the account's. */\n sendAs: string\n displayName: string\n }\n /**\n * For a pause, the engine's own sentence — `autoPause.message`; for a\n * reconnect, {@link outreachReconnectRequiredSentence}.\n */\n message: string\n /** Enrollments active on the mailbox, waiting for it to send again. */\n waiting: number\n}\n\nexport interface OutreachMailboxNoticeEmail {\n subject: string\n text: string\n}\n\n/** The mailbox as the notice names it: `Avery Quinn <avery@example.com>`, the send-as beside it when it differs. */\nexport function outreachMailboxNoticeName(mailbox: OutreachMailboxNotice['mailbox']): string {\n const name = String(mailbox.displayName ?? '').trim()\n const own = name ? `${name} <${mailbox.email}>` : mailbox.email\n const sendAs = String(mailbox.sendAs ?? '')\n .trim()\n .toLowerCase()\n return sendAs && sendAs !== String(mailbox.email).trim().toLowerCase() ? `${own}, sending as ${sendAs}` : own\n}\n\n/**\n * What the notice says when the provider stopped accepting the mailbox's\n * connection: Google's, or Microsoft's for a Microsoft 365 mailbox (AGL-3489).\n */\nexport function outreachReconnectRequiredSentence(\n errorCode: string | null | undefined,\n provider: string | null | undefined = 'google',\n): string {\n const code = String(errorCode ?? '').trim()\n const name = provider === 'microsoft' ? 'Microsoft' : 'Google'\n return (\n `${name} stopped accepting this mailbox’s connection` +\n (code ? ` (${code})` : '') +\n ` — the password changed, access was removed from the ${name} account, or an administrator revoked it. ` +\n 'Nothing sends from it until it is connected again.'\n )\n}\n\nfunction waitingSentence(waiting: number): string {\n const count = Math.max(0, Math.floor(Number(waiting) || 0))\n if (count === 0) return 'No enrollment is waiting on it right now.'\n return count === 1\n ? '1 enrollment is active on it and waiting; its next email goes out once the mailbox sends again.'\n : `${count} enrollments are active on it and waiting; their next emails go out once the mailbox sends again.`\n}\n\n/** The email, composed — see the module note. */\nexport function composeOutreachMailboxNotice(\n notice: OutreachMailboxNotice,\n options: { mailboxesUrl: string | null },\n): OutreachMailboxNoticeEmail {\n const name = outreachMailboxNoticeName(notice.mailbox)\n const paused = notice.kind === 'auto_pause'\n const subject = paused\n ? `Sequences paused your mailbox ${notice.mailbox.email}`\n : `Your mailbox ${notice.mailbox.email} needs reconnecting`\n const action = paused\n ? 'Re-check the addresses in the sequences it sends, then open Mailboxes and select Resume.'\n : 'Open Mailboxes and select Reconnect to connect it again.'\n const where = options.mailboxesUrl\n ? `${action}\\n\\n${options.mailboxesUrl}`\n : `${action} Mailboxes is under Sequences in your workspace’s console.`\n const
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/engine/mailbox-notice.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 MAILBOX'S OWNER IS TOLD (AGL-3244).\n *\n * A mailbox that pauses itself, or that Google stops accepting, holds every\n * enrollment on it until a person acts — and the person is the member whose\n * mailbox it is, who is not watching the Mailboxes page. So the moment the\n * runtime judges it, they are emailed: the engine's own sentence for the\n * pause, the mailbox by name, how many enrollments are waiting on it, and\n * the link to the page where Resume and Reconnect live.\n *\n * Plain text, one screen long, and the reason in the first line: it is read\n * on a phone between meetings.\n *==========================================*/\n\nexport type OutreachMailboxNoticeKind = 'auto_pause' | 'reconnect_required'\n\nexport interface OutreachMailboxNotice {\n kind: OutreachMailboxNoticeKind\n mailbox: {\n /** The account's own address. */\n email: string\n /** The address mail goes out as, when it is not the account's. */\n sendAs: string\n displayName: string\n }\n /**\n * For a pause, the engine's own sentence — `autoPause.message`; for a\n * reconnect, {@link outreachReconnectRequiredSentence}.\n */\n message: string\n /** Enrollments active on the mailbox, waiting for it to send again. */\n waiting: number\n}\n\nexport interface OutreachMailboxNoticeEmail {\n subject: string\n text: string\n /** What happened and what to do, without the link: the designed email's body. */\n lead: string\n /** \"Label: value\" lines for the designed email's details panel. */\n details: string\n /** The Mailboxes page, when there is one to link. */\n url: string | null\n urlLabel: string\n /** Why this reader is getting it. */\n reason: string\n}\n\n/** The mailbox as the notice names it: `Avery Quinn <avery@example.com>`, the send-as beside it when it differs. */\nexport function outreachMailboxNoticeName(mailbox: OutreachMailboxNotice['mailbox']): string {\n const name = String(mailbox.displayName ?? '').trim()\n const own = name ? `${name} <${mailbox.email}>` : mailbox.email\n const sendAs = String(mailbox.sendAs ?? '')\n .trim()\n .toLowerCase()\n return sendAs && sendAs !== String(mailbox.email).trim().toLowerCase() ? `${own}, sending as ${sendAs}` : own\n}\n\n/**\n * What the notice says when the provider stopped accepting the mailbox's\n * connection: Google's, or Microsoft's for a Microsoft 365 mailbox (AGL-3489).\n */\nexport function outreachReconnectRequiredSentence(\n errorCode: string | null | undefined,\n provider: string | null | undefined = 'google',\n): string {\n const code = String(errorCode ?? '').trim()\n const name = provider === 'microsoft' ? 'Microsoft' : 'Google'\n return (\n `${name} stopped accepting this mailbox’s connection` +\n (code ? ` (${code})` : '') +\n ` — the password changed, access was removed from the ${name} account, or an administrator revoked it. ` +\n 'Nothing sends from it until it is connected again.'\n )\n}\n\nfunction waitingSentence(waiting: number): string {\n const count = Math.max(0, Math.floor(Number(waiting) || 0))\n if (count === 0) return 'No enrollment is waiting on it right now.'\n return count === 1\n ? '1 enrollment is active on it and waiting; its next email goes out once the mailbox sends again.'\n : `${count} enrollments are active on it and waiting; their next emails go out once the mailbox sends again.`\n}\n\n/** The email, composed — see the module note. */\nexport function composeOutreachMailboxNotice(\n notice: OutreachMailboxNotice,\n options: { mailboxesUrl: string | null },\n): OutreachMailboxNoticeEmail {\n const name = outreachMailboxNoticeName(notice.mailbox)\n const paused = notice.kind === 'auto_pause'\n const subject = paused\n ? `Sequences paused your mailbox ${notice.mailbox.email}`\n : `Your mailbox ${notice.mailbox.email} needs reconnecting`\n const action = paused\n ? 'Re-check the addresses in the sequences it sends, then open Mailboxes and select Resume.'\n : 'Open Mailboxes and select Reconnect to connect it again.'\n const where = options.mailboxesUrl\n ? `${action}\\n\\n${options.mailboxesUrl}`\n : `${action} Mailboxes is under Sequences in your workspace’s console.`\n const reason = 'You are getting this because the mailbox is yours, or you manage the organization it belongs to.'\n const opening = paused ? `Sequences paused the mailbox ${name}.` : `The mailbox ${name} needs reconnecting.`\n const message = String(notice.message ?? '').trim()\n const text = [opening, message, waitingSentence(notice.waiting), where, reason].filter(Boolean).join('\\n\\n')\n const waiting = Math.max(0, Math.floor(Number(notice.waiting) || 0))\n const details = [\n `Mailbox: ${notice.mailbox.email}`,\n `Status: ${paused ? 'Paused by Sequences' : 'Needs reconnecting'}`,\n `Enrollments waiting: ${waiting.toLocaleString('en-US')}`,\n ].join('\\n')\n const lead = [message || opening, waitingSentence(notice.waiting), action].join(' ')\n return {\n subject,\n text,\n lead,\n details,\n url: options.mailboxesUrl,\n urlLabel: paused ? 'Open Mailboxes to resume' : 'Open Mailboxes to reconnect',\n reason,\n }\n}\n"],"names":["outreachMailboxNoticeName","mailbox","name","String","displayName","trim","own","email","sendAs","toLowerCase","outreachReconnectRequiredSentence","errorCode","provider","code","waitingSentence","waiting","count","Math","max","floor","Number","composeOutreachMailboxNotice","notice","options","paused","kind","subject","action","where","mailboxesUrl","reason","opening","message","text","filter","Boolean","join","details","toLocaleString","lead","url","urlLabel"],"mappings":"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"}
|
|
@@ -176,6 +176,11 @@ import { createOutreachUnsubscribeRoute } from "./unsubscribe-route.js";
|
|
|
176
176
|
includeAdmins: true,
|
|
177
177
|
subject: email.subject,
|
|
178
178
|
text: email.text,
|
|
179
|
+
lead: email.lead,
|
|
180
|
+
details: email.details,
|
|
181
|
+
url: email.url,
|
|
182
|
+
urlLabel: email.urlLabel,
|
|
183
|
+
reason: email.reason,
|
|
179
184
|
context: 'outreach-mailbox-notice'
|
|
180
185
|
});
|
|
181
186
|
if (!result.sent) console.warn(`[outreach] the mailbox owner was not emailed: ${result.reason}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/runtime/platform-runtime-deps.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\nimport { SALES_TOPIC_ID } from '../model/sales-topic'\nimport { platformMailDnsResolver } from '@aglyn/tenant-data-admin/server/email-deliverability'\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { isPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport { pluginRecordTimelineWriter } from '@aglyn/aglyn/plugin-manager/plugin-record-timeline'\nimport {\n creditConversion,\n creditConversionOutcome,\n recordConversionClick,\n} from '@aglyn/aglyn/plugin-manager/plugin-conversion-credit'\nimport { suppressEmail } from '@aglyn/tenant-data-admin/server/email-suppression'\nimport { recordTopicOptOut } from '@aglyn/tenant-data-admin/server/topic-subscriptions'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { sendOrgMemberNotice } from '@aglyn/tenant-data-admin/server/org-member-notice'\nimport { logOrgActivity } from '@aglyn/tenant-data-admin/server/organizations'\nimport { filterEnabledPluginsByReleaseFlags } from '@aglyn/tenant-data-admin/server/release-flags'\nimport { resolveTrackingLinkOrigin } from '@aglyn/tenant-data-admin/server/tracking-hosts'\nimport { sendingWorkspaceFor } from '@aglyn/tenant-data-admin/server/outbound-send-review'\nimport { screenTenantMessage } from '@aglyn/shared-util-email/outbound-screen-gate'\nimport { stampRecordEmailState } from '@aglyn/aglyn/plugin-manager/plugin-record-email-state'\nimport { OUTREACH_PLUGIN_ID } from '../constants/bundle-common'\nimport { composeOutreachMailboxNotice } from '../engine/mailbox-notice'\nimport { openOutreachMailboxClient } from '../mailboxes/mailbox-transport'\nimport { canonicalConsoleOrigin } from '../mailboxes/oauth-redirect'\nimport type { PluginWebApiHandler } from '@aglyn/aglyn/server'\nimport type { PluginPersonEraser } from '@aglyn/aglyn/plugin-manager/plugin-person-erasure'\nimport { outreachShortLinkUrl } from './click-link'\nimport { createOutreachClickRoute, createOutreachShortLinkRoute } from './click-route'\nimport { createOutreachPersonEraser } from './person-erasure'\nimport type { OutreachCampaignCredit, OutreachRuntimeDeps } from './runtime-deps'\nimport { outreachUnsubscribeUrl } from './unsubscribe-link'\nimport { createOutreachUnsubscribeRoute } from './unsubscribe-route'\n\n/**\n * What a sequence produced (AGL-3254), handed to whichever plugin credits\n * outcomes (`plugin-conversion-credit`): the same contract every form,\n * booking and campaign email is credited through, so a sequence's numbers\n * and a campaign send's are two readings of one rule. A workspace whose\n * plugins credit nothing credits nothing here either; the seam never throws.\n *\n * The campaign a sequence is filed under is what its mail is credited to,\n * and the sequence and the enrollment ride with it as the mail's own facts.\n */\nexport function platformOutreachCampaignCredit(): OutreachCampaignCredit {\n return {\n async credit({ hostId, orgId, campaignIds, outcome, atMs }) {\n await creditConversionOutcome({ hostId, orgId, containerIds: campaignIds, outcome, atMs })\n },\n async attributeRecord({ hostId, kind, refId, campaignId, sequenceId, enrollmentId, atMs }) {\n await creditConversion({\n hostId,\n kind,\n refId,\n click: { hostId, creditTo: campaignId, atMs, via: { sequenceId, enrollmentId } },\n convertedAtMs: atMs,\n })\n },\n async recordTouch({ hostId, email, campaignId, sequenceId, enrollmentId, atMs }) {\n await recordConversionClick({\n email,\n hostId,\n creditTo: campaignId,\n atMs,\n via: { sequenceId, enrollmentId },\n })\n },\n }\n}\n\n/**\n * The platform's own reach for the sending runtime (AGL-2981). Loaded only\n * when a job runs or the unsubscribe route answers — never at boot — so the\n * console pays for these modules when Outreach does work.\n */\nexport function platformOutreachRuntimeDeps(): OutreachRuntimeDeps {\n const firestore = () => firebaseAdmin.app().firestore()\n return {\n firestore,\n now: Date.now,\n random: Math.random,\n campaignCredit: platformOutreachCampaignCredit(),\n openMailbox: (mailboxId) => openOutreachMailboxClient(firestore(), { mailboxId }),\n // The domain's MX (AGL-3326) and, for a domain with none, its address\n // record — through the platform's pinned resolver (AGL-3328), which\n // asks twice before it says a domain takes no mail.\n resolveMx: (domain) => platformMailDnsResolver().resolveMx(domain),\n resolveAddress: (domain) => platformMailDnsResolver().resolveAddress?.(domain) ?? Promise.resolve(false),\n async orgRefusal(orgId, org) {\n if (!isPluginEnabled(org as { enabledPlugins?: string[] }, OUTREACH_PLUGIN_ID)) return 'plugin-disabled'\n if (!checkEntitlement(org, 'outreach')) return 'not-entitled'\n const released = await filterEnabledPluginsByReleaseFlags([OUTREACH_PLUGIN_ID], { orgId })\n return released.length ? null : 'not-released'\n },\n async sendRefusal({ org, uid }) {\n const locked = await getLockdownVerdict({ org, uid, intent: 'write' })\n return locked ? `locked:${locked.scope}` : null\n },\n timeline: () => pluginRecordTimelineWriter()?.writer ?? null,\n logOrgActivity: (orgId, actor, action, target) => logOrgActivity(orgId, actor, action, target),\n async optOutOfSalesTopic({ hostId, email }) {\n await recordTopicOptOut(hostId, email, SALES_TOPIC_ID, { firestore: firestore() })\n },\n async suppressBouncedEmail({ email, hostId }) {\n // The runtime stamps the record itself, with the richer verdict — the\n // domain block, the enrollment — so the list's own stamp stands down.\n await suppressEmail({ email, reason: 'bounce', context: 'outreach', hostId, stampRecord: false, firestore: firestore() })\n },\n async stampRecordEmailState(stamp) {\n await stampRecordEmailState(stamp)\n },\n async notifyMailboxOwner(notice) {\n // The Mailboxes page, by the organization's slug — the page Resume and\n // Reconnect live on (AGL-3244). Without a slug or a console origin the\n // notice still goes, and says where the page is instead.\n const org = await firestore().collection('orgs').doc(notice.orgId).get()\n const slug = typeof org.get('slug') === 'string' ? String(org.get('slug')) : ''\n const origin = canonicalConsoleOrigin()\n const mailboxesUrl = slug && origin ? `${origin}/${encodeURIComponent(slug)}/outreach/mailboxes` : null\n const email = composeOutreachMailboxNotice(notice, { mailboxesUrl })\n const result = await sendOrgMemberNotice({\n orgId: notice.orgId,\n uids: [notice.connectedByUid],\n includeAdmins: true,\n subject: email.subject,\n text: email.text,\n context: 'outreach-mailbox-notice',\n })\n if (!result.sent) console.warn(`[outreach] the mailbox owner was not emailed: ${result.reason}`)\n },\n unsubscribeUrl: (target) => outreachUnsubscribeUrl({ origin: canonicalConsoleOrigin(), target }),\n clickLinkUrl: (linkId, trackingOrigin) =>\n outreachShortLinkUrl({ origin: canonicalConsoleOrigin(), linkId, trackingOrigin }),\n // The sending domain's own `links.` host once the organization has\n // verified it (AGL-3306). A static import like every other\n // `tenant-data-admin` module here: a dynamic one marks the whole library\n // lazy-loaded, and `@nx/enforce-module-boundaries` then refuses every\n // static import of it across the plugin and the console.\n clickLinkOrigin({ orgId, senderAddress }) {\n return resolveTrackingLinkOrigin(firestore(), orgId, senderAddress)\n },\n // The one phishing screen every tenant message passes (AGL-3356). The\n // review module's import installs its store, so a hold files the same\n // abuse-queue row a held newsletter or campaign does.\n async screenMessage({ orgId, org, hostId, host, subject, text, fromName, fromAddress }) {\n const refusal = await screenTenantMessage(\n {\n workspace: sendingWorkspaceFor({ hostId, orgId, org, host }),\n subject,\n fromName,\n fromAddress: fromAddress ?? null,\n bodies: [text],\n context: 'outreach sequence',\n },\n 'outreach email',\n )\n return refusal?.detail ?? null\n },\n }\n}\n\n/** The unsubscribe route on the platform's own reach. */\nexport function platformOutreachUnsubscribeRoute(): PluginWebApiHandler {\n return createOutreachUnsubscribeRoute(platformOutreachRuntimeDeps())\n}\n\n/** The click-tracking redirect on the platform's own reach (AGL-3239). */\nexport function platformOutreachClickRoute(): PluginWebApiHandler {\n return createOutreachClickRoute(platformOutreachRuntimeDeps())\n}\n\n/** The short tracking link's redirect on the platform's own reach (AGL-3297). */\nexport function platformOutreachShortLinkRoute(): PluginWebApiHandler {\n return createOutreachShortLinkRoute(platformOutreachRuntimeDeps())\n}\n\n/** The person eraser on the platform's own Firestore. */\nexport function platformOutreachPersonEraser(): PluginPersonEraser {\n return createOutreachPersonEraser({ firestore: () => firebaseAdmin.app().firestore() })\n}\n"],"names":["SALES_TOPIC_ID","platformMailDnsResolver","checkEntitlement","isPluginEnabled","pluginRecordTimelineWriter","creditConversion","creditConversionOutcome","recordConversionClick","suppressEmail","recordTopicOptOut","firebaseAdmin","getLockdownVerdict","sendOrgMemberNotice","logOrgActivity","filterEnabledPluginsByReleaseFlags","resolveTrackingLinkOrigin","sendingWorkspaceFor","screenTenantMessage","stampRecordEmailState","OUTREACH_PLUGIN_ID","composeOutreachMailboxNotice","openOutreachMailboxClient","canonicalConsoleOrigin","outreachShortLinkUrl","createOutreachClickRoute","createOutreachShortLinkRoute","createOutreachPersonEraser","outreachUnsubscribeUrl","createOutreachUnsubscribeRoute","platformOutreachCampaignCredit","credit","hostId","orgId","campaignIds","outcome","atMs","containerIds","attributeRecord","kind","refId","campaignId","sequenceId","enrollmentId","click","creditTo","via","convertedAtMs","recordTouch","email","platformOutreachRuntimeDeps","firestore","app","now","Date","random","Math","campaignCredit","openMailbox","mailboxId","resolveMx","domain","resolveAddress","Promise","resolve","orgRefusal","org","released","length","sendRefusal","uid","locked","intent","scope","timeline","writer","actor","action","target","optOutOfSalesTopic","suppressBouncedEmail","reason","context","stampRecord","stamp","notifyMailboxOwner","notice","collection","doc","get","slug","String","origin","mailboxesUrl","encodeURIComponent","result","uids","connectedByUid","includeAdmins","subject","text","sent","console","warn","unsubscribeUrl","clickLinkUrl","linkId","trackingOrigin","clickLinkOrigin","senderAddress","screenMessage","host","fromName","fromAddress","refusal","workspace","bodies","detail","platformOutreachUnsubscribeRoute","platformOutreachClickRoute","platformOutreachShortLinkRoute","platformOutreachPersonEraser"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/runtime/platform-runtime-deps.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\nimport { SALES_TOPIC_ID } from '../model/sales-topic'\nimport { platformMailDnsResolver } from '@aglyn/tenant-data-admin/server/email-deliverability'\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { isPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport { pluginRecordTimelineWriter } from '@aglyn/aglyn/plugin-manager/plugin-record-timeline'\nimport {\n creditConversion,\n creditConversionOutcome,\n recordConversionClick,\n} from '@aglyn/aglyn/plugin-manager/plugin-conversion-credit'\nimport { suppressEmail } from '@aglyn/tenant-data-admin/server/email-suppression'\nimport { recordTopicOptOut } from '@aglyn/tenant-data-admin/server/topic-subscriptions'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { sendOrgMemberNotice } from '@aglyn/tenant-data-admin/server/org-member-notice'\nimport { logOrgActivity } from '@aglyn/tenant-data-admin/server/organizations'\nimport { filterEnabledPluginsByReleaseFlags } from '@aglyn/tenant-data-admin/server/release-flags'\nimport { resolveTrackingLinkOrigin } from '@aglyn/tenant-data-admin/server/tracking-hosts'\nimport { sendingWorkspaceFor } from '@aglyn/tenant-data-admin/server/outbound-send-review'\nimport { screenTenantMessage } from '@aglyn/shared-util-email/outbound-screen-gate'\nimport { stampRecordEmailState } from '@aglyn/aglyn/plugin-manager/plugin-record-email-state'\nimport { OUTREACH_PLUGIN_ID } from '../constants/bundle-common'\nimport { composeOutreachMailboxNotice } from '../engine/mailbox-notice'\nimport { openOutreachMailboxClient } from '../mailboxes/mailbox-transport'\nimport { canonicalConsoleOrigin } from '../mailboxes/oauth-redirect'\nimport type { PluginWebApiHandler } from '@aglyn/aglyn/server'\nimport type { PluginPersonEraser } from '@aglyn/aglyn/plugin-manager/plugin-person-erasure'\nimport { outreachShortLinkUrl } from './click-link'\nimport { createOutreachClickRoute, createOutreachShortLinkRoute } from './click-route'\nimport { createOutreachPersonEraser } from './person-erasure'\nimport type { OutreachCampaignCredit, OutreachRuntimeDeps } from './runtime-deps'\nimport { outreachUnsubscribeUrl } from './unsubscribe-link'\nimport { createOutreachUnsubscribeRoute } from './unsubscribe-route'\n\n/**\n * What a sequence produced (AGL-3254), handed to whichever plugin credits\n * outcomes (`plugin-conversion-credit`): the same contract every form,\n * booking and campaign email is credited through, so a sequence's numbers\n * and a campaign send's are two readings of one rule. A workspace whose\n * plugins credit nothing credits nothing here either; the seam never throws.\n *\n * The campaign a sequence is filed under is what its mail is credited to,\n * and the sequence and the enrollment ride with it as the mail's own facts.\n */\nexport function platformOutreachCampaignCredit(): OutreachCampaignCredit {\n return {\n async credit({ hostId, orgId, campaignIds, outcome, atMs }) {\n await creditConversionOutcome({ hostId, orgId, containerIds: campaignIds, outcome, atMs })\n },\n async attributeRecord({ hostId, kind, refId, campaignId, sequenceId, enrollmentId, atMs }) {\n await creditConversion({\n hostId,\n kind,\n refId,\n click: { hostId, creditTo: campaignId, atMs, via: { sequenceId, enrollmentId } },\n convertedAtMs: atMs,\n })\n },\n async recordTouch({ hostId, email, campaignId, sequenceId, enrollmentId, atMs }) {\n await recordConversionClick({\n email,\n hostId,\n creditTo: campaignId,\n atMs,\n via: { sequenceId, enrollmentId },\n })\n },\n }\n}\n\n/**\n * The platform's own reach for the sending runtime (AGL-2981). Loaded only\n * when a job runs or the unsubscribe route answers — never at boot — so the\n * console pays for these modules when Outreach does work.\n */\nexport function platformOutreachRuntimeDeps(): OutreachRuntimeDeps {\n const firestore = () => firebaseAdmin.app().firestore()\n return {\n firestore,\n now: Date.now,\n random: Math.random,\n campaignCredit: platformOutreachCampaignCredit(),\n openMailbox: (mailboxId) => openOutreachMailboxClient(firestore(), { mailboxId }),\n // The domain's MX (AGL-3326) and, for a domain with none, its address\n // record — through the platform's pinned resolver (AGL-3328), which\n // asks twice before it says a domain takes no mail.\n resolveMx: (domain) => platformMailDnsResolver().resolveMx(domain),\n resolveAddress: (domain) => platformMailDnsResolver().resolveAddress?.(domain) ?? Promise.resolve(false),\n async orgRefusal(orgId, org) {\n if (!isPluginEnabled(org as { enabledPlugins?: string[] }, OUTREACH_PLUGIN_ID)) return 'plugin-disabled'\n if (!checkEntitlement(org, 'outreach')) return 'not-entitled'\n const released = await filterEnabledPluginsByReleaseFlags([OUTREACH_PLUGIN_ID], { orgId })\n return released.length ? null : 'not-released'\n },\n async sendRefusal({ org, uid }) {\n const locked = await getLockdownVerdict({ org, uid, intent: 'write' })\n return locked ? `locked:${locked.scope}` : null\n },\n timeline: () => pluginRecordTimelineWriter()?.writer ?? null,\n logOrgActivity: (orgId, actor, action, target) => logOrgActivity(orgId, actor, action, target),\n async optOutOfSalesTopic({ hostId, email }) {\n await recordTopicOptOut(hostId, email, SALES_TOPIC_ID, { firestore: firestore() })\n },\n async suppressBouncedEmail({ email, hostId }) {\n // The runtime stamps the record itself, with the richer verdict — the\n // domain block, the enrollment — so the list's own stamp stands down.\n await suppressEmail({ email, reason: 'bounce', context: 'outreach', hostId, stampRecord: false, firestore: firestore() })\n },\n async stampRecordEmailState(stamp) {\n await stampRecordEmailState(stamp)\n },\n async notifyMailboxOwner(notice) {\n // The Mailboxes page, by the organization's slug — the page Resume and\n // Reconnect live on (AGL-3244). Without a slug or a console origin the\n // notice still goes, and says where the page is instead.\n const org = await firestore().collection('orgs').doc(notice.orgId).get()\n const slug = typeof org.get('slug') === 'string' ? String(org.get('slug')) : ''\n const origin = canonicalConsoleOrigin()\n const mailboxesUrl = slug && origin ? `${origin}/${encodeURIComponent(slug)}/outreach/mailboxes` : null\n const email = composeOutreachMailboxNotice(notice, { mailboxesUrl })\n const result = await sendOrgMemberNotice({\n orgId: notice.orgId,\n uids: [notice.connectedByUid],\n includeAdmins: true,\n subject: email.subject,\n text: email.text,\n lead: email.lead,\n details: email.details,\n url: email.url,\n urlLabel: email.urlLabel,\n reason: email.reason,\n context: 'outreach-mailbox-notice',\n })\n if (!result.sent) console.warn(`[outreach] the mailbox owner was not emailed: ${result.reason}`)\n },\n unsubscribeUrl: (target) => outreachUnsubscribeUrl({ origin: canonicalConsoleOrigin(), target }),\n clickLinkUrl: (linkId, trackingOrigin) =>\n outreachShortLinkUrl({ origin: canonicalConsoleOrigin(), linkId, trackingOrigin }),\n // The sending domain's own `links.` host once the organization has\n // verified it (AGL-3306). A static import like every other\n // `tenant-data-admin` module here: a dynamic one marks the whole library\n // lazy-loaded, and `@nx/enforce-module-boundaries` then refuses every\n // static import of it across the plugin and the console.\n clickLinkOrigin({ orgId, senderAddress }) {\n return resolveTrackingLinkOrigin(firestore(), orgId, senderAddress)\n },\n // The one phishing screen every tenant message passes (AGL-3356). The\n // review module's import installs its store, so a hold files the same\n // abuse-queue row a held newsletter or campaign does.\n async screenMessage({ orgId, org, hostId, host, subject, text, fromName, fromAddress }) {\n const refusal = await screenTenantMessage(\n {\n workspace: sendingWorkspaceFor({ hostId, orgId, org, host }),\n subject,\n fromName,\n fromAddress: fromAddress ?? null,\n bodies: [text],\n context: 'outreach sequence',\n },\n 'outreach email',\n )\n return refusal?.detail ?? null\n },\n }\n}\n\n/** The unsubscribe route on the platform's own reach. */\nexport function platformOutreachUnsubscribeRoute(): PluginWebApiHandler {\n return createOutreachUnsubscribeRoute(platformOutreachRuntimeDeps())\n}\n\n/** The click-tracking redirect on the platform's own reach (AGL-3239). */\nexport function platformOutreachClickRoute(): PluginWebApiHandler {\n return createOutreachClickRoute(platformOutreachRuntimeDeps())\n}\n\n/** The short tracking link's redirect on the platform's own reach (AGL-3297). */\nexport function platformOutreachShortLinkRoute(): PluginWebApiHandler {\n return createOutreachShortLinkRoute(platformOutreachRuntimeDeps())\n}\n\n/** The person eraser on the platform's own Firestore. */\nexport function platformOutreachPersonEraser(): PluginPersonEraser {\n return createOutreachPersonEraser({ firestore: () => firebaseAdmin.app().firestore() })\n}\n"],"names":["SALES_TOPIC_ID","platformMailDnsResolver","checkEntitlement","isPluginEnabled","pluginRecordTimelineWriter","creditConversion","creditConversionOutcome","recordConversionClick","suppressEmail","recordTopicOptOut","firebaseAdmin","getLockdownVerdict","sendOrgMemberNotice","logOrgActivity","filterEnabledPluginsByReleaseFlags","resolveTrackingLinkOrigin","sendingWorkspaceFor","screenTenantMessage","stampRecordEmailState","OUTREACH_PLUGIN_ID","composeOutreachMailboxNotice","openOutreachMailboxClient","canonicalConsoleOrigin","outreachShortLinkUrl","createOutreachClickRoute","createOutreachShortLinkRoute","createOutreachPersonEraser","outreachUnsubscribeUrl","createOutreachUnsubscribeRoute","platformOutreachCampaignCredit","credit","hostId","orgId","campaignIds","outcome","atMs","containerIds","attributeRecord","kind","refId","campaignId","sequenceId","enrollmentId","click","creditTo","via","convertedAtMs","recordTouch","email","platformOutreachRuntimeDeps","firestore","app","now","Date","random","Math","campaignCredit","openMailbox","mailboxId","resolveMx","domain","resolveAddress","Promise","resolve","orgRefusal","org","released","length","sendRefusal","uid","locked","intent","scope","timeline","writer","actor","action","target","optOutOfSalesTopic","suppressBouncedEmail","reason","context","stampRecord","stamp","notifyMailboxOwner","notice","collection","doc","get","slug","String","origin","mailboxesUrl","encodeURIComponent","result","uids","connectedByUid","includeAdmins","subject","text","lead","details","url","urlLabel","sent","console","warn","unsubscribeUrl","clickLinkUrl","linkId","trackingOrigin","clickLinkOrigin","senderAddress","screenMessage","host","fromName","fromAddress","refusal","workspace","bodies","detail","platformOutreachUnsubscribeRoute","platformOutreachClickRoute","platformOutreachShortLinkRoute","platformOutreachPersonEraser"],"mappings":"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"}
|
|
@@ -92,6 +92,7 @@ import { outreachUnsubscribeMailbox } from "./unsubscribe-link.js";
|
|
|
92
92
|
/** How long after its last send an enrollment's mail is still watched. */ export const OUTREACH_SYNC_WATCH_MS = 45 * 24 * 60 * 60000;
|
|
93
93
|
/** The most pages of one search a run reads. */ const SEARCH_PAGES = 5;
|
|
94
94
|
/** Handled message ids a mailbox keeps, and an enrollment keeps. */ const MAILBOX_HANDLED_MAX = 500;
|
|
95
|
+
/** How far a quiet mailbox's window may lag before its document is moved anyway. */ const MAILBOX_SYNC_WINDOW_REFRESH_MS = 60 * 60000;
|
|
95
96
|
const ENROLLMENT_HANDLED_MAX = 100;
|
|
96
97
|
/** Addresses in one `from:` search. */ const FROM_CHUNK = 15;
|
|
97
98
|
/** Runs the sync job once — see the module note. */ export async function runOutreachSyncJob(deps, context) {
|
|
@@ -332,15 +333,24 @@ async function syncMailbox(deps, firestore, input) {
|
|
|
332
333
|
delta: context.delta
|
|
333
334
|
});
|
|
334
335
|
if (paused) report.paused += 1;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
336
|
+
// The window is moved only when something was handled or an hour has
|
|
337
|
+
// passed: a quiet mailbox re-reading an hour's window is a few list calls,
|
|
338
|
+
// where rewriting every mailbox every run was a write per mailbox every
|
|
339
|
+
// fifteen minutes with nothing in it (AGL-3932). The handled ids keep a
|
|
340
|
+
// re-read from acting twice.
|
|
341
|
+
if (newlyHandled.length || nowMs - ((_ref2 = (_mailbox_sync2 = mailbox.sync) == null ? void 0 : _mailbox_sync2.throughMs) != null ? _ref2 : 0) >= MAILBOX_SYNC_WINDOW_REFRESH_MS) {
|
|
342
|
+
var _ref4;
|
|
343
|
+
var _mailbox_sync3;
|
|
344
|
+
await mailboxRef(firestore, orgId, mailbox.id).update({
|
|
345
|
+
sync: {
|
|
346
|
+
throughMs: nowMs,
|
|
347
|
+
handledMessageIds: [
|
|
348
|
+
...(_ref4 = (_mailbox_sync3 = mailbox.sync) == null ? void 0 : _mailbox_sync3.handledMessageIds) != null ? _ref4 : [],
|
|
349
|
+
...newlyHandled
|
|
350
|
+
].slice(-MAILBOX_HANDLED_MAX)
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
344
354
|
}
|
|
345
355
|
/** The sequence an enrollment runs through, read once per run. */ async function sequenceOf(context, sequenceId) {
|
|
346
356
|
var _context_sequences_get;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/runtime/sync-job.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\nimport { normalizeContactEmail } from '@aglyn/aglyn/app-utils/contacts'\nimport { isPublicMailboxDomain } from '@aglyn/aglyn/app-utils/crm'\nimport { emailAddressOf, emailExcerpt } from '@aglyn/aglyn/app-utils/email-text'\nimport { outreachMessageIdHeader } from '../engine/compose'\nimport { readOutreachDeliveryReport } from '../engine/delivery-status'\nimport { outreachEmailDomain } from '../engine/do-not-contact-domain'\nimport { outreachStoppedEntry } from '../engine/enrollment-activity'\nimport type { OutreachEnrollmentEvent } from '../engine/enrollment-state'\nimport { isOutreachGatewayBlock, outreachGatewayBlockDetail } from '../engine/gateway-block'\nimport { effectiveOutreachWindow, postponeOutreachForAutoReply } from '../engine/schedule'\nimport {\n decideOutreachThread,\n type OutreachMessageClassification,\n type OutreachThreadDecision,\n} from '../engine/thread-classification'\nimport { outreachHeader, outreachThreadMessageFromGmail, type OutreachThreadMessage } from '../engine/thread-message'\nimport { mailboxRef } from '../mailboxes/mailbox-credentials'\nimport {\n OUTREACH_COLLECTIONS,\n type OutreachEnrollment,\n type OutreachMailbox,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport { addOutreachDoNotContact, addOutreachDoNotContactDomain } from '../storage/do-not-contact-store'\nimport {\n outreachEnrollmentLink,\n outreachOrgCollection,\n readStoredOutreachEnrollment,\n readStoredOutreachMailbox,\n readStoredOutreachSequence,\n} from '../storage/outreach-records'\nimport type { OutreachMailClient, OutreachMailSearch, OutreachMailStub } from '../transport/mail-client'\nimport { GmailTransportError, isReconnectRequired } from '../transport/gmail-errors'\nimport { creditOutreachReply } from './campaign-credit'\nimport { applyOutreachEvent, recordOutreachOptOut } from './enrollment-events'\nimport { creditOutreachGatewayDeliveries, recordOutreachGatewayBlock } from './gateway-ledger'\nimport { outreachMailboxSendingDomain } from '../storage/domain-intel-store'\nimport {\n applyOutreachMailboxHealth,\n emptyOutreachHealthDelta,\n type OutreachMailboxHealthDelta,\n} from './mailbox-health-store'\nimport { noteOutreachMailboxReconnectRequired } from './mailbox-notices'\nimport type { OutreachRuntimeDeps } from './runtime-deps'\nimport { stampRecordEmailReply } from '@aglyn/aglyn/plugin-manager/plugin-record-email-state'\nimport { fileOutreachEmail, fileOutreachNote, fileOutreachTask, markOutreachEmailDelivery } from './timeline'\nimport { outreachUnsubscribeMailbox } from './unsubscribe-link'\n\n/*==========================================\n * THE SYNC JOB (AGL-2981): every fifteen minutes, on the console.\n *\n * For each connected or paused mailbox, what came back since the last run —\n * read with `format=full`, turned into the engine's message shape and\n * classified by `decideOutreachThread`:\n *\n * - a REPLY stops the enrollment, files the message on the contact's\n * timeline (once per `Message-ID`, under the capture address's own id for\n * it) and gives the rep the task \"Reply from <name>\";\n * - an OUT-OF-OFFICE postpones the next step a working week from the reply;\n * - an OPT-OUT — a reply asking to be left alone, or a message to the\n * `+unsubscribe` address the `mailto:` names — puts the address on the\n * organization's do-not-contact list and off the site's `sales` topic,\n * and stops every open enrollment of it;\n * - a HARD BOUNCE stops the enrollment, files the address on the platform's\n * suppression list and the do-not-contact list — and, when its diagnostic\n * reads as the domain's mail gateway refusing the sender rather than one\n * address being unknown (`engine/gateway-block`, AGL-3244), files the\n * DOMAIN on the do-not-contact list too, and says so on the enrollment —\n * and counts the block on the gateway ledger (AGL-3326), under the\n * gateway the bounce's `Remote-MTA` names.\n *\n * And once the mail is read, every email step of a watched enrollment that\n * is a day old with no bounce against it is credited to the ledger as\n * delivered (`./gateway-ledger`): the silence that says a gateway let the\n * sender through.\n *\n * Every verdict a list takes is said on the record the person is as well\n * (AGL-3245): the lead and the contact carry `emailState` through the\n * platform's stamp, and a bounce or a complaint lands on the send's own\n * timeline entry, so a member reading the record sees what the runtime saw.\n *\n * Four reads find those, each narrowed by the provider rather than read in\n * full (`OutreachMailSearch`, AGL-3489):\n * the threads of this mailbox's enrollments that have new mail; delivery\n * reports outside those threads (a receiving server's own bounce), by\n * searching for the mail systems that send them; messages to the\n * unsubscribe address; and messages from the people this mailbox is\n * writing to, in a thread of their own.\n *\n * Every write it makes is idempotent — the do-not-contact list keeps its\n * first entry, a topic opt-out keeps its first moment, a timeline entry is\n * filed once per message — and the messages it handled are marked AFTER\n * their effects, on the enrollment and on the mailbox, so a run that dies\n * part-way repeats harmless work rather than skipping some. The mailbox's\n * health takes the run's bounces and complaints in one write at the end,\n * which is where it pauses itself.\n *==========================================*/\n\nexport { OUTREACH_SYNC_JOB_ID } from '../constants/runtime-jobs'\n\n/** The furthest back a run reads: a mailbox's first run, or one that fell behind. */\nexport const OUTREACH_SYNC_LOOKBACK_MS = 7 * 24 * 60 * 60_000\n\n/** How far a run reads back past the previous one, for mail Gmail indexed late. */\nexport const OUTREACH_SYNC_OVERLAP_MS = 2 * 60 * 60_000\n\n/** How long after its last send an enrollment's mail is still watched. */\nexport const OUTREACH_SYNC_WATCH_MS = 45 * 24 * 60 * 60_000\n\n/** The most pages of one search a run reads. */\nconst SEARCH_PAGES = 5\n/** Handled message ids a mailbox keeps, and an enrollment keeps. */\nconst MAILBOX_HANDLED_MAX = 500\nconst ENROLLMENT_HANDLED_MAX = 100\n/** Addresses in one `from:` search. */\nconst FROM_CHUNK = 15\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport interface OutreachSyncReport {\n [key: string]: number\n mailboxes: number\n threads: number\n replies: number\n optOuts: number\n bounces: number\n /** Hard bounces that read as a gateway block, each filing its domain (AGL-3244). */\n gatewayBlocks: number\n /** Email steps a day old with no bounce, credited to the gateway ledger (AGL-3326). */\n deliveries: number\n postponed: number\n paused: number\n}\n\ninterface SyncContext {\n deps: OutreachRuntimeDeps\n firestore: Firestore\n orgId: string\n mailbox: OutreachMailbox\n client: OutreachMailClient\n selfAddresses: string[]\n unsubscribeAddress: string | null\n sequences: Map<string, OutreachSequence | null>\n delta: OutreachMailboxHealthDelta\n report: OutreachSyncReport\n nowMs: number\n}\n\n/** Runs the sync job once — see the module note. */\nexport async function runOutreachSyncJob(\n deps: OutreachRuntimeDeps,\n context: { nowMs: number; deadlineMs: number },\n): Promise<OutreachSyncReport> {\n const firestore = deps.firestore()\n const report: OutreachSyncReport = {\n mailboxes: 0,\n threads: 0,\n replies: 0,\n optOuts: 0,\n bounces: 0,\n gatewayBlocks: 0,\n deliveries: 0,\n postponed: 0,\n paused: 0,\n }\n const all = await firestore.collectionGroup(OUTREACH_COLLECTIONS.mailboxes).get()\n const byOrg = new Map<string, OutreachMailbox[]>()\n for (const doc of all.docs) {\n const orgId = doc.ref.parent.parent?.id\n const mailbox = readStoredOutreachMailbox(doc.id, doc.data())\n if (!orgId || !mailbox || (mailbox.status !== 'connected' && mailbox.status !== 'paused')) continue\n byOrg.set(orgId, [...(byOrg.get(orgId) ?? []), mailbox])\n }\n for (const [orgId, mailboxes] of byOrg) {\n if (deps.now() >= context.deadlineMs) break\n const orgSnapshot = await firestore.collection('orgs').doc(orgId).get()\n const org = (orgSnapshot.exists ? orgSnapshot.data() : null) as Record<string, unknown> | null\n if (!org || (await deps.orgRefusal(orgId, org))) continue\n for (const mailbox of mailboxes) {\n if (deps.now() >= context.deadlineMs) break\n try {\n await syncMailbox(deps, firestore, { orgId, mailbox, report, nowMs: context.nowMs })\n report.mailboxes += 1\n } catch (error) {\n // One mailbox's failure is its own; the next mailbox is read. A grant\n // Google refused is the mailbox's to reconnect.\n if (isReconnectRequired(error)) {\n await noteOutreachMailboxReconnectRequired(deps, {\n orgId,\n mailboxId: mailbox.id,\n errorCode: error instanceof GmailTransportError ? error.code : 'invalid_grant',\n nowMs: context.nowMs,\n })\n }\n console.error('[outreach] syncing a mailbox failed', error)\n }\n }\n }\n return report\n}\n\n/** Every stub a search finds, a few pages deep. */\nasync function search(client: OutreachMailClient, criteria: OutreachMailSearch): Promise<OutreachMailStub[]> {\n const found: OutreachMailStub[] = []\n let pageToken: string | null = null\n for (let page = 0; page < SEARCH_PAGES; page += 1) {\n const list = await client.searchMessages(criteria, { maxResults: 100, pageToken })\n found.push(...list.messages.filter((message) => message.id))\n pageToken = list.nextPageToken\n if (!pageToken) break\n }\n return found\n}\n\nasync function syncMailbox(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n input: { orgId: string; mailbox: OutreachMailbox; report: OutreachSyncReport; nowMs: number },\n): Promise<void> {\n const { orgId, mailbox, report, nowMs } = input\n const opened = await deps.openMailbox(mailbox.id)\n if (opened.ok === false) {\n if (opened.reason !== 'not-configured') {\n await noteOutreachMailboxReconnectRequired(deps, {\n orgId,\n mailboxId: mailbox.id,\n errorCode: opened.reason,\n nowMs,\n })\n }\n return\n }\n const unsubscribe = outreachUnsubscribeMailbox(mailbox.email)\n const context: SyncContext = {\n deps,\n firestore,\n orgId,\n mailbox,\n client: opened.client,\n selfAddresses: [\n mailbox.email,\n mailbox.sendAs,\n ...(mailbox.sendAsOptions ?? []).map((option) => option.email),\n ].filter(Boolean),\n unsubscribeAddress: unsubscribe?.address ?? null,\n sequences: new Map(),\n delta: emptyOutreachHealthDelta(),\n report,\n nowMs,\n }\n\n // The enrollments this mailbox is writing to, by thread and by address.\n const watched = await outreachOrgCollection(firestore, orgId, 'enrollments')\n .where('mailboxId', '==', mailbox.id)\n .where('lastSentAtMs', '>=', nowMs - OUTREACH_SYNC_WATCH_MS)\n .get()\n const byThread = new Map<string, OutreachEnrollment>()\n const byAddress = new Map<string, OutreachEnrollment>()\n const watchedEnrollments: OutreachEnrollment[] = []\n for (const doc of watched.docs) {\n const enrollment = readStoredOutreachEnrollment(doc.id, doc.data())\n if (!enrollment) continue\n watchedEnrollments.push(enrollment)\n for (const threadId of enrollment.gmailThreadIds) byThread.set(threadId, enrollment)\n const known = byAddress.get(enrollment.email)\n if (!known || (enrollment.lastSentAtMs ?? 0) > (known.lastSentAtMs ?? 0)) byAddress.set(enrollment.email, enrollment)\n }\n\n const sinceMs = Math.max(Number(mailbox.sync?.throughMs) || 0, nowMs - OUTREACH_SYNC_LOOKBACK_MS) - OUTREACH_SYNC_OVERLAP_MS\n // Every message this run or an earlier one handled outside an enrollment's\n // thread; one message is handled once however many searches find it.\n const handled = new Set(mailbox.sync?.handledMessageIds ?? [])\n const newlyHandled: string[] = []\n const markHandled = (id: string) => {\n if (handled.has(id)) return\n handled.add(id)\n newlyHandled.push(id)\n }\n const fresh = (stub: OutreachMailStub) => !handled.has(stub.id) && !byThread.has(stub.threadId)\n\n // 1. The enrollments' own threads that have new mail.\n const inbound = await search(context.client, { afterMs: sinceMs, notFromSelf: true })\n for (const threadId of new Set(inbound.map((stub) => stub.threadId))) {\n const enrollment = byThread.get(threadId)\n if (!enrollment) continue\n const thread = await context.client.getFullThread(threadId)\n report.threads += 1\n await applyMessages(context, enrollment, thread.messages.map(outreachThreadMessageFromGmail))\n }\n\n // 2. Delivery reports outside those threads: a receiving server's bounce.\n for (const stub of await search(context.client, { afterMs: sinceMs, from: ['mailer-daemon', 'postmaster'] })) {\n if (!fresh(stub)) continue\n const message = outreachThreadMessageFromGmail(await context.client.getFullMessage(stub.id))\n const delivery = readOutreachDeliveryReport(message)\n if (delivery?.kind === 'hard') {\n for (const address of delivery.failedAddresses) {\n const enrollment = byAddress.get(address)\n if (enrollment) await applyMessages(context, enrollment, [message])\n }\n }\n markHandled(stub.id)\n }\n\n // 3. Messages to the unsubscribe address: whoever wrote asked to leave.\n if (unsubscribe) {\n for (const stub of await search(context.client, { afterMs: sinceMs, to: unsubscribe.address })) {\n if (handled.has(stub.id)) continue\n const metadata = await context.client.getMessage(stub.id, { metadataHeaders: ['From'] })\n const from = emailAddressOf(metadata.headers.find((header) => header.name.toLowerCase() === 'from')?.value ?? '')\n const address = normalizeContactEmail(from)\n if (address && !context.selfAddresses.includes(address)) {\n const enrollment = byAddress.get(address) ?? null\n await recordOutreachOptOut(deps, {\n orgId,\n email: address,\n source: 'unsubscribe',\n enrollment,\n hostIds: enrollment ? [enrollment.hostId] : [],\n detail: 'Wrote to the unsubscribe address.',\n nowMs,\n })\n report.optOuts += 1\n }\n markHandled(stub.id)\n }\n }\n\n // 4. The people this mailbox writes to, answering in a thread of their own.\n const addresses = [...byAddress.keys()]\n for (let start = 0; start < addresses.length; start += FROM_CHUNK) {\n const chunk = addresses.slice(start, start + FROM_CHUNK)\n for (const stub of await search(context.client, { afterMs: sinceMs, from: chunk })) {\n if (!fresh(stub)) continue\n const message = outreachThreadMessageFromGmail(await context.client.getFullMessage(stub.id))\n const enrollment = byAddress.get(normalizeContactEmail(emailAddressOf(message.from)) ?? '')\n if (enrollment) await applyMessages(context, enrollment, [message])\n markHandled(stub.id)\n }\n }\n\n // 5. What went through: a day of silence after a send is a delivery\n // (AGL-3326), judged on the enrollments as this run left them.\n try {\n report.deliveries += await creditOutreachGatewayDeliveries(firestore, {\n orgId,\n enrollments: watchedEnrollments,\n sendingDomain: outreachMailboxSendingDomain(mailbox),\n resolveMx: deps.resolveMx,\n nowMs,\n })\n } catch (error) {\n console.error('[outreach] the gateway ledger could not credit deliveries', error)\n }\n\n const paused = await applyOutreachMailboxHealth(deps, { orgId, mailboxId: mailbox.id, delta: context.delta })\n if (paused) report.paused += 1\n await mailboxRef(firestore, orgId, mailbox.id).update({\n sync: {\n throughMs: nowMs,\n handledMessageIds: [...(mailbox.sync?.handledMessageIds ?? []), ...newlyHandled].slice(-MAILBOX_HANDLED_MAX),\n },\n })\n}\n\n/** The sequence an enrollment runs through, read once per run. */\nasync function sequenceOf(context: SyncContext, sequenceId: string): Promise<OutreachSequence | null> {\n if (!context.sequences.has(sequenceId)) {\n const snapshot = await outreachOrgCollection(context.firestore, context.orgId, 'sequences').doc(sequenceId).get()\n context.sequences.set(sequenceId, readStoredOutreachSequence(sequenceId, snapshot.exists ? snapshot.data() : undefined))\n }\n return context.sequences.get(sequenceId) ?? null\n}\n\n/** A message's `Message-ID` in header form, or `null` when it carries none. */\nconst messageIdOf = (message: OutreachThreadMessage) => outreachMessageIdHeader(outreachHeader(message, 'Message-ID'))\n\n/** A bounce's diagnostic with the recipient's address taken out of it. */\nexport function scrubbedOutreachDiagnostic(diagnostic: string | null, email: string): string | null {\n if (!diagnostic) return null\n const pattern = new RegExp(email.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'gi')\n return diagnostic.replace(pattern, 'the address')\n}\n\n/** Files a message the person wrote on their timeline. */\nasync function fileInbound(context: SyncContext, enrollment: OutreachEnrollment, message: OutreachThreadMessage) {\n const messageId = messageIdOf(message)\n if (!messageId) return\n await fileOutreachEmail(context.deps, {\n orgId: context.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n direction: 'inbound',\n subject: message.subject,\n from: emailAddressOf(message.from) ?? message.from,\n to: context.mailbox.sendAs || context.mailbox.email,\n messageId,\n inReplyTo: outreachMessageIdHeader(outreachHeader(message, 'In-Reply-To')),\n body: emailExcerpt(message.textBody ?? message.snippet ?? '', message.htmlBody ?? ''),\n atMs: message.internalDateMs,\n byUid: '',\n })\n}\n\n/**\n * What the new messages of one enrollment mean, done: the effects first,\n * each idempotent, then the event and the handled ids in one transaction.\n */\nasync function applyMessages(\n context: SyncContext,\n enrollment: OutreachEnrollment,\n messages: readonly OutreachThreadMessage[],\n): Promise<void> {\n const { deps, firestore, report, nowMs } = context\n const decision: OutreachThreadDecision = decideOutreachThread({\n messages,\n recipient: enrollment.email,\n handledMessageIds: enrollment.syncedMessageIds ?? [],\n selfAddresses: context.selfAddresses,\n unsubscribeAddress: context.unsubscribeAddress,\n threadSubject: enrollment.threadSubject,\n })\n const freshIds = decision.classifications.map((classification) => classification.messageId).filter(Boolean)\n if (!freshIds.length) return\n const decidedBy: OutreachMessageClassification | null = decision.decidedBy\n const decidingMessage = decidedBy ? messages.find((message) => message.id === decidedBy.messageId) ?? null : null\n const lastSentMessageId = enrollment.messageIds[enrollment.messageIds.length - 1] ?? null\n\n for (const classification of decision.classifications) {\n const aboutRecipient =\n !classification.bounce?.recipients.length || classification.bounce.recipients.includes(enrollment.email)\n if (classification.kind === 'hard_bounce' && aboutRecipient) {\n context.delta.bounces.push({ atMs: classification.atMs || nowMs, messageId: lastSentMessageId })\n }\n if (classification.complaint) {\n context.delta.complaintAtMs = Math.max(context.delta.complaintAtMs ?? 0, classification.atMs || nowMs)\n }\n }\n\n let event: OutreachEnrollmentEvent | null = null\n let postponedTo: number | null = null\n switch (decision.outcome) {\n case 'opted_out': {\n if (decidingMessage) await fileInbound(context, enrollment, decidingMessage)\n const complaint = decidedBy?.complaint === true\n await recordOutreachOptOut(deps, {\n orgId: context.orgId,\n email: enrollment.email,\n source: decidedBy?.evidence?.startsWith('To:') ? 'unsubscribe' : 'opt_out_reply',\n enrollment,\n hostIds: [enrollment.hostId],\n detail: decidedBy?.evidence ?? null,\n nowMs,\n complaint,\n })\n // A reply that called the email spam marks the send it answered\n // (AGL-3245), as the campaign webhook would mark a reported one.\n if (complaint) {\n await markOutreachEmailDelivery(deps, {\n orgId: context.orgId,\n messageId: lastSentMessageId,\n state: 'complained',\n atMs: decidedBy?.atMs || nowMs,\n detail: decidedBy?.evidence ?? null,\n })\n }\n report.optOuts += 1\n break\n }\n case 'replied': {\n if (decidingMessage) {\n await fileInbound(context, enrollment, decidingMessage)\n try {\n await fileOutreachTask(deps, {\n orgId: context.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n dedupeKey: `reply:${messageIdOf(decidingMessage) ?? decidingMessage.id}`,\n title: `Reply from ${enrollment.contactName || enrollment.email}`,\n notes: emailExcerpt(decidingMessage.textBody ?? decidingMessage.snippet ?? '', decidingMessage.htmlBody ?? '').slice(0, 500),\n kind: 'email',\n dueAtMs: nowMs,\n assigneeUid: context.mailbox.connectedByUid,\n })\n } catch (error) {\n console.error('[outreach] the reply task could not be filed', error)\n }\n }\n // A lead somebody wrote back to is being worked (AGL-3234): the record\n // system hears of the reply, and moves a lead nobody had worked to\n // Working (AGL-3080). Never throws.\n if (enrollment.target === 'lead' && enrollment.leadId && enrollment.email) {\n await stampRecordEmailReply({\n orgId: context.orgId,\n hostId: enrollment.hostId,\n emails: [enrollment.email],\n })\n }\n context.delta.replies += 1\n event = { type: 'reply', atMs: decidedBy?.atMs || nowMs, detail: decidedBy?.evidence ?? null }\n report.replies += 1\n // Once per enrollment (AGL-3254): a reply moves the status to\n // `replied`, and nothing is synced for it again.\n await creditOutreachReply(deps, { enrollment, atMs: event.atMs })\n break\n }\n case 'bounced': {\n const diagnostic = scrubbedOutreachDiagnostic(decidedBy?.bounce?.diagnostic ?? decidedBy?.bounce?.status ?? null, enrollment.email)\n await deps.suppressBouncedEmail({ email: enrollment.email, hostId: enrollment.hostId })\n await addOutreachDoNotContact(firestore, {\n orgId: context.orgId,\n email: enrollment.email,\n reason: 'hard_bounce',\n source: 'runtime',\n nowMs,\n enrollmentId: enrollment.id,\n sequenceId: enrollment.sequenceId,\n detail: diagnostic,\n })\n let detail = diagnostic\n // A gateway block is the domain's verdict on the sender (AGL-3244):\n // the domain is filed beside the address, and the enrollment says so.\n // Never a public mailbox provider's domain — Gmail refusing one\n // message on policy is not a reason to stop writing to Gmail.\n const domain = outreachEmailDomain(enrollment.email)\n const gateway = isOutreachGatewayBlock(decidedBy?.bounce ?? null)\n if (domain && !isPublicMailboxDomain(domain) && gateway) {\n await addOutreachDoNotContactDomain(firestore, {\n orgId: context.orgId,\n domain,\n reason: 'gateway_block',\n source: 'runtime',\n nowMs,\n enrollmentId: enrollment.id,\n sequenceId: enrollment.sequenceId,\n detail: diagnostic,\n })\n detail = outreachGatewayBlockDetail(domain, diagnostic)\n report.gatewayBlocks += 1\n }\n // The ledger learns the refusal (AGL-3326), whichever domain it was\n // at: a public provider refusing on policy is still that provider's\n // verdict on the sender, even though its domain is never listed.\n if (gateway) {\n await recordOutreachGatewayBlock(firestore, {\n orgId: context.orgId,\n email: enrollment.email,\n remoteMta: decidedBy?.bounce?.remoteMta ?? null,\n sendingDomain: outreachMailboxSendingDomain(context.mailbox),\n diagnostic,\n resolveMx: deps.resolveMx,\n nowMs,\n })\n }\n const bouncedAtMs = decidedBy?.atMs || nowMs\n // The record the person is says so too (AGL-3245): the verdict on the\n // lead and the contact, and the bounce on the send's own timeline\n // entry, so the record reads Sent, then Bounced.\n await deps.stampRecordEmailState({\n orgId: context.orgId,\n email: enrollment.email,\n state: {\n status: gateway ? 'blocked' : 'bounced',\n atMs: bouncedAtMs,\n source: 'sequence',\n detail: diagnostic,\n enrollmentId: enrollment.id,\n },\n })\n await markOutreachEmailDelivery(deps, {\n orgId: context.orgId,\n messageId: lastSentMessageId,\n state: 'bounced',\n atMs: bouncedAtMs,\n detail: diagnostic,\n })\n event = { type: 'bounce', atMs: bouncedAtMs, detail }\n report.bounces += 1\n break\n }\n case 'postpone': {\n const sequence = await sequenceOf(context, enrollment.sequenceId)\n postponedTo = postponeOutreachForAutoReply({\n nextDueAtMs: enrollment.nextDueAtMs,\n receivedAtMs: decidedBy?.atMs || nowMs,\n timeZone: context.mailbox.timezone,\n window: effectiveOutreachWindow(sequence?.settings.window, context.mailbox.window),\n random: deps.random,\n })\n break\n }\n default:\n break\n }\n\n const outcome = await applyOutreachEvent(firestore, {\n orgId: context.orgId,\n enrollmentId: enrollment.id,\n event,\n extra: (current) => {\n const extra: Record<string, unknown> = {\n syncedMessageIds: [...(current.syncedMessageIds ?? []), ...freshIds].slice(-ENROLLMENT_HANDLED_MAX),\n }\n const waiting = current.status === 'active' || current.status === 'paused'\n if (postponedTo !== null && waiting && current.nextDueAtMs !== null && postponedTo > current.nextDueAtMs) {\n extra['nextDueAtMs'] = postponedTo\n }\n return extra\n },\n nowMs,\n })\n if (postponedTo !== null && outcome.enrollment?.nextDueAtMs === postponedTo) report.postponed += 1\n // The person's record says the sequence ended (AGL-3274), once, under the\n // reply that ended it — after the transaction moved the status, so a run\n // that reads the same reply again finds nothing to file. An opt-out's\n // line is `recordOutreachOptOut`'s, beside the lists it writes.\n if (event?.type === 'reply' && outcome.changed) {\n const sequence = await sequenceOf(context, enrollment.sequenceId)\n const entry = outreachStoppedEntry({\n enrollmentId: enrollment.id,\n sequenceName: sequence?.name ?? '',\n reason: 'replied',\n })\n await fileOutreachNote(deps, {\n orgId: context.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n dedupeKey: entry.dedupeKey,\n body: entry.body,\n atMs: event.atMs,\n })\n }\n // The run's own view of the enrollment moves with it, so a second message\n // of it in this run is judged against what this one did.\n if (outcome.enrollment) Object.assign(enrollment, outcome.enrollment)\n}\n"],"names":["normalizeContactEmail","isPublicMailboxDomain","emailAddressOf","emailExcerpt","outreachMessageIdHeader","readOutreachDeliveryReport","outreachEmailDomain","outreachStoppedEntry","isOutreachGatewayBlock","outreachGatewayBlockDetail","effectiveOutreachWindow","postponeOutreachForAutoReply","decideOutreachThread","outreachHeader","outreachThreadMessageFromGmail","mailboxRef","OUTREACH_COLLECTIONS","addOutreachDoNotContact","addOutreachDoNotContactDomain","outreachEnrollmentLink","outreachOrgCollection","readStoredOutreachEnrollment","readStoredOutreachMailbox","readStoredOutreachSequence","GmailTransportError","isReconnectRequired","creditOutreachReply","applyOutreachEvent","recordOutreachOptOut","creditOutreachGatewayDeliveries","recordOutreachGatewayBlock","outreachMailboxSendingDomain","applyOutreachMailboxHealth","emptyOutreachHealthDelta","noteOutreachMailboxReconnectRequired","stampRecordEmailReply","fileOutreachEmail","fileOutreachNote","fileOutreachTask","markOutreachEmailDelivery","outreachUnsubscribeMailbox","OUTREACH_SYNC_JOB_ID","OUTREACH_SYNC_LOOKBACK_MS","OUTREACH_SYNC_OVERLAP_MS","OUTREACH_SYNC_WATCH_MS","SEARCH_PAGES","MAILBOX_HANDLED_MAX","ENROLLMENT_HANDLED_MAX","FROM_CHUNK","runOutreachSyncJob","deps","context","firestore","report","mailboxes","threads","replies","optOuts","bounces","gatewayBlocks","deliveries","postponed","paused","all","collectionGroup","get","byOrg","Map","doc","docs","orgId","ref","parent","id","mailbox","data","status","set","now","deadlineMs","orgSnapshot","collection","org","exists","orgRefusal","syncMailbox","nowMs","error","mailboxId","errorCode","code","console","search","client","criteria","found","pageToken","page","list","searchMessages","maxResults","push","messages","filter","message","nextPageToken","input","opened","openMailbox","ok","reason","unsubscribe","email","selfAddresses","sendAs","sendAsOptions","map","option","Boolean","unsubscribeAddress","address","sequences","delta","watched","where","byThread","byAddress","watchedEnrollments","enrollment","known","threadId","gmailThreadIds","lastSentAtMs","sinceMs","Math","max","Number","sync","throughMs","handled","Set","handledMessageIds","newlyHandled","markHandled","has","add","fresh","stub","inbound","afterMs","notFromSelf","thread","getFullThread","applyMessages","from","getFullMessage","delivery","kind","failedAddresses","to","metadata","getMessage","metadataHeaders","headers","find","header","name","toLowerCase","value","includes","source","hostIds","hostId","detail","addresses","keys","start","length","chunk","slice","enrollments","sendingDomain","resolveMx","update","sequenceOf","sequenceId","snapshot","undefined","messageIdOf","scrubbedOutreachDiagnostic","diagnostic","pattern","RegExp","replace","fileInbound","messageId","link","direction","subject","inReplyTo","body","textBody","snippet","htmlBody","atMs","internalDateMs","byUid","outcome","decision","recipient","syncedMessageIds","threadSubject","freshIds","classifications","classification","decidedBy","decidingMessage","lastSentMessageId","messageIds","aboutRecipient","bounce","recipients","complaint","complaintAtMs","event","postponedTo","evidence","startsWith","state","dedupeKey","title","contactName","notes","dueAtMs","assigneeUid","connectedByUid","target","leadId","emails","type","suppressBouncedEmail","enrollmentId","domain","gateway","remoteMta","bouncedAtMs","stampRecordEmailState","sequence","nextDueAtMs","receivedAtMs","timeZone","timezone","window","settings","random","extra","current","waiting","changed","entry","sequenceName","Object","assign"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/runtime/sync-job.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\nimport { normalizeContactEmail } from '@aglyn/aglyn/app-utils/contacts'\nimport { isPublicMailboxDomain } from '@aglyn/aglyn/app-utils/crm'\nimport { emailAddressOf, emailExcerpt } from '@aglyn/aglyn/app-utils/email-text'\nimport { outreachMessageIdHeader } from '../engine/compose'\nimport { readOutreachDeliveryReport } from '../engine/delivery-status'\nimport { outreachEmailDomain } from '../engine/do-not-contact-domain'\nimport { outreachStoppedEntry } from '../engine/enrollment-activity'\nimport type { OutreachEnrollmentEvent } from '../engine/enrollment-state'\nimport { isOutreachGatewayBlock, outreachGatewayBlockDetail } from '../engine/gateway-block'\nimport { effectiveOutreachWindow, postponeOutreachForAutoReply } from '../engine/schedule'\nimport {\n decideOutreachThread,\n type OutreachMessageClassification,\n type OutreachThreadDecision,\n} from '../engine/thread-classification'\nimport { outreachHeader, outreachThreadMessageFromGmail, type OutreachThreadMessage } from '../engine/thread-message'\nimport { mailboxRef } from '../mailboxes/mailbox-credentials'\nimport {\n OUTREACH_COLLECTIONS,\n type OutreachEnrollment,\n type OutreachMailbox,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport { addOutreachDoNotContact, addOutreachDoNotContactDomain } from '../storage/do-not-contact-store'\nimport {\n outreachEnrollmentLink,\n outreachOrgCollection,\n readStoredOutreachEnrollment,\n readStoredOutreachMailbox,\n readStoredOutreachSequence,\n} from '../storage/outreach-records'\nimport type { OutreachMailClient, OutreachMailSearch, OutreachMailStub } from '../transport/mail-client'\nimport { GmailTransportError, isReconnectRequired } from '../transport/gmail-errors'\nimport { creditOutreachReply } from './campaign-credit'\nimport { applyOutreachEvent, recordOutreachOptOut } from './enrollment-events'\nimport { creditOutreachGatewayDeliveries, recordOutreachGatewayBlock } from './gateway-ledger'\nimport { outreachMailboxSendingDomain } from '../storage/domain-intel-store'\nimport {\n applyOutreachMailboxHealth,\n emptyOutreachHealthDelta,\n type OutreachMailboxHealthDelta,\n} from './mailbox-health-store'\nimport { noteOutreachMailboxReconnectRequired } from './mailbox-notices'\nimport type { OutreachRuntimeDeps } from './runtime-deps'\nimport { stampRecordEmailReply } from '@aglyn/aglyn/plugin-manager/plugin-record-email-state'\nimport { fileOutreachEmail, fileOutreachNote, fileOutreachTask, markOutreachEmailDelivery } from './timeline'\nimport { outreachUnsubscribeMailbox } from './unsubscribe-link'\n\n/*==========================================\n * THE SYNC JOB (AGL-2981): every fifteen minutes, on the console.\n *\n * For each connected or paused mailbox, what came back since the last run —\n * read with `format=full`, turned into the engine's message shape and\n * classified by `decideOutreachThread`:\n *\n * - a REPLY stops the enrollment, files the message on the contact's\n * timeline (once per `Message-ID`, under the capture address's own id for\n * it) and gives the rep the task \"Reply from <name>\";\n * - an OUT-OF-OFFICE postpones the next step a working week from the reply;\n * - an OPT-OUT — a reply asking to be left alone, or a message to the\n * `+unsubscribe` address the `mailto:` names — puts the address on the\n * organization's do-not-contact list and off the site's `sales` topic,\n * and stops every open enrollment of it;\n * - a HARD BOUNCE stops the enrollment, files the address on the platform's\n * suppression list and the do-not-contact list — and, when its diagnostic\n * reads as the domain's mail gateway refusing the sender rather than one\n * address being unknown (`engine/gateway-block`, AGL-3244), files the\n * DOMAIN on the do-not-contact list too, and says so on the enrollment —\n * and counts the block on the gateway ledger (AGL-3326), under the\n * gateway the bounce's `Remote-MTA` names.\n *\n * And once the mail is read, every email step of a watched enrollment that\n * is a day old with no bounce against it is credited to the ledger as\n * delivered (`./gateway-ledger`): the silence that says a gateway let the\n * sender through.\n *\n * Every verdict a list takes is said on the record the person is as well\n * (AGL-3245): the lead and the contact carry `emailState` through the\n * platform's stamp, and a bounce or a complaint lands on the send's own\n * timeline entry, so a member reading the record sees what the runtime saw.\n *\n * Four reads find those, each narrowed by the provider rather than read in\n * full (`OutreachMailSearch`, AGL-3489):\n * the threads of this mailbox's enrollments that have new mail; delivery\n * reports outside those threads (a receiving server's own bounce), by\n * searching for the mail systems that send them; messages to the\n * unsubscribe address; and messages from the people this mailbox is\n * writing to, in a thread of their own.\n *\n * Every write it makes is idempotent — the do-not-contact list keeps its\n * first entry, a topic opt-out keeps its first moment, a timeline entry is\n * filed once per message — and the messages it handled are marked AFTER\n * their effects, on the enrollment and on the mailbox, so a run that dies\n * part-way repeats harmless work rather than skipping some. The mailbox's\n * health takes the run's bounces and complaints in one write at the end,\n * which is where it pauses itself.\n *==========================================*/\n\nexport { OUTREACH_SYNC_JOB_ID } from '../constants/runtime-jobs'\n\n/** The furthest back a run reads: a mailbox's first run, or one that fell behind. */\nexport const OUTREACH_SYNC_LOOKBACK_MS = 7 * 24 * 60 * 60_000\n\n/** How far a run reads back past the previous one, for mail Gmail indexed late. */\nexport const OUTREACH_SYNC_OVERLAP_MS = 2 * 60 * 60_000\n\n/** How long after its last send an enrollment's mail is still watched. */\nexport const OUTREACH_SYNC_WATCH_MS = 45 * 24 * 60 * 60_000\n\n/** The most pages of one search a run reads. */\nconst SEARCH_PAGES = 5\n/** Handled message ids a mailbox keeps, and an enrollment keeps. */\nconst MAILBOX_HANDLED_MAX = 500\n\n/** How far a quiet mailbox's window may lag before its document is moved anyway. */\nconst MAILBOX_SYNC_WINDOW_REFRESH_MS = 60 * 60_000\nconst ENROLLMENT_HANDLED_MAX = 100\n/** Addresses in one `from:` search. */\nconst FROM_CHUNK = 15\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport interface OutreachSyncReport {\n [key: string]: number\n mailboxes: number\n threads: number\n replies: number\n optOuts: number\n bounces: number\n /** Hard bounces that read as a gateway block, each filing its domain (AGL-3244). */\n gatewayBlocks: number\n /** Email steps a day old with no bounce, credited to the gateway ledger (AGL-3326). */\n deliveries: number\n postponed: number\n paused: number\n}\n\ninterface SyncContext {\n deps: OutreachRuntimeDeps\n firestore: Firestore\n orgId: string\n mailbox: OutreachMailbox\n client: OutreachMailClient\n selfAddresses: string[]\n unsubscribeAddress: string | null\n sequences: Map<string, OutreachSequence | null>\n delta: OutreachMailboxHealthDelta\n report: OutreachSyncReport\n nowMs: number\n}\n\n/** Runs the sync job once — see the module note. */\nexport async function runOutreachSyncJob(\n deps: OutreachRuntimeDeps,\n context: { nowMs: number; deadlineMs: number },\n): Promise<OutreachSyncReport> {\n const firestore = deps.firestore()\n const report: OutreachSyncReport = {\n mailboxes: 0,\n threads: 0,\n replies: 0,\n optOuts: 0,\n bounces: 0,\n gatewayBlocks: 0,\n deliveries: 0,\n postponed: 0,\n paused: 0,\n }\n const all = await firestore.collectionGroup(OUTREACH_COLLECTIONS.mailboxes).get()\n const byOrg = new Map<string, OutreachMailbox[]>()\n for (const doc of all.docs) {\n const orgId = doc.ref.parent.parent?.id\n const mailbox = readStoredOutreachMailbox(doc.id, doc.data())\n if (!orgId || !mailbox || (mailbox.status !== 'connected' && mailbox.status !== 'paused')) continue\n byOrg.set(orgId, [...(byOrg.get(orgId) ?? []), mailbox])\n }\n for (const [orgId, mailboxes] of byOrg) {\n if (deps.now() >= context.deadlineMs) break\n const orgSnapshot = await firestore.collection('orgs').doc(orgId).get()\n const org = (orgSnapshot.exists ? orgSnapshot.data() : null) as Record<string, unknown> | null\n if (!org || (await deps.orgRefusal(orgId, org))) continue\n for (const mailbox of mailboxes) {\n if (deps.now() >= context.deadlineMs) break\n try {\n await syncMailbox(deps, firestore, { orgId, mailbox, report, nowMs: context.nowMs })\n report.mailboxes += 1\n } catch (error) {\n // One mailbox's failure is its own; the next mailbox is read. A grant\n // Google refused is the mailbox's to reconnect.\n if (isReconnectRequired(error)) {\n await noteOutreachMailboxReconnectRequired(deps, {\n orgId,\n mailboxId: mailbox.id,\n errorCode: error instanceof GmailTransportError ? error.code : 'invalid_grant',\n nowMs: context.nowMs,\n })\n }\n console.error('[outreach] syncing a mailbox failed', error)\n }\n }\n }\n return report\n}\n\n/** Every stub a search finds, a few pages deep. */\nasync function search(client: OutreachMailClient, criteria: OutreachMailSearch): Promise<OutreachMailStub[]> {\n const found: OutreachMailStub[] = []\n let pageToken: string | null = null\n for (let page = 0; page < SEARCH_PAGES; page += 1) {\n const list = await client.searchMessages(criteria, { maxResults: 100, pageToken })\n found.push(...list.messages.filter((message) => message.id))\n pageToken = list.nextPageToken\n if (!pageToken) break\n }\n return found\n}\n\nasync function syncMailbox(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n input: { orgId: string; mailbox: OutreachMailbox; report: OutreachSyncReport; nowMs: number },\n): Promise<void> {\n const { orgId, mailbox, report, nowMs } = input\n const opened = await deps.openMailbox(mailbox.id)\n if (opened.ok === false) {\n if (opened.reason !== 'not-configured') {\n await noteOutreachMailboxReconnectRequired(deps, {\n orgId,\n mailboxId: mailbox.id,\n errorCode: opened.reason,\n nowMs,\n })\n }\n return\n }\n const unsubscribe = outreachUnsubscribeMailbox(mailbox.email)\n const context: SyncContext = {\n deps,\n firestore,\n orgId,\n mailbox,\n client: opened.client,\n selfAddresses: [\n mailbox.email,\n mailbox.sendAs,\n ...(mailbox.sendAsOptions ?? []).map((option) => option.email),\n ].filter(Boolean),\n unsubscribeAddress: unsubscribe?.address ?? null,\n sequences: new Map(),\n delta: emptyOutreachHealthDelta(),\n report,\n nowMs,\n }\n\n // The enrollments this mailbox is writing to, by thread and by address.\n const watched = await outreachOrgCollection(firestore, orgId, 'enrollments')\n .where('mailboxId', '==', mailbox.id)\n .where('lastSentAtMs', '>=', nowMs - OUTREACH_SYNC_WATCH_MS)\n .get()\n const byThread = new Map<string, OutreachEnrollment>()\n const byAddress = new Map<string, OutreachEnrollment>()\n const watchedEnrollments: OutreachEnrollment[] = []\n for (const doc of watched.docs) {\n const enrollment = readStoredOutreachEnrollment(doc.id, doc.data())\n if (!enrollment) continue\n watchedEnrollments.push(enrollment)\n for (const threadId of enrollment.gmailThreadIds) byThread.set(threadId, enrollment)\n const known = byAddress.get(enrollment.email)\n if (!known || (enrollment.lastSentAtMs ?? 0) > (known.lastSentAtMs ?? 0)) byAddress.set(enrollment.email, enrollment)\n }\n\n const sinceMs = Math.max(Number(mailbox.sync?.throughMs) || 0, nowMs - OUTREACH_SYNC_LOOKBACK_MS) - OUTREACH_SYNC_OVERLAP_MS\n // Every message this run or an earlier one handled outside an enrollment's\n // thread; one message is handled once however many searches find it.\n const handled = new Set(mailbox.sync?.handledMessageIds ?? [])\n const newlyHandled: string[] = []\n const markHandled = (id: string) => {\n if (handled.has(id)) return\n handled.add(id)\n newlyHandled.push(id)\n }\n const fresh = (stub: OutreachMailStub) => !handled.has(stub.id) && !byThread.has(stub.threadId)\n\n // 1. The enrollments' own threads that have new mail.\n const inbound = await search(context.client, { afterMs: sinceMs, notFromSelf: true })\n for (const threadId of new Set(inbound.map((stub) => stub.threadId))) {\n const enrollment = byThread.get(threadId)\n if (!enrollment) continue\n const thread = await context.client.getFullThread(threadId)\n report.threads += 1\n await applyMessages(context, enrollment, thread.messages.map(outreachThreadMessageFromGmail))\n }\n\n // 2. Delivery reports outside those threads: a receiving server's bounce.\n for (const stub of await search(context.client, { afterMs: sinceMs, from: ['mailer-daemon', 'postmaster'] })) {\n if (!fresh(stub)) continue\n const message = outreachThreadMessageFromGmail(await context.client.getFullMessage(stub.id))\n const delivery = readOutreachDeliveryReport(message)\n if (delivery?.kind === 'hard') {\n for (const address of delivery.failedAddresses) {\n const enrollment = byAddress.get(address)\n if (enrollment) await applyMessages(context, enrollment, [message])\n }\n }\n markHandled(stub.id)\n }\n\n // 3. Messages to the unsubscribe address: whoever wrote asked to leave.\n if (unsubscribe) {\n for (const stub of await search(context.client, { afterMs: sinceMs, to: unsubscribe.address })) {\n if (handled.has(stub.id)) continue\n const metadata = await context.client.getMessage(stub.id, { metadataHeaders: ['From'] })\n const from = emailAddressOf(metadata.headers.find((header) => header.name.toLowerCase() === 'from')?.value ?? '')\n const address = normalizeContactEmail(from)\n if (address && !context.selfAddresses.includes(address)) {\n const enrollment = byAddress.get(address) ?? null\n await recordOutreachOptOut(deps, {\n orgId,\n email: address,\n source: 'unsubscribe',\n enrollment,\n hostIds: enrollment ? [enrollment.hostId] : [],\n detail: 'Wrote to the unsubscribe address.',\n nowMs,\n })\n report.optOuts += 1\n }\n markHandled(stub.id)\n }\n }\n\n // 4. The people this mailbox writes to, answering in a thread of their own.\n const addresses = [...byAddress.keys()]\n for (let start = 0; start < addresses.length; start += FROM_CHUNK) {\n const chunk = addresses.slice(start, start + FROM_CHUNK)\n for (const stub of await search(context.client, { afterMs: sinceMs, from: chunk })) {\n if (!fresh(stub)) continue\n const message = outreachThreadMessageFromGmail(await context.client.getFullMessage(stub.id))\n const enrollment = byAddress.get(normalizeContactEmail(emailAddressOf(message.from)) ?? '')\n if (enrollment) await applyMessages(context, enrollment, [message])\n markHandled(stub.id)\n }\n }\n\n // 5. What went through: a day of silence after a send is a delivery\n // (AGL-3326), judged on the enrollments as this run left them.\n try {\n report.deliveries += await creditOutreachGatewayDeliveries(firestore, {\n orgId,\n enrollments: watchedEnrollments,\n sendingDomain: outreachMailboxSendingDomain(mailbox),\n resolveMx: deps.resolveMx,\n nowMs,\n })\n } catch (error) {\n console.error('[outreach] the gateway ledger could not credit deliveries', error)\n }\n\n const paused = await applyOutreachMailboxHealth(deps, { orgId, mailboxId: mailbox.id, delta: context.delta })\n if (paused) report.paused += 1\n // The window is moved only when something was handled or an hour has\n // passed: a quiet mailbox re-reading an hour's window is a few list calls,\n // where rewriting every mailbox every run was a write per mailbox every\n // fifteen minutes with nothing in it (AGL-3932). The handled ids keep a\n // re-read from acting twice.\n if (newlyHandled.length || nowMs - (mailbox.sync?.throughMs ?? 0) >= MAILBOX_SYNC_WINDOW_REFRESH_MS) {\n await mailboxRef(firestore, orgId, mailbox.id).update({\n sync: {\n throughMs: nowMs,\n handledMessageIds: [...(mailbox.sync?.handledMessageIds ?? []), ...newlyHandled].slice(-MAILBOX_HANDLED_MAX),\n },\n })\n }\n}\n\n/** The sequence an enrollment runs through, read once per run. */\nasync function sequenceOf(context: SyncContext, sequenceId: string): Promise<OutreachSequence | null> {\n if (!context.sequences.has(sequenceId)) {\n const snapshot = await outreachOrgCollection(context.firestore, context.orgId, 'sequences').doc(sequenceId).get()\n context.sequences.set(sequenceId, readStoredOutreachSequence(sequenceId, snapshot.exists ? snapshot.data() : undefined))\n }\n return context.sequences.get(sequenceId) ?? null\n}\n\n/** A message's `Message-ID` in header form, or `null` when it carries none. */\nconst messageIdOf = (message: OutreachThreadMessage) => outreachMessageIdHeader(outreachHeader(message, 'Message-ID'))\n\n/** A bounce's diagnostic with the recipient's address taken out of it. */\nexport function scrubbedOutreachDiagnostic(diagnostic: string | null, email: string): string | null {\n if (!diagnostic) return null\n const pattern = new RegExp(email.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'gi')\n return diagnostic.replace(pattern, 'the address')\n}\n\n/** Files a message the person wrote on their timeline. */\nasync function fileInbound(context: SyncContext, enrollment: OutreachEnrollment, message: OutreachThreadMessage) {\n const messageId = messageIdOf(message)\n if (!messageId) return\n await fileOutreachEmail(context.deps, {\n orgId: context.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n direction: 'inbound',\n subject: message.subject,\n from: emailAddressOf(message.from) ?? message.from,\n to: context.mailbox.sendAs || context.mailbox.email,\n messageId,\n inReplyTo: outreachMessageIdHeader(outreachHeader(message, 'In-Reply-To')),\n body: emailExcerpt(message.textBody ?? message.snippet ?? '', message.htmlBody ?? ''),\n atMs: message.internalDateMs,\n byUid: '',\n })\n}\n\n/**\n * What the new messages of one enrollment mean, done: the effects first,\n * each idempotent, then the event and the handled ids in one transaction.\n */\nasync function applyMessages(\n context: SyncContext,\n enrollment: OutreachEnrollment,\n messages: readonly OutreachThreadMessage[],\n): Promise<void> {\n const { deps, firestore, report, nowMs } = context\n const decision: OutreachThreadDecision = decideOutreachThread({\n messages,\n recipient: enrollment.email,\n handledMessageIds: enrollment.syncedMessageIds ?? [],\n selfAddresses: context.selfAddresses,\n unsubscribeAddress: context.unsubscribeAddress,\n threadSubject: enrollment.threadSubject,\n })\n const freshIds = decision.classifications.map((classification) => classification.messageId).filter(Boolean)\n if (!freshIds.length) return\n const decidedBy: OutreachMessageClassification | null = decision.decidedBy\n const decidingMessage = decidedBy ? messages.find((message) => message.id === decidedBy.messageId) ?? null : null\n const lastSentMessageId = enrollment.messageIds[enrollment.messageIds.length - 1] ?? null\n\n for (const classification of decision.classifications) {\n const aboutRecipient =\n !classification.bounce?.recipients.length || classification.bounce.recipients.includes(enrollment.email)\n if (classification.kind === 'hard_bounce' && aboutRecipient) {\n context.delta.bounces.push({ atMs: classification.atMs || nowMs, messageId: lastSentMessageId })\n }\n if (classification.complaint) {\n context.delta.complaintAtMs = Math.max(context.delta.complaintAtMs ?? 0, classification.atMs || nowMs)\n }\n }\n\n let event: OutreachEnrollmentEvent | null = null\n let postponedTo: number | null = null\n switch (decision.outcome) {\n case 'opted_out': {\n if (decidingMessage) await fileInbound(context, enrollment, decidingMessage)\n const complaint = decidedBy?.complaint === true\n await recordOutreachOptOut(deps, {\n orgId: context.orgId,\n email: enrollment.email,\n source: decidedBy?.evidence?.startsWith('To:') ? 'unsubscribe' : 'opt_out_reply',\n enrollment,\n hostIds: [enrollment.hostId],\n detail: decidedBy?.evidence ?? null,\n nowMs,\n complaint,\n })\n // A reply that called the email spam marks the send it answered\n // (AGL-3245), as the campaign webhook would mark a reported one.\n if (complaint) {\n await markOutreachEmailDelivery(deps, {\n orgId: context.orgId,\n messageId: lastSentMessageId,\n state: 'complained',\n atMs: decidedBy?.atMs || nowMs,\n detail: decidedBy?.evidence ?? null,\n })\n }\n report.optOuts += 1\n break\n }\n case 'replied': {\n if (decidingMessage) {\n await fileInbound(context, enrollment, decidingMessage)\n try {\n await fileOutreachTask(deps, {\n orgId: context.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n dedupeKey: `reply:${messageIdOf(decidingMessage) ?? decidingMessage.id}`,\n title: `Reply from ${enrollment.contactName || enrollment.email}`,\n notes: emailExcerpt(decidingMessage.textBody ?? decidingMessage.snippet ?? '', decidingMessage.htmlBody ?? '').slice(0, 500),\n kind: 'email',\n dueAtMs: nowMs,\n assigneeUid: context.mailbox.connectedByUid,\n })\n } catch (error) {\n console.error('[outreach] the reply task could not be filed', error)\n }\n }\n // A lead somebody wrote back to is being worked (AGL-3234): the record\n // system hears of the reply, and moves a lead nobody had worked to\n // Working (AGL-3080). Never throws.\n if (enrollment.target === 'lead' && enrollment.leadId && enrollment.email) {\n await stampRecordEmailReply({\n orgId: context.orgId,\n hostId: enrollment.hostId,\n emails: [enrollment.email],\n })\n }\n context.delta.replies += 1\n event = { type: 'reply', atMs: decidedBy?.atMs || nowMs, detail: decidedBy?.evidence ?? null }\n report.replies += 1\n // Once per enrollment (AGL-3254): a reply moves the status to\n // `replied`, and nothing is synced for it again.\n await creditOutreachReply(deps, { enrollment, atMs: event.atMs })\n break\n }\n case 'bounced': {\n const diagnostic = scrubbedOutreachDiagnostic(decidedBy?.bounce?.diagnostic ?? decidedBy?.bounce?.status ?? null, enrollment.email)\n await deps.suppressBouncedEmail({ email: enrollment.email, hostId: enrollment.hostId })\n await addOutreachDoNotContact(firestore, {\n orgId: context.orgId,\n email: enrollment.email,\n reason: 'hard_bounce',\n source: 'runtime',\n nowMs,\n enrollmentId: enrollment.id,\n sequenceId: enrollment.sequenceId,\n detail: diagnostic,\n })\n let detail = diagnostic\n // A gateway block is the domain's verdict on the sender (AGL-3244):\n // the domain is filed beside the address, and the enrollment says so.\n // Never a public mailbox provider's domain — Gmail refusing one\n // message on policy is not a reason to stop writing to Gmail.\n const domain = outreachEmailDomain(enrollment.email)\n const gateway = isOutreachGatewayBlock(decidedBy?.bounce ?? null)\n if (domain && !isPublicMailboxDomain(domain) && gateway) {\n await addOutreachDoNotContactDomain(firestore, {\n orgId: context.orgId,\n domain,\n reason: 'gateway_block',\n source: 'runtime',\n nowMs,\n enrollmentId: enrollment.id,\n sequenceId: enrollment.sequenceId,\n detail: diagnostic,\n })\n detail = outreachGatewayBlockDetail(domain, diagnostic)\n report.gatewayBlocks += 1\n }\n // The ledger learns the refusal (AGL-3326), whichever domain it was\n // at: a public provider refusing on policy is still that provider's\n // verdict on the sender, even though its domain is never listed.\n if (gateway) {\n await recordOutreachGatewayBlock(firestore, {\n orgId: context.orgId,\n email: enrollment.email,\n remoteMta: decidedBy?.bounce?.remoteMta ?? null,\n sendingDomain: outreachMailboxSendingDomain(context.mailbox),\n diagnostic,\n resolveMx: deps.resolveMx,\n nowMs,\n })\n }\n const bouncedAtMs = decidedBy?.atMs || nowMs\n // The record the person is says so too (AGL-3245): the verdict on the\n // lead and the contact, and the bounce on the send's own timeline\n // entry, so the record reads Sent, then Bounced.\n await deps.stampRecordEmailState({\n orgId: context.orgId,\n email: enrollment.email,\n state: {\n status: gateway ? 'blocked' : 'bounced',\n atMs: bouncedAtMs,\n source: 'sequence',\n detail: diagnostic,\n enrollmentId: enrollment.id,\n },\n })\n await markOutreachEmailDelivery(deps, {\n orgId: context.orgId,\n messageId: lastSentMessageId,\n state: 'bounced',\n atMs: bouncedAtMs,\n detail: diagnostic,\n })\n event = { type: 'bounce', atMs: bouncedAtMs, detail }\n report.bounces += 1\n break\n }\n case 'postpone': {\n const sequence = await sequenceOf(context, enrollment.sequenceId)\n postponedTo = postponeOutreachForAutoReply({\n nextDueAtMs: enrollment.nextDueAtMs,\n receivedAtMs: decidedBy?.atMs || nowMs,\n timeZone: context.mailbox.timezone,\n window: effectiveOutreachWindow(sequence?.settings.window, context.mailbox.window),\n random: deps.random,\n })\n break\n }\n default:\n break\n }\n\n const outcome = await applyOutreachEvent(firestore, {\n orgId: context.orgId,\n enrollmentId: enrollment.id,\n event,\n extra: (current) => {\n const extra: Record<string, unknown> = {\n syncedMessageIds: [...(current.syncedMessageIds ?? []), ...freshIds].slice(-ENROLLMENT_HANDLED_MAX),\n }\n const waiting = current.status === 'active' || current.status === 'paused'\n if (postponedTo !== null && waiting && current.nextDueAtMs !== null && postponedTo > current.nextDueAtMs) {\n extra['nextDueAtMs'] = postponedTo\n }\n return extra\n },\n nowMs,\n })\n if (postponedTo !== null && outcome.enrollment?.nextDueAtMs === postponedTo) report.postponed += 1\n // The person's record says the sequence ended (AGL-3274), once, under the\n // reply that ended it — after the transaction moved the status, so a run\n // that reads the same reply again finds nothing to file. An opt-out's\n // line is `recordOutreachOptOut`'s, beside the lists it writes.\n if (event?.type === 'reply' && outcome.changed) {\n const sequence = await sequenceOf(context, enrollment.sequenceId)\n const entry = outreachStoppedEntry({\n enrollmentId: enrollment.id,\n sequenceName: sequence?.name ?? '',\n reason: 'replied',\n })\n await fileOutreachNote(deps, {\n orgId: context.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n dedupeKey: entry.dedupeKey,\n body: entry.body,\n atMs: event.atMs,\n })\n }\n // The run's own view of the enrollment moves with it, so a second message\n // of it in this run is judged against what this one did.\n if (outcome.enrollment) Object.assign(enrollment, outcome.enrollment)\n}\n"],"names":["normalizeContactEmail","isPublicMailboxDomain","emailAddressOf","emailExcerpt","outreachMessageIdHeader","readOutreachDeliveryReport","outreachEmailDomain","outreachStoppedEntry","isOutreachGatewayBlock","outreachGatewayBlockDetail","effectiveOutreachWindow","postponeOutreachForAutoReply","decideOutreachThread","outreachHeader","outreachThreadMessageFromGmail","mailboxRef","OUTREACH_COLLECTIONS","addOutreachDoNotContact","addOutreachDoNotContactDomain","outreachEnrollmentLink","outreachOrgCollection","readStoredOutreachEnrollment","readStoredOutreachMailbox","readStoredOutreachSequence","GmailTransportError","isReconnectRequired","creditOutreachReply","applyOutreachEvent","recordOutreachOptOut","creditOutreachGatewayDeliveries","recordOutreachGatewayBlock","outreachMailboxSendingDomain","applyOutreachMailboxHealth","emptyOutreachHealthDelta","noteOutreachMailboxReconnectRequired","stampRecordEmailReply","fileOutreachEmail","fileOutreachNote","fileOutreachTask","markOutreachEmailDelivery","outreachUnsubscribeMailbox","OUTREACH_SYNC_JOB_ID","OUTREACH_SYNC_LOOKBACK_MS","OUTREACH_SYNC_OVERLAP_MS","OUTREACH_SYNC_WATCH_MS","SEARCH_PAGES","MAILBOX_HANDLED_MAX","MAILBOX_SYNC_WINDOW_REFRESH_MS","ENROLLMENT_HANDLED_MAX","FROM_CHUNK","runOutreachSyncJob","deps","context","firestore","report","mailboxes","threads","replies","optOuts","bounces","gatewayBlocks","deliveries","postponed","paused","all","collectionGroup","get","byOrg","Map","doc","docs","orgId","ref","parent","id","mailbox","data","status","set","now","deadlineMs","orgSnapshot","collection","org","exists","orgRefusal","syncMailbox","nowMs","error","mailboxId","errorCode","code","console","search","client","criteria","found","pageToken","page","list","searchMessages","maxResults","push","messages","filter","message","nextPageToken","input","opened","openMailbox","ok","reason","unsubscribe","email","selfAddresses","sendAs","sendAsOptions","map","option","Boolean","unsubscribeAddress","address","sequences","delta","watched","where","byThread","byAddress","watchedEnrollments","enrollment","known","threadId","gmailThreadIds","lastSentAtMs","sinceMs","Math","max","Number","sync","throughMs","handled","Set","handledMessageIds","newlyHandled","markHandled","has","add","fresh","stub","inbound","afterMs","notFromSelf","thread","getFullThread","applyMessages","from","getFullMessage","delivery","kind","failedAddresses","to","metadata","getMessage","metadataHeaders","headers","find","header","name","toLowerCase","value","includes","source","hostIds","hostId","detail","addresses","keys","start","length","chunk","slice","enrollments","sendingDomain","resolveMx","update","sequenceOf","sequenceId","snapshot","undefined","messageIdOf","scrubbedOutreachDiagnostic","diagnostic","pattern","RegExp","replace","fileInbound","messageId","link","direction","subject","inReplyTo","body","textBody","snippet","htmlBody","atMs","internalDateMs","byUid","outcome","decision","recipient","syncedMessageIds","threadSubject","freshIds","classifications","classification","decidedBy","decidingMessage","lastSentMessageId","messageIds","aboutRecipient","bounce","recipients","complaint","complaintAtMs","event","postponedTo","evidence","startsWith","state","dedupeKey","title","contactName","notes","dueAtMs","assigneeUid","connectedByUid","target","leadId","emails","type","suppressBouncedEmail","enrollmentId","domain","gateway","remoteMta","bouncedAtMs","stampRecordEmailState","sequence","nextDueAtMs","receivedAtMs","timeZone","timezone","window","settings","random","extra","current","waiting","changed","entry","sequenceName","Object","assign"],"mappings":"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"}
|