@aglyn/plugins-outreach 1.0.0-beta.152 → 1.0.0-beta.154

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +5 -1
  2. package/package.json +12 -12
  3. package/src/lib/components/enroll-dialog.js +142 -32
  4. package/src/lib/components/enroll-dialog.js.map +1 -1
  5. package/src/lib/components/enrollments-table.js +37 -8
  6. package/src/lib/components/enrollments-table.js.map +1 -1
  7. package/src/lib/components/mailbox-card.d.ts +1 -0
  8. package/src/lib/components/mailbox-card.js +25 -5
  9. package/src/lib/components/mailbox-card.js.map +1 -1
  10. package/src/lib/components/use-outreach-crm.d.ts +20 -1
  11. package/src/lib/components/use-outreach-crm.js +78 -4
  12. package/src/lib/components/use-outreach-crm.js.map +1 -1
  13. package/src/lib/declarations.console-server.js +14 -0
  14. package/src/lib/declarations.console-server.js.map +1 -1
  15. package/src/lib/engine/enrollment-state.d.ts +6 -1
  16. package/src/lib/engine/enrollment-state.js +3 -1
  17. package/src/lib/engine/enrollment-state.js.map +1 -1
  18. package/src/lib/enrollment/enroll-people.d.ts +52 -7
  19. package/src/lib/enrollment/enroll-people.js +109 -12
  20. package/src/lib/enrollment/enroll-people.js.map +1 -1
  21. package/src/lib/enrollment/gate-lookups.d.ts +12 -3
  22. package/src/lib/enrollment/gate-lookups.js +17 -14
  23. package/src/lib/enrollment/gate-lookups.js.map +1 -1
  24. package/src/lib/mailboxes/mailbox-api.d.ts +6 -0
  25. package/src/lib/mailboxes/mailbox-api.js.map +1 -1
  26. package/src/lib/mailboxes/mailbox-routes.js +11 -0
  27. package/src/lib/mailboxes/mailbox-routes.js.map +1 -1
  28. package/src/lib/model/outreach-api.d.ts +39 -8
  29. package/src/lib/model/outreach-api.js.map +1 -1
  30. package/src/lib/model/outreach.types.d.ts +28 -3
  31. package/src/lib/model/outreach.types.js +9 -0
  32. package/src/lib/model/outreach.types.js.map +1 -1
  33. package/src/lib/model/stored-records.js +7 -0
  34. package/src/lib/model/stored-records.js.map +1 -1
  35. package/src/lib/routes/enroll-routes.js +112 -39
  36. package/src/lib/routes/enroll-routes.js.map +1 -1
  37. package/src/lib/routes/preview-routes.js +18 -5
  38. package/src/lib/routes/preview-routes.js.map +1 -1
  39. package/src/lib/runtime/lead-records.d.ts +26 -0
  40. package/src/lib/runtime/lead-records.js +84 -0
  41. package/src/lib/runtime/lead-records.js.map +1 -0
  42. package/src/lib/runtime/send-job.js +30 -12
  43. package/src/lib/runtime/send-job.js.map +1 -1
  44. package/src/lib/runtime/sync-job.js +11 -3
  45. package/src/lib/runtime/sync-job.js.map +1 -1
  46. package/src/lib/runtime/timeline.d.ts +6 -4
  47. package/src/lib/runtime/timeline.js +3 -7
  48. package/src/lib/runtime/timeline.js.map +1 -1
  49. package/src/lib/storage/outreach-records.d.ts +21 -2
  50. package/src/lib/storage/outreach-records.js +31 -2
  51. package/src/lib/storage/outreach-records.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/runtime/send-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 { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport { CRM_COLLECTIONS } from '@aglyn/aglyn/app-utils/crm'\nimport { normalizeCrmEmailTemplate } from '@aglyn/aglyn/app-utils/crm-email-templates'\nimport { randomUUID } from 'node:crypto'\nimport { composeOutreachEmail } from '../engine/compose'\nimport { planOutreachStepCompletion, type OutreachEnrollmentEvent } from '../engine/enrollment-state'\nimport { evaluateOutreachGates, type OutreachGateBlock, type OutreachGateLookups } from '../engine/gates'\nimport {\n outreachSentToday,\n outreachTickAllowance,\n selectDueOutreachEnrollments,\n type OutreachDueCandidate,\n} from '../engine/sending-capacity'\nimport { readOutreachEnrollCandidates, type OutreachEnrollCandidate } from '../enrollment/enroll-people'\nimport { readOutreachGateLookups } from '../enrollment/gate-lookups'\nimport { mailboxRef } from '../mailboxes/mailbox-credentials'\nimport { outreachEffectiveDailyCap, outreachLocalDay } from '../mailboxes/mailbox-settings'\nimport { markOutreachMailboxReconnectRequired, type OpenedOutreachMailbox } from '../mailboxes/mailbox-transport'\nimport { effectiveOutreachAllowedCountries } from '../model/compliance-settings'\nimport {\n OUTREACH_COLLECTIONS,\n type OutreachComplianceSettingsDocument,\n type OutreachEnrollment,\n type OutreachMailbox,\n type OutreachSequence,\n type OutreachStepRecord,\n type OutreachTaskStep,\n} from '../model/outreach.types'\nimport { readOutreachComplianceSettingsDoc } from '../storage/compliance-settings-store'\nimport {\n outreachOrgCollection,\n readStoredOutreachEnrollment,\n readStoredOutreachMailbox,\n readStoredOutreachSequence,\n} from '../storage/outreach-records'\nimport { GmailTransportError, isReconnectRequired } from '../transport/gmail-errors'\nimport { Rfc5322MessageError } from '../transport/rfc5322'\nimport { sendComposedOutreachEmail } from '../transport/send-message'\nimport { applyOutreachEvent } from './enrollment-events'\nimport { outreachSendDigest, withRecentSend } from './mailbox-health-store'\nimport type { OutreachRuntimeDeps } from './runtime-deps'\nimport { fileOutreachEmail, fileOutreachTask, OUTREACH_TASK_KIND_TO_RECORD } from './timeline'\nimport { outreachUnsubscribeMailbox } from './unsubscribe-link'\n\n/*==========================================\n * THE SEND JOB (AGL-2981): every fifteen minutes, on the console.\n *\n * ## What comes due\n *\n * One collection-group read finds every active enrollment whose next step\n * is due, oldest first, bounded per run. They are grouped by workspace and\n * mailbox, and each group is judged before anything is sent:\n *\n * - the workspace: Outreach switched on, entitled and released to it;\n * - the mailbox: an enrollment whose mailbox is gone, disconnected, or whose\n * member left the organization is STOPPED — nothing will ever send it, and\n * leaving it active would show a sequence that is not running. A paused\n * mailbox, or one waiting to be reconnected, holds its enrollments;\n * - the member it sends as: a lockdown of the platform, the workspace or the\n * member holds everything.\n *\n * ## How much goes\n *\n * The engine's per-run allowance — the day's cap and ramp, spread across\n * the window, at most five a run — picks the enrollments (follow-ups first);\n * a task step is due whenever its time comes and spends no allowance.\n *\n * ## One send, exactly once\n *\n * Each email is CLAIMED before it is sent: a transaction over the enrollment\n * and its mailbox checks the enrollment is still active on the same step\n * with no live claim, and that the mailbox's day has room under its cap,\n * then writes the claim and reserves the day's count. Two runs racing for\n * one enrollment write one claim; two racing for one mailbox's last slot\n * reserve it once. The claim carries the `Message-ID` minted for the send,\n * so a claim a run died holding is settled by finding the message in the\n * mailbox rather than by sending it a second time. A send that fails hands\n * its reservation back.\n *\n * Before it is claimed, every gate is asked again — the do-not-contact\n * list, the suppression lists, the sales topic, the roster, the other\n * sequences — because a person enrolled on Monday can unsubscribe by\n * Thursday. A lookup that could not be made holds the send; a gate that\n * refuses stops the enrollment, saying why.\n *==========================================*/\n\nexport { OUTREACH_SEND_JOB_ID } from '../constants/runtime-jobs'\n\n/** The most due enrollments one run reads. */\nexport const OUTREACH_SEND_SCAN_LIMIT = 300\n\n/** How long a claim holds before a later run settles it. */\nexport const OUTREACH_CLAIM_STALE_MS = 10 * 60_000\n\n/** What one run did, for the tick's answer. */\nexport interface OutreachSendReport {\n [key: string]: number\n due: number\n sent: number\n tasks: number\n /** Stopped by a gate or an unusable mailbox. */\n stopped: number\n /** Paused for the rep to fix, or failed for good. */\n failed: number\n /** Due, and left for a later run: the allowance, a lookup, a hold. */\n held: number\n /** Claims a dead run left, settled. */\n recovered: number\n}\n\ntype Firestore = FirebaseFirestore.Firestore\n\ninterface MailboxRun {\n orgId: string\n org: Record<string, unknown>\n mailbox: OutreachMailbox\n settings: OutreachComplianceSettingsDocument\n report: OutreachSendReport\n deadlineMs: number\n opened: OpenedOutreachMailbox | null\n /** Set once the mailbox itself cannot send this run. */\n stopped: boolean\n templates: Map<string, string | null>\n}\n\nconst blankReport = (): OutreachSendReport => ({\n due: 0,\n sent: 0,\n tasks: 0,\n stopped: 0,\n failed: 0,\n held: 0,\n recovered: 0,\n})\n\n/** Runs the send job once — see the module note. */\nexport async function runOutreachSendJob(\n deps: OutreachRuntimeDeps,\n context: { nowMs: number; deadlineMs: number },\n): Promise<OutreachSendReport> {\n const firestore = deps.firestore()\n const report = blankReport()\n const due = await firestore\n .collectionGroup(OUTREACH_COLLECTIONS.enrollments)\n .where('status', '==', 'active')\n .where('nextDueAtMs', '<=', context.nowMs)\n .orderBy('nextDueAtMs')\n .limit(OUTREACH_SEND_SCAN_LIMIT)\n .get()\n report.due = due.size\n const byOrg = new Map<string, Map<string, OutreachEnrollment[]>>()\n for (const doc of due.docs) {\n const orgId = doc.ref.parent.parent?.id\n const enrollment = readStoredOutreachEnrollment(doc.id, doc.data())\n if (!orgId || !enrollment) continue\n const mailboxes = byOrg.get(orgId) ?? new Map<string, OutreachEnrollment[]>()\n mailboxes.set(enrollment.mailboxId, [...(mailboxes.get(enrollment.mailboxId) ?? []), enrollment])\n byOrg.set(orgId, mailboxes)\n }\n\n for (const [orgId, mailboxes] of byOrg) {\n const waiting = [...mailboxes.values()].reduce((sum, list) => sum + list.length, 0)\n if (deps.now() >= context.deadlineMs) {\n report.held += waiting\n continue\n }\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))) {\n report.held += waiting\n continue\n }\n const settings = await readOutreachComplianceSettingsDoc(firestore, orgId)\n for (const [mailboxId, enrollments] of mailboxes) {\n await runMailbox(deps, firestore, { orgId, org, mailboxId, enrollments, settings, report, context })\n }\n }\n return report\n}\n\n/** The reason an unusable mailbox stops an enrollment, or `null` for a usable one. */\nasync function unusableMailbox(\n firestore: Firestore,\n orgId: string,\n mailbox: OutreachMailbox | null,\n): Promise<string | null> {\n if (!mailbox) return 'The mailbox this sequence sends from was removed.'\n if (mailbox.status === 'disconnected') return 'The mailbox this sequence sends from was disconnected.'\n const member = await firestore.collection('orgs').doc(orgId).collection('members').doc(mailbox.connectedByUid).get()\n return member.exists\n ? null\n : 'The member who connected the mailbox this sequence sends from is no longer in the organization.'\n}\n\nasync function runMailbox(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n input: {\n orgId: string\n org: Record<string, unknown>\n mailboxId: string\n enrollments: OutreachEnrollment[]\n settings: OutreachComplianceSettingsDocument\n report: OutreachSendReport\n context: { nowMs: number; deadlineMs: number }\n },\n): Promise<void> {\n const { orgId, org, report, context } = input\n const nowMs = context.nowMs\n const snapshot = await mailboxRef(firestore, orgId, input.mailboxId).get()\n const mailbox = readStoredOutreachMailbox(input.mailboxId, snapshot.exists ? snapshot.data() : undefined)\n const unusable = await unusableMailbox(firestore, orgId, mailbox)\n if (unusable || !mailbox) {\n for (const enrollment of input.enrollments) {\n const outcome = await applyOutreachEvent(firestore, {\n orgId,\n enrollmentId: enrollment.id,\n event: { type: 'stop', atMs: nowMs, byUid: null, reason: 'gate', detail: unusable },\n nowMs,\n })\n if (outcome.changed) report.stopped += 1\n }\n return\n }\n if (mailbox.status !== 'connected' || (await deps.sendRefusal({ orgId, org, uid: mailbox.connectedByUid }))) {\n report.held += input.enrollments.length\n return\n }\n\n const run: MailboxRun = {\n orgId,\n org,\n mailbox,\n settings: input.settings,\n report,\n deadlineMs: context.deadlineMs,\n opened: null,\n stopped: false,\n templates: new Map(),\n }\n const sequenceIds = [...new Set(input.enrollments.map((enrollment) => enrollment.sequenceId))]\n const sequenceSnapshots = await firestore.getAll(\n ...sequenceIds.map((id) => outreachOrgCollection(firestore, orgId, 'sequences').doc(id)),\n )\n const sequences = new Map<string, OutreachSequence>()\n sequenceSnapshots.forEach((sequenceSnapshot, index) => {\n const sequence = readStoredOutreachSequence(\n sequenceIds[index],\n sequenceSnapshot.exists ? sequenceSnapshot.data() : undefined,\n )\n if (sequence) sequences.set(sequence.id, sequence)\n })\n\n const candidates: OutreachDueCandidate[] = []\n for (const enrollment of input.enrollments) {\n const sequence = sequences.get(enrollment.sequenceId)\n const claim = enrollment.sendClaim\n if (claim && nowMs - claim.atMs < OUTREACH_CLAIM_STALE_MS) continue\n if (claim && sequence) {\n if (await recoverClaim(deps, firestore, run, enrollment, sequence)) report.recovered += 1\n continue\n }\n if (sequence) candidates.push({ enrollment, sequence })\n }\n\n const allowance = outreachTickAllowance({ mailbox, nowMs })\n const selection = selectDueOutreachEnrollments({ candidates, mailbox, nowMs, allowance: allowance.allowance })\n report.held += selection.deferred.length\n\n for (const candidate of selection.tasks) {\n if (deps.now() >= context.deadlineMs) {\n report.held += 1\n continue\n }\n if (await runTaskStep(deps, firestore, run, candidate)) report.tasks += 1\n }\n if (!selection.emails.length) return\n\n // The people behind the emails, read once per site: the contact and its\n // company, and every lookup the gates decide on.\n const people = new Map<string, OutreachEnrollCandidate>()\n const lookups = new Map<string, OutreachGateLookups>()\n const byHost = new Map<string, OutreachEnrollment[]>()\n for (const candidate of selection.emails) {\n const enrollment = candidate.enrollment as OutreachEnrollment\n byHost.set(enrollment.hostId, [...(byHost.get(enrollment.hostId) ?? []), enrollment])\n }\n const siteNames = new Map<string, string>()\n for (const [hostId, enrollments] of byHost) {\n const contactGroupId = consentGroupForHost(org, hostId).groupId\n const [candidatesRead, lookupsRead, host] = await Promise.all([\n readOutreachEnrollCandidates(firestore, {\n orgId,\n hostId,\n contactGroupId,\n contactIds: enrollments.map((enrollment) => enrollment.contactId),\n }),\n readOutreachGateLookups(firestore, {\n orgId,\n hostId,\n people: enrollments.map((enrollment) => ({ contactId: enrollment.contactId, email: enrollment.email })),\n }),\n firestore.collection('hosts').doc(hostId).get(),\n ])\n for (const person of candidatesRead) people.set(person.contactId, person)\n for (const [contactId, answer] of lookupsRead) lookups.set(contactId, answer)\n siteNames.set(hostId, host.exists ? String(host.get('name') ?? '') : '')\n }\n\n for (const candidate of selection.emails) {\n if (run.stopped || deps.now() >= context.deadlineMs) {\n report.held += 1\n continue\n }\n const enrollment = candidate.enrollment as OutreachEnrollment\n await runEmailStep(deps, firestore, run, {\n enrollment,\n sequence: candidate.sequence as OutreachSequence,\n person: people.get(enrollment.contactId) ?? null,\n lookups: lookups.get(enrollment.contactId) ?? null,\n siteName: siteNames.get(enrollment.hostId) ?? '',\n })\n }\n}\n\n/** Any lookup the runtime could not make: the send waits for a run that can. */\nfunction uncheckable(lookups: OutreachGateLookups | null): boolean {\n return !lookups || Object.values(lookups).some((value) => value === null)\n}\n\n/** The event a gate's refusal ends an enrollment with. */\nfunction gateEvent(blocks: readonly OutreachGateBlock[], atMs: number): OutreachEnrollmentEvent {\n const optedOut = blocks.find((block) => block.code === 'do_not_contact' || block.code === 'sales_opted_out')\n if (optedOut) return { type: 'opt_out', atMs, reason: 'do_not_contact', detail: optedOut.reason }\n return { type: 'stop', atMs, byUid: null, reason: 'gate', detail: blocks.map((block) => block.reason).join(' ') }\n}\n\n/** The sender line a mailbox's mail goes out with. */\nconst senderOf = (mailbox: OutreachMailbox) => ({\n address: mailbox.sendAs || mailbox.email,\n name: mailbox.displayName || null,\n})\n\nasync function openClient(deps: OutreachRuntimeDeps, firestore: Firestore, run: MailboxRun) {\n const opened = (run.opened ??= await deps.openMailbox(run.mailbox.id))\n if (opened.ok === true) return opened.client\n run.stopped = true\n if (opened.reason !== 'not-configured') {\n await markOutreachMailboxReconnectRequired(firestore, {\n orgId: run.orgId,\n mailboxId: run.mailbox.id,\n errorCode: opened.reason,\n nowMs: deps.now(),\n })\n }\n return null\n}\n\n/**\n * Claims one step: the enrollment still active on the same step with no\n * live claim, and — for an email — a slot under the mailbox's cap today,\n * reserved in the same transaction.\n */\nasync function claimStep(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n run: MailboxRun,\n enrollment: OutreachEnrollment,\n messageId: string | null,\n): Promise<'claimed' | 'taken' | 'cap'> {\n const enrollmentRef = outreachOrgCollection(firestore, run.orgId, 'enrollments').doc(enrollment.id)\n const boxRef = mailboxRef(firestore, run.orgId, run.mailbox.id)\n const nowMs = deps.now()\n return firestore.runTransaction(async (transaction) => {\n const [current, box] = await Promise.all([transaction.get(enrollmentRef), transaction.get(boxRef)])\n const stored = readStoredOutreachEnrollment(enrollment.id, current.exists ? current.data() : undefined)\n const claim = stored?.sendClaim\n if (\n !stored ||\n stored.status !== 'active' ||\n stored.stepIndex !== enrollment.stepIndex ||\n stored.nextDueAtMs === null ||\n stored.nextDueAtMs > nowMs ||\n (claim && nowMs - claim.atMs < OUTREACH_CLAIM_STALE_MS)\n ) {\n return 'taken'\n }\n if (messageId) {\n const mailbox = readStoredOutreachMailbox(run.mailbox.id, box.exists ? box.data() : undefined)\n if (!mailbox || mailbox.status !== 'connected') return 'taken'\n const sent = outreachSentToday(mailbox.health, nowMs, mailbox.timezone)\n if (sent >= outreachEffectiveDailyCap(mailbox, nowMs)) return 'cap'\n transaction.update(boxRef, {\n 'health.sentToday': sent + 1,\n 'health.sentOnDay': outreachLocalDay(nowMs, mailbox.timezone),\n })\n }\n transaction.update(enrollmentRef, {\n sendClaim: { token: randomUUID(), atMs: nowMs, stepIndex: enrollment.stepIndex, messageId },\n updatedAtMs: nowMs,\n })\n return 'claimed'\n })\n}\n\n/** Drops a claim, and hands an email's reservation back to the mailbox's day. */\nasync function releaseClaim(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n run: MailboxRun,\n enrollment: Pick<OutreachEnrollment, 'id'>,\n reserved: boolean,\n event: OutreachEnrollmentEvent | null = null,\n): Promise<void> {\n const nowMs = deps.now()\n await applyOutreachEvent(firestore, {\n orgId: run.orgId,\n enrollmentId: enrollment.id,\n event,\n extra: () => ({ sendClaim: null }),\n nowMs,\n })\n if (!reserved) return\n const boxRef = mailboxRef(firestore, run.orgId, run.mailbox.id)\n await firestore.runTransaction(async (transaction) => {\n const box = await transaction.get(boxRef)\n const mailbox = readStoredOutreachMailbox(run.mailbox.id, box.exists ? box.data() : undefined)\n if (!mailbox || mailbox.health?.sentOnDay !== outreachLocalDay(nowMs, mailbox.timezone)) return\n transaction.update(boxRef, { 'health.sentToday': Math.max(0, (Number(mailbox.health.sentToday) || 0) - 1) })\n })\n}\n\n/**\n * Records a step that ran: the engine's completion patch, the claim\n * cleared, the step's record kept — and for an email the mailbox's send on\n * its day, its last-sent time and its window of recent sends. Refuses\n * (answers false) when the claim is no longer this run's.\n */\nasync function completeStep(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n run: MailboxRun,\n input: {\n enrollment: OutreachEnrollment\n sequence: OutreachSequence\n record: OutreachStepRecord\n sent: { messageId: string; threadId: string; subject: string } | null\n claimMessageId: string | null\n },\n): Promise<boolean> {\n const enrollmentRef = outreachOrgCollection(firestore, run.orgId, 'enrollments').doc(input.enrollment.id)\n const boxRef = mailboxRef(firestore, run.orgId, run.mailbox.id)\n const nowMs = deps.now()\n return firestore.runTransaction(async (transaction) => {\n const [current, box] = await Promise.all([\n transaction.get(enrollmentRef),\n input.sent ? transaction.get(boxRef) : Promise.resolve(null),\n ])\n const stored = readStoredOutreachEnrollment(input.enrollment.id, current.exists ? current.data() : undefined)\n if (!stored?.sendClaim || stored.sendClaim.messageId !== input.claimMessageId) return false\n if (stored.stepIndex !== input.enrollment.stepIndex) return false\n // The step RAN, whatever happened to the enrollment while it did — a\n // member paused it, a reply stopped it — so it is recorded as run, and a\n // resume never sends it twice.\n const plan = planOutreachStepCompletion({\n enrollment: { ...stored, status: 'active' },\n sequence: input.sequence,\n mailbox: run.mailbox,\n completedAtMs: nowMs,\n sent: input.sent,\n random: deps.random,\n })\n const patch: Record<string, unknown> = {\n sendClaim: null,\n stepRecords: [...(stored.stepRecords ?? []), input.record].slice(-20),\n updatedAtMs: nowMs,\n ...(plan.patch ?? {}),\n }\n if (stored.status !== 'active') {\n patch['status'] = stored.status\n if (stored.status !== 'paused') patch['nextDueAtMs'] = null\n }\n transaction.update(enrollmentRef, patch)\n if (input.sent && box?.exists) {\n const mailbox = readStoredOutreachMailbox(run.mailbox.id, box.data())\n if (mailbox) {\n const day = outreachLocalDay(nowMs, mailbox.timezone)\n const counts = mailbox.health?.daily?.[day] ?? { sent: 0, bounces: 0, replies: 0 }\n transaction.update(boxRef, {\n 'health.lastSentAtMs': nowMs,\n 'health.lastErrorAtMs': null,\n 'health.lastErrorCode': null,\n [`health.daily.${day}`]: { ...counts, sent: (Number(counts.sent) || 0) + 1 },\n 'health.recentSends': withRecentSend(mailbox.health?.recentSends, {\n id: outreachSendDigest(input.sent.messageId),\n atMs: nowMs,\n bounced: false,\n }),\n updatedAtMs: nowMs,\n })\n }\n }\n return true\n })\n}\n\n/** One task step: the rep's task on the contact, then the step recorded. */\nasync function runTaskStep(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n run: MailboxRun,\n candidate: OutreachDueCandidate,\n): Promise<boolean> {\n const enrollment = candidate.enrollment as OutreachEnrollment\n const sequence = candidate.sequence as OutreachSequence\n const step = sequence.steps[enrollment.stepIndex] as OutreachTaskStep\n if ((await claimStep(deps, firestore, run, enrollment, null)) !== 'claimed') return false\n const nowMs = deps.now()\n let taskId: string | null\n try {\n const written = await fileOutreachTask(deps, {\n orgId: run.orgId,\n hostId: enrollment.hostId,\n contactId: enrollment.contactId,\n dedupeKey: `step:${enrollment.id}:${step.id}`,\n title: step.title,\n notes: `From sequence step ${enrollment.stepIndex + 1}.`,\n kind: OUTREACH_TASK_KIND_TO_RECORD[step.taskKind] ?? 'todo',\n dueAtMs: nowMs,\n assigneeUid: run.mailbox.connectedByUid,\n })\n taskId = written?.ok ? written.id : null\n if (written?.ok === false) console.warn(`[outreach] a task step's task was not filed: ${written.error}`)\n } catch (error) {\n // The record system failed rather than refused: the step waits for a\n // run that can file its task.\n console.error('[outreach] filing a task step failed; the step waits', error)\n await releaseClaim(deps, firestore, run, enrollment, false)\n return false\n }\n return completeStep(deps, firestore, run, {\n enrollment,\n sequence,\n record: { stepIndex: enrollment.stepIndex, stepId: step.id, kind: 'task', atMs: nowMs, taskId },\n sent: null,\n claimMessageId: null,\n })\n}\n\n/** A CRM template's body, read once per run. */\nasync function templateBody(firestore: Firestore, run: MailboxRun, templateId: string): Promise<string | null> {\n if (!run.templates.has(templateId)) {\n const snapshot = await firestore\n .collection('orgs')\n .doc(run.orgId)\n .collection(CRM_COLLECTIONS.emailTemplates)\n .doc(templateId)\n .get()\n run.templates.set(\n templateId,\n snapshot.exists ? normalizeCrmEmailTemplate(snapshot.data() as Record<string, unknown>).body : null,\n )\n }\n return run.templates.get(templateId) ?? null\n}\n\nasync function runEmailStep(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n run: MailboxRun,\n input: {\n enrollment: OutreachEnrollment\n sequence: OutreachSequence\n person: OutreachEnrollCandidate | null\n lookups: OutreachGateLookups | null\n siteName: string\n },\n): Promise<void> {\n const { enrollment, sequence, person } = input\n const { report, mailbox } = run\n const nowMs = deps.now()\n const stop = async (event: OutreachEnrollmentEvent, counter: 'stopped' | 'failed') => {\n const outcome = await applyOutreachEvent(firestore, { orgId: run.orgId, enrollmentId: enrollment.id, event, nowMs })\n if (outcome.changed) report[counter] += 1\n }\n\n // Every gate again, on the person as they stand now.\n if (!person?.contact) {\n return stop({ type: 'stop', atMs: nowMs, byUid: null, reason: 'gate', detail: 'This contact no longer exists in the CRM.' }, 'stopped')\n }\n if (!person.visible) {\n return stop(\n { type: 'stop', atMs: nowMs, byUid: null, reason: 'gate', detail: \"This contact is no longer in the sequence's site's CRM.\" },\n 'stopped',\n )\n }\n if (uncheckable(input.lookups)) {\n report.held += 1\n return\n }\n const contactGroupId = consentGroupForHost(run.org, enrollment.hostId).groupId\n const gate = evaluateOutreachGates({\n email: enrollment.email,\n contact: person.contact,\n contactGroupId,\n company: person.company,\n settings: {\n ...sequence.settings,\n allowedCountries: effectiveOutreachAllowedCountries(\n sequence.settings.allowedCountries,\n run.settings.allowedCountries,\n ),\n },\n personalLine: enrollment.personalLine,\n attestations: enrollment.attestations,\n enrollmentId: enrollment.id,\n lookups: input.lookups as OutreachGateLookups,\n })\n if (!gate.allowed) return stop(gateEvent(gate.blocks, nowMs), 'stopped')\n\n // The email, with its way out.\n const listUnsubscribeUrl = deps.unsubscribeUrl({ orgId: run.orgId, enrollmentId: enrollment.id })\n const mailto = outreachUnsubscribeMailbox(mailbox.email)\n if (!listUnsubscribeUrl || !mailto) {\n // No link can be minted — no secret, or no HTTPS console origin — and an\n // email without its one-click way out does not leave.\n console.error('[outreach] no unsubscribe link could be minted; nothing sends until one can')\n run.stopped = true\n report.held += 1\n return\n }\n const step = sequence.steps[enrollment.stepIndex]\n const sender = senderOf(mailbox)\n const composed = composeOutreachEmail({\n sequence,\n enrollment,\n orgSettings: run.settings,\n merge: {\n contact: person.contact,\n contactGroupId,\n sender: { name: mailbox.displayName, email: sender.address },\n site: { name: input.siteName },\n },\n templateBody: step?.kind === 'email' && step.templateId ? await templateBody(firestore, run, step.templateId) : null,\n listUnsubscribeUrl,\n listUnsubscribeMailto: mailto.uri,\n })\n if (composed.error || !composed.email) {\n const code = composed.error?.code\n if (code === 'missing_postal_address' || code === 'missing_legal_name') {\n // The organization's footer: nothing of theirs sends until it is set.\n run.stopped = true\n report.held += 1\n return\n }\n if (code === 'invalid_recipient' || code === 'missing_thread' || code === 'not_an_email_step') {\n return stop({ type: 'send_failed', atMs: nowMs, detail: composed.error?.message }, 'failed')\n }\n // The sequence's own words: the rep fixes the step and resumes.\n return stop({ type: 'pause', atMs: nowMs, byUid: null, detail: composed.error?.message }, 'failed')\n }\n if (composed.unresolvedFields.length) {\n const fields = composed.unresolvedFields.map((field) => `{{${field}}}`).join(', ')\n return stop(\n {\n type: 'pause',\n atMs: nowMs,\n byUid: null,\n detail: `Paused before sending: this contact has nothing for ${fields}. Fill it in and resume.`,\n },\n 'failed',\n )\n }\n\n // Claimed, then sent, then recorded.\n const domain = sender.address.slice(sender.address.lastIndexOf('@') + 1)\n const local = randomUUID()\n const messageId = `<${local}@${domain}>`\n const claimed = await claimStep(deps, firestore, run, enrollment, messageId)\n if (claimed !== 'claimed') {\n if (claimed === 'cap') run.stopped = true\n report.held += 1\n return\n }\n const client = await openClient(deps, firestore, run)\n if (!client) {\n await releaseClaim(deps, firestore, run, enrollment, true)\n report.held += 1\n return\n }\n let sent: Awaited<ReturnType<typeof sendComposedOutreachEmail>>\n try {\n sent = await sendComposedOutreachEmail(client, composed.email, sender, {\n messageIdLocalPart: local,\n messageIdDomain: domain,\n })\n } catch (error) {\n if (error instanceof Rfc5322MessageError || (error instanceof GmailTransportError && error.code === 'invalid_request')) {\n await releaseClaim(deps, firestore, run, enrollment, true, {\n type: 'send_failed',\n atMs: deps.now(),\n detail: `Gmail refused the email: ${error.message}`,\n })\n report.failed += 1\n return\n }\n await releaseClaim(deps, firestore, run, enrollment, true)\n report.held += 1\n run.stopped = true\n const code = error instanceof GmailTransportError ? error.code : 'unexpected'\n if (isReconnectRequired(error)) {\n await markOutreachMailboxReconnectRequired(firestore, {\n orgId: run.orgId,\n mailboxId: mailbox.id,\n errorCode: code,\n nowMs: deps.now(),\n })\n } else {\n await mailboxRef(firestore, run.orgId, mailbox.id)\n .update({ 'health.lastErrorAtMs': deps.now(), 'health.lastErrorCode': code })\n .catch(() => undefined)\n }\n console.error(`[outreach] a send from a mailbox failed (${code}); its sends wait for the next run`)\n return\n }\n\n const record: OutreachStepRecord = {\n stepIndex: enrollment.stepIndex,\n stepId: step?.id ?? '',\n kind: 'email',\n atMs: deps.now(),\n gmailMessageId: sent.gmailMessageId,\n gmailThreadId: sent.threadId,\n messageId: sent.messageId,\n subject: sent.subject,\n }\n await completeStep(deps, firestore, run, {\n enrollment,\n sequence,\n record,\n sent: { messageId: sent.messageId, threadId: sent.threadId, subject: sent.subject },\n claimMessageId: messageId,\n })\n report.sent += 1\n await fileOutreachEmail(deps, {\n orgId: run.orgId,\n hostId: enrollment.hostId,\n contactId: enrollment.contactId,\n direction: 'outbound',\n subject: sent.subject,\n from: sender.address,\n to: composed.email.to,\n messageId: sent.messageId,\n inReplyTo: composed.email.inReplyTo ?? null,\n body: composed.email.text,\n atMs: record.atMs,\n byUid: mailbox.connectedByUid,\n byName: mailbox.displayName || null,\n })\n}\n\n/**\n * Settles a claim a run died holding. A task step's claim is dropped and\n * the step runs again (its task is filed once however often it is asked).\n * An email's claim looks for the message it minted in the mailbox: found,\n * the send happened and is recorded; not found, it did not, and the claim\n * and its reservation are handed back for the next run.\n */\nasync function recoverClaim(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n run: MailboxRun,\n enrollment: OutreachEnrollment,\n sequence: OutreachSequence,\n): Promise<boolean> {\n const claim = enrollment.sendClaim\n if (!claim) return false\n if (!claim.messageId) {\n await releaseClaim(deps, firestore, run, enrollment, false)\n return true\n }\n const client = await openClient(deps, firestore, run)\n if (!client) return false\n const bare = claim.messageId.replace(/^<|>$/g, '')\n const found = await client.listMessages({ q: `rfc822msgid:${bare}`, maxResults: 1, includeSpamTrash: true })\n const hit = found.messages[0]\n if (!hit) {\n await releaseClaim(deps, firestore, run, enrollment, true)\n return true\n }\n const message = await client.getMessage(hit.id, { metadataHeaders: ['Subject'] })\n const subject = message.headers.find((header) => header.name.toLowerCase() === 'subject')?.value ?? ''\n const step = sequence.steps[enrollment.stepIndex]\n return completeStep(deps, firestore, run, {\n enrollment,\n sequence,\n record: {\n stepIndex: enrollment.stepIndex,\n stepId: step?.id ?? '',\n kind: 'email',\n atMs: deps.now(),\n gmailMessageId: hit.id,\n gmailThreadId: hit.threadId,\n messageId: claim.messageId,\n subject,\n },\n sent: { messageId: claim.messageId, threadId: hit.threadId, subject },\n claimMessageId: claim.messageId,\n })\n}\n"],"names":["consentGroupForHost","CRM_COLLECTIONS","normalizeCrmEmailTemplate","randomUUID","composeOutreachEmail","planOutreachStepCompletion","evaluateOutreachGates","outreachSentToday","outreachTickAllowance","selectDueOutreachEnrollments","readOutreachEnrollCandidates","readOutreachGateLookups","mailboxRef","outreachEffectiveDailyCap","outreachLocalDay","markOutreachMailboxReconnectRequired","effectiveOutreachAllowedCountries","OUTREACH_COLLECTIONS","readOutreachComplianceSettingsDoc","outreachOrgCollection","readStoredOutreachEnrollment","readStoredOutreachMailbox","readStoredOutreachSequence","GmailTransportError","isReconnectRequired","Rfc5322MessageError","sendComposedOutreachEmail","applyOutreachEvent","outreachSendDigest","withRecentSend","fileOutreachEmail","fileOutreachTask","OUTREACH_TASK_KIND_TO_RECORD","outreachUnsubscribeMailbox","OUTREACH_SEND_JOB_ID","OUTREACH_SEND_SCAN_LIMIT","OUTREACH_CLAIM_STALE_MS","blankReport","due","sent","tasks","stopped","failed","held","recovered","runOutreachSendJob","deps","context","firestore","report","collectionGroup","enrollments","where","nowMs","orderBy","limit","get","size","byOrg","Map","doc","docs","mailboxes","orgId","ref","parent","id","enrollment","data","set","mailboxId","waiting","values","reduce","sum","list","length","now","deadlineMs","orgSnapshot","collection","org","exists","orgRefusal","settings","runMailbox","unusableMailbox","mailbox","status","member","connectedByUid","input","snapshot","undefined","unusable","outcome","enrollmentId","event","type","atMs","byUid","reason","detail","changed","sendRefusal","uid","run","opened","templates","sequenceIds","Set","map","sequenceId","sequenceSnapshots","getAll","sequences","forEach","sequenceSnapshot","index","sequence","candidates","claim","sendClaim","recoverClaim","push","allowance","selection","deferred","candidate","runTaskStep","emails","people","lookups","byHost","hostId","siteNames","host","contactGroupId","groupId","candidatesRead","lookupsRead","Promise","all","contactIds","contactId","email","person","answer","String","runEmailStep","siteName","uncheckable","Object","some","value","gateEvent","blocks","optedOut","find","block","code","join","senderOf","address","sendAs","name","displayName","openClient","openMailbox","ok","client","errorCode","claimStep","messageId","enrollmentRef","boxRef","runTransaction","transaction","current","box","stored","stepIndex","nextDueAtMs","health","timezone","update","token","updatedAtMs","releaseClaim","reserved","extra","sentOnDay","Math","max","Number","sentToday","completeStep","plan","resolve","claimMessageId","completedAtMs","random","patch","stepRecords","record","slice","day","counts","daily","bounces","replies","recentSends","bounced","step","steps","taskId","written","dedupeKey","title","notes","kind","taskKind","dueAtMs","assigneeUid","console","warn","error","stepId","templateBody","templateId","has","emailTemplates","body","composed","stop","counter","contact","visible","gate","company","allowedCountries","personalLine","attestations","allowed","listUnsubscribeUrl","unsubscribeUrl","mailto","sender","orgSettings","merge","site","listUnsubscribeMailto","uri","message","unresolvedFields","fields","field","domain","lastIndexOf","local","claimed","messageIdLocalPart","messageIdDomain","catch","gmailMessageId","gmailThreadId","threadId","subject","direction","from","to","inReplyTo","text","byName","bare","replace","found","listMessages","q","maxResults","includeSpamTrash","hit","messages","getMessage","metadataHeaders","headers","header","toLowerCase"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/runtime/send-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 { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport { CRM_COLLECTIONS } from '@aglyn/aglyn/app-utils/crm'\nimport { normalizeCrmEmailTemplate } from '@aglyn/aglyn/app-utils/crm-email-templates'\nimport { randomUUID } from 'node:crypto'\nimport { composeOutreachEmail } from '../engine/compose'\nimport { planOutreachStepCompletion, type OutreachEnrollmentEvent } from '../engine/enrollment-state'\nimport { evaluateOutreachGates, type OutreachGateBlock, type OutreachGateLookups } from '../engine/gates'\nimport {\n outreachSentToday,\n outreachTickAllowance,\n selectDueOutreachEnrollments,\n type OutreachDueCandidate,\n} from '../engine/sending-capacity'\nimport { readOutreachEnrollCandidates, type OutreachEnrollCandidate } from '../enrollment/enroll-people'\nimport { readOutreachGateLookups } from '../enrollment/gate-lookups'\nimport { mailboxRef } from '../mailboxes/mailbox-credentials'\nimport { outreachEffectiveDailyCap, outreachLocalDay } from '../mailboxes/mailbox-settings'\nimport { markOutreachMailboxReconnectRequired, type OpenedOutreachMailbox } from '../mailboxes/mailbox-transport'\nimport { effectiveOutreachAllowedCountries } from '../model/compliance-settings'\nimport {\n OUTREACH_COLLECTIONS,\n type OutreachComplianceSettingsDocument,\n type OutreachEnrollment,\n type OutreachMailbox,\n type OutreachSequence,\n type OutreachStepRecord,\n type OutreachTaskStep,\n} from '../model/outreach.types'\nimport { readOutreachComplianceSettingsDoc } from '../storage/compliance-settings-store'\nimport {\n outreachEnrollmentLink,\n outreachEnrollmentPerson,\n outreachOrgCollection,\n readStoredOutreachEnrollment,\n readStoredOutreachMailbox,\n readStoredOutreachSequence,\n} from '../storage/outreach-records'\nimport { GmailTransportError, isReconnectRequired } from '../transport/gmail-errors'\nimport { Rfc5322MessageError } from '../transport/rfc5322'\nimport { sendComposedOutreachEmail } from '../transport/send-message'\nimport { applyOutreachEvent } from './enrollment-events'\nimport { outreachSendDigest, withRecentSend } from './mailbox-health-store'\nimport type { OutreachRuntimeDeps } from './runtime-deps'\nimport { fileOutreachEmail, fileOutreachTask, OUTREACH_TASK_KIND_TO_RECORD } from './timeline'\nimport { outreachUnsubscribeMailbox } from './unsubscribe-link'\n\n/*==========================================\n * THE SEND JOB (AGL-2981): every fifteen minutes, on the console.\n *\n * ## What comes due\n *\n * One collection-group read finds every active enrollment whose next step\n * is due, oldest first, bounded per run. They are grouped by workspace and\n * mailbox, and each group is judged before anything is sent:\n *\n * - the workspace: Outreach switched on, entitled and released to it;\n * - the mailbox: an enrollment whose mailbox is gone, disconnected, or whose\n * member left the organization is STOPPED — nothing will ever send it, and\n * leaving it active would show a sequence that is not running. A paused\n * mailbox, or one waiting to be reconnected, holds its enrollments;\n * - the member it sends as: a lockdown of the platform, the workspace or the\n * member holds everything.\n *\n * ## How much goes\n *\n * The engine's per-run allowance — the day's cap and ramp, spread across\n * the window, at most five a run — picks the enrollments (follow-ups first);\n * a task step is due whenever its time comes and spends no allowance.\n *\n * ## One send, exactly once\n *\n * Each email is CLAIMED before it is sent: a transaction over the enrollment\n * and its mailbox checks the enrollment is still active on the same step\n * with no live claim, and that the mailbox's day has room under its cap,\n * then writes the claim and reserves the day's count. Two runs racing for\n * one enrollment write one claim; two racing for one mailbox's last slot\n * reserve it once. The claim carries the `Message-ID` minted for the send,\n * so a claim a run died holding is settled by finding the message in the\n * mailbox rather than by sending it a second time. A send that fails hands\n * its reservation back.\n *\n * Before it is claimed, every gate is asked again — the do-not-contact\n * list, the suppression lists, the sales topic, the roster, the other\n * sequences — because a person enrolled on Monday can unsubscribe by\n * Thursday. A lookup that could not be made holds the send; a gate that\n * refuses stops the enrollment, saying why.\n *==========================================*/\n\nexport { OUTREACH_SEND_JOB_ID } from '../constants/runtime-jobs'\n\n/** The most due enrollments one run reads. */\nexport const OUTREACH_SEND_SCAN_LIMIT = 300\n\n/** How long a claim holds before a later run settles it. */\nexport const OUTREACH_CLAIM_STALE_MS = 10 * 60_000\n\n/** What one run did, for the tick's answer. */\nexport interface OutreachSendReport {\n [key: string]: number\n due: number\n sent: number\n tasks: number\n /** Stopped by a gate or an unusable mailbox. */\n stopped: number\n /** Paused for the rep to fix, or failed for good. */\n failed: number\n /** Due, and left for a later run: the allowance, a lookup, a hold. */\n held: number\n /** Claims a dead run left, settled. */\n recovered: number\n}\n\ntype Firestore = FirebaseFirestore.Firestore\n\ninterface MailboxRun {\n orgId: string\n org: Record<string, unknown>\n mailbox: OutreachMailbox\n settings: OutreachComplianceSettingsDocument\n report: OutreachSendReport\n deadlineMs: number\n opened: OpenedOutreachMailbox | null\n /** Set once the mailbox itself cannot send this run. */\n stopped: boolean\n templates: Map<string, string | null>\n}\n\nconst blankReport = (): OutreachSendReport => ({\n due: 0,\n sent: 0,\n tasks: 0,\n stopped: 0,\n failed: 0,\n held: 0,\n recovered: 0,\n})\n\n/** Runs the send job once — see the module note. */\nexport async function runOutreachSendJob(\n deps: OutreachRuntimeDeps,\n context: { nowMs: number; deadlineMs: number },\n): Promise<OutreachSendReport> {\n const firestore = deps.firestore()\n const report = blankReport()\n const due = await firestore\n .collectionGroup(OUTREACH_COLLECTIONS.enrollments)\n .where('status', '==', 'active')\n .where('nextDueAtMs', '<=', context.nowMs)\n .orderBy('nextDueAtMs')\n .limit(OUTREACH_SEND_SCAN_LIMIT)\n .get()\n report.due = due.size\n const byOrg = new Map<string, Map<string, OutreachEnrollment[]>>()\n for (const doc of due.docs) {\n const orgId = doc.ref.parent.parent?.id\n const enrollment = readStoredOutreachEnrollment(doc.id, doc.data())\n if (!orgId || !enrollment) continue\n const mailboxes = byOrg.get(orgId) ?? new Map<string, OutreachEnrollment[]>()\n mailboxes.set(enrollment.mailboxId, [...(mailboxes.get(enrollment.mailboxId) ?? []), enrollment])\n byOrg.set(orgId, mailboxes)\n }\n\n for (const [orgId, mailboxes] of byOrg) {\n const waiting = [...mailboxes.values()].reduce((sum, list) => sum + list.length, 0)\n if (deps.now() >= context.deadlineMs) {\n report.held += waiting\n continue\n }\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))) {\n report.held += waiting\n continue\n }\n const settings = await readOutreachComplianceSettingsDoc(firestore, orgId)\n for (const [mailboxId, enrollments] of mailboxes) {\n await runMailbox(deps, firestore, { orgId, org, mailboxId, enrollments, settings, report, context })\n }\n }\n return report\n}\n\n/** The reason an unusable mailbox stops an enrollment, or `null` for a usable one. */\nasync function unusableMailbox(\n firestore: Firestore,\n orgId: string,\n mailbox: OutreachMailbox | null,\n): Promise<string | null> {\n if (!mailbox) return 'The mailbox this sequence sends from was removed.'\n if (mailbox.status === 'disconnected') return 'The mailbox this sequence sends from was disconnected.'\n const member = await firestore.collection('orgs').doc(orgId).collection('members').doc(mailbox.connectedByUid).get()\n return member.exists\n ? null\n : 'The member who connected the mailbox this sequence sends from is no longer in the organization.'\n}\n\nasync function runMailbox(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n input: {\n orgId: string\n org: Record<string, unknown>\n mailboxId: string\n enrollments: OutreachEnrollment[]\n settings: OutreachComplianceSettingsDocument\n report: OutreachSendReport\n context: { nowMs: number; deadlineMs: number }\n },\n): Promise<void> {\n const { orgId, org, report, context } = input\n const nowMs = context.nowMs\n const snapshot = await mailboxRef(firestore, orgId, input.mailboxId).get()\n const mailbox = readStoredOutreachMailbox(input.mailboxId, snapshot.exists ? snapshot.data() : undefined)\n const unusable = await unusableMailbox(firestore, orgId, mailbox)\n if (unusable || !mailbox) {\n for (const enrollment of input.enrollments) {\n const outcome = await applyOutreachEvent(firestore, {\n orgId,\n enrollmentId: enrollment.id,\n event: { type: 'stop', atMs: nowMs, byUid: null, reason: 'gate', detail: unusable },\n nowMs,\n })\n if (outcome.changed) report.stopped += 1\n }\n return\n }\n if (mailbox.status !== 'connected' || (await deps.sendRefusal({ orgId, org, uid: mailbox.connectedByUid }))) {\n report.held += input.enrollments.length\n return\n }\n\n const run: MailboxRun = {\n orgId,\n org,\n mailbox,\n settings: input.settings,\n report,\n deadlineMs: context.deadlineMs,\n opened: null,\n stopped: false,\n templates: new Map(),\n }\n const sequenceIds = [...new Set(input.enrollments.map((enrollment) => enrollment.sequenceId))]\n const sequenceSnapshots = await firestore.getAll(\n ...sequenceIds.map((id) => outreachOrgCollection(firestore, orgId, 'sequences').doc(id)),\n )\n const sequences = new Map<string, OutreachSequence>()\n sequenceSnapshots.forEach((sequenceSnapshot, index) => {\n const sequence = readStoredOutreachSequence(\n sequenceIds[index],\n sequenceSnapshot.exists ? sequenceSnapshot.data() : undefined,\n )\n if (sequence) sequences.set(sequence.id, sequence)\n })\n\n const candidates: OutreachDueCandidate[] = []\n for (const enrollment of input.enrollments) {\n const sequence = sequences.get(enrollment.sequenceId)\n const claim = enrollment.sendClaim\n if (claim && nowMs - claim.atMs < OUTREACH_CLAIM_STALE_MS) continue\n if (claim && sequence) {\n if (await recoverClaim(deps, firestore, run, enrollment, sequence)) report.recovered += 1\n continue\n }\n if (sequence) candidates.push({ enrollment, sequence })\n }\n\n const allowance = outreachTickAllowance({ mailbox, nowMs })\n const selection = selectDueOutreachEnrollments({ candidates, mailbox, nowMs, allowance: allowance.allowance })\n report.held += selection.deferred.length\n\n for (const candidate of selection.tasks) {\n if (deps.now() >= context.deadlineMs) {\n report.held += 1\n continue\n }\n if (await runTaskStep(deps, firestore, run, candidate)) report.tasks += 1\n }\n if (!selection.emails.length) return\n\n // The people behind the emails, read once per site: the contact and its\n // company, and every lookup the gates decide on.\n const people = new Map<string, OutreachEnrollCandidate>()\n const lookups = new Map<string, OutreachGateLookups>()\n const byHost = new Map<string, OutreachEnrollment[]>()\n for (const candidate of selection.emails) {\n const enrollment = candidate.enrollment as OutreachEnrollment\n byHost.set(enrollment.hostId, [...(byHost.get(enrollment.hostId) ?? []), enrollment])\n }\n const siteNames = new Map<string, string>()\n for (const [hostId, enrollments] of byHost) {\n const contactGroupId = consentGroupForHost(org, hostId).groupId\n // Each by the record it names — the contact, or the lead while the\n // person is one (AGL-3234) — keyed by that record's id.\n const [candidatesRead, lookupsRead, host] = await Promise.all([\n readOutreachEnrollCandidates(firestore, {\n orgId,\n hostId,\n contactGroupId,\n people: enrollments.map(outreachEnrollmentPerson),\n }),\n readOutreachGateLookups(firestore, {\n orgId,\n hostId,\n people: enrollments.map((enrollment) => {\n const person = outreachEnrollmentPerson(enrollment)\n return {\n personId: person.id,\n contactId: person.kind === 'contact' ? person.id : null,\n leadId: person.kind === 'lead' ? person.id : null,\n email: enrollment.email,\n }\n }),\n }),\n firestore.collection('hosts').doc(hostId).get(),\n ])\n for (const person of candidatesRead) people.set(person.personId, person)\n for (const [personId, answer] of lookupsRead) lookups.set(personId, answer)\n siteNames.set(hostId, host.exists ? String(host.get('name') ?? '') : '')\n }\n\n for (const candidate of selection.emails) {\n if (run.stopped || deps.now() >= context.deadlineMs) {\n report.held += 1\n continue\n }\n const enrollment = candidate.enrollment as OutreachEnrollment\n const personId = outreachEnrollmentPerson(enrollment).id\n await runEmailStep(deps, firestore, run, {\n enrollment,\n sequence: candidate.sequence as OutreachSequence,\n person: people.get(personId) ?? null,\n lookups: lookups.get(personId) ?? null,\n siteName: siteNames.get(enrollment.hostId) ?? '',\n })\n }\n}\n\n/** Any lookup the runtime could not make: the send waits for a run that can. */\nfunction uncheckable(lookups: OutreachGateLookups | null): boolean {\n return !lookups || Object.values(lookups).some((value) => value === null)\n}\n\n/** The event a gate's refusal ends an enrollment with. */\nfunction gateEvent(blocks: readonly OutreachGateBlock[], atMs: number): OutreachEnrollmentEvent {\n const optedOut = blocks.find((block) => block.code === 'do_not_contact' || block.code === 'sales_opted_out')\n if (optedOut) return { type: 'opt_out', atMs, reason: 'do_not_contact', detail: optedOut.reason }\n return { type: 'stop', atMs, byUid: null, reason: 'gate', detail: blocks.map((block) => block.reason).join(' ') }\n}\n\n/** The sender line a mailbox's mail goes out with. */\nconst senderOf = (mailbox: OutreachMailbox) => ({\n address: mailbox.sendAs || mailbox.email,\n name: mailbox.displayName || null,\n})\n\nasync function openClient(deps: OutreachRuntimeDeps, firestore: Firestore, run: MailboxRun) {\n const opened = (run.opened ??= await deps.openMailbox(run.mailbox.id))\n if (opened.ok === true) return opened.client\n run.stopped = true\n if (opened.reason !== 'not-configured') {\n await markOutreachMailboxReconnectRequired(firestore, {\n orgId: run.orgId,\n mailboxId: run.mailbox.id,\n errorCode: opened.reason,\n nowMs: deps.now(),\n })\n }\n return null\n}\n\n/**\n * Claims one step: the enrollment still active on the same step with no\n * live claim, and — for an email — a slot under the mailbox's cap today,\n * reserved in the same transaction.\n */\nasync function claimStep(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n run: MailboxRun,\n enrollment: OutreachEnrollment,\n messageId: string | null,\n): Promise<'claimed' | 'taken' | 'cap'> {\n const enrollmentRef = outreachOrgCollection(firestore, run.orgId, 'enrollments').doc(enrollment.id)\n const boxRef = mailboxRef(firestore, run.orgId, run.mailbox.id)\n const nowMs = deps.now()\n return firestore.runTransaction(async (transaction) => {\n const [current, box] = await Promise.all([transaction.get(enrollmentRef), transaction.get(boxRef)])\n const stored = readStoredOutreachEnrollment(enrollment.id, current.exists ? current.data() : undefined)\n const claim = stored?.sendClaim\n if (\n !stored ||\n stored.status !== 'active' ||\n stored.stepIndex !== enrollment.stepIndex ||\n stored.nextDueAtMs === null ||\n stored.nextDueAtMs > nowMs ||\n (claim && nowMs - claim.atMs < OUTREACH_CLAIM_STALE_MS)\n ) {\n return 'taken'\n }\n if (messageId) {\n const mailbox = readStoredOutreachMailbox(run.mailbox.id, box.exists ? box.data() : undefined)\n if (!mailbox || mailbox.status !== 'connected') return 'taken'\n const sent = outreachSentToday(mailbox.health, nowMs, mailbox.timezone)\n if (sent >= outreachEffectiveDailyCap(mailbox, nowMs)) return 'cap'\n transaction.update(boxRef, {\n 'health.sentToday': sent + 1,\n 'health.sentOnDay': outreachLocalDay(nowMs, mailbox.timezone),\n })\n }\n transaction.update(enrollmentRef, {\n sendClaim: { token: randomUUID(), atMs: nowMs, stepIndex: enrollment.stepIndex, messageId },\n updatedAtMs: nowMs,\n })\n return 'claimed'\n })\n}\n\n/** Drops a claim, and hands an email's reservation back to the mailbox's day. */\nasync function releaseClaim(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n run: MailboxRun,\n enrollment: Pick<OutreachEnrollment, 'id'>,\n reserved: boolean,\n event: OutreachEnrollmentEvent | null = null,\n): Promise<void> {\n const nowMs = deps.now()\n await applyOutreachEvent(firestore, {\n orgId: run.orgId,\n enrollmentId: enrollment.id,\n event,\n extra: () => ({ sendClaim: null }),\n nowMs,\n })\n if (!reserved) return\n const boxRef = mailboxRef(firestore, run.orgId, run.mailbox.id)\n await firestore.runTransaction(async (transaction) => {\n const box = await transaction.get(boxRef)\n const mailbox = readStoredOutreachMailbox(run.mailbox.id, box.exists ? box.data() : undefined)\n if (!mailbox || mailbox.health?.sentOnDay !== outreachLocalDay(nowMs, mailbox.timezone)) return\n transaction.update(boxRef, { 'health.sentToday': Math.max(0, (Number(mailbox.health.sentToday) || 0) - 1) })\n })\n}\n\n/**\n * Records a step that ran: the engine's completion patch, the claim\n * cleared, the step's record kept — and for an email the mailbox's send on\n * its day, its last-sent time and its window of recent sends. Refuses\n * (answers false) when the claim is no longer this run's.\n */\nasync function completeStep(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n run: MailboxRun,\n input: {\n enrollment: OutreachEnrollment\n sequence: OutreachSequence\n record: OutreachStepRecord\n sent: { messageId: string; threadId: string; subject: string } | null\n claimMessageId: string | null\n },\n): Promise<boolean> {\n const enrollmentRef = outreachOrgCollection(firestore, run.orgId, 'enrollments').doc(input.enrollment.id)\n const boxRef = mailboxRef(firestore, run.orgId, run.mailbox.id)\n const nowMs = deps.now()\n return firestore.runTransaction(async (transaction) => {\n const [current, box] = await Promise.all([\n transaction.get(enrollmentRef),\n input.sent ? transaction.get(boxRef) : Promise.resolve(null),\n ])\n const stored = readStoredOutreachEnrollment(input.enrollment.id, current.exists ? current.data() : undefined)\n if (!stored?.sendClaim || stored.sendClaim.messageId !== input.claimMessageId) return false\n if (stored.stepIndex !== input.enrollment.stepIndex) return false\n // The step RAN, whatever happened to the enrollment while it did — a\n // member paused it, a reply stopped it — so it is recorded as run, and a\n // resume never sends it twice.\n const plan = planOutreachStepCompletion({\n enrollment: { ...stored, status: 'active' },\n sequence: input.sequence,\n mailbox: run.mailbox,\n completedAtMs: nowMs,\n sent: input.sent,\n random: deps.random,\n })\n const patch: Record<string, unknown> = {\n sendClaim: null,\n stepRecords: [...(stored.stepRecords ?? []), input.record].slice(-20),\n updatedAtMs: nowMs,\n ...(plan.patch ?? {}),\n }\n if (stored.status !== 'active') {\n patch['status'] = stored.status\n if (stored.status !== 'paused') patch['nextDueAtMs'] = null\n }\n transaction.update(enrollmentRef, patch)\n if (input.sent && box?.exists) {\n const mailbox = readStoredOutreachMailbox(run.mailbox.id, box.data())\n if (mailbox) {\n const day = outreachLocalDay(nowMs, mailbox.timezone)\n const counts = mailbox.health?.daily?.[day] ?? { sent: 0, bounces: 0, replies: 0 }\n transaction.update(boxRef, {\n 'health.lastSentAtMs': nowMs,\n 'health.lastErrorAtMs': null,\n 'health.lastErrorCode': null,\n [`health.daily.${day}`]: { ...counts, sent: (Number(counts.sent) || 0) + 1 },\n 'health.recentSends': withRecentSend(mailbox.health?.recentSends, {\n id: outreachSendDigest(input.sent.messageId),\n atMs: nowMs,\n bounced: false,\n }),\n updatedAtMs: nowMs,\n })\n }\n }\n return true\n })\n}\n\n/** One task step: the rep's task on the contact, then the step recorded. */\nasync function runTaskStep(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n run: MailboxRun,\n candidate: OutreachDueCandidate,\n): Promise<boolean> {\n const enrollment = candidate.enrollment as OutreachEnrollment\n const sequence = candidate.sequence as OutreachSequence\n const step = sequence.steps[enrollment.stepIndex] as OutreachTaskStep\n if ((await claimStep(deps, firestore, run, enrollment, null)) !== 'claimed') return false\n const nowMs = deps.now()\n let taskId: string | null\n try {\n const written = await fileOutreachTask(deps, {\n orgId: run.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n dedupeKey: `step:${enrollment.id}:${step.id}`,\n title: step.title,\n notes: `From sequence step ${enrollment.stepIndex + 1}.`,\n kind: OUTREACH_TASK_KIND_TO_RECORD[step.taskKind] ?? 'todo',\n dueAtMs: nowMs,\n assigneeUid: run.mailbox.connectedByUid,\n })\n taskId = written?.ok ? written.id : null\n if (written?.ok === false) console.warn(`[outreach] a task step's task was not filed: ${written.error}`)\n } catch (error) {\n // The record system failed rather than refused: the step waits for a\n // run that can file its task.\n console.error('[outreach] filing a task step failed; the step waits', error)\n await releaseClaim(deps, firestore, run, enrollment, false)\n return false\n }\n return completeStep(deps, firestore, run, {\n enrollment,\n sequence,\n record: { stepIndex: enrollment.stepIndex, stepId: step.id, kind: 'task', atMs: nowMs, taskId },\n sent: null,\n claimMessageId: null,\n })\n}\n\n/** A CRM template's body, read once per run. */\nasync function templateBody(firestore: Firestore, run: MailboxRun, templateId: string): Promise<string | null> {\n if (!run.templates.has(templateId)) {\n const snapshot = await firestore\n .collection('orgs')\n .doc(run.orgId)\n .collection(CRM_COLLECTIONS.emailTemplates)\n .doc(templateId)\n .get()\n run.templates.set(\n templateId,\n snapshot.exists ? normalizeCrmEmailTemplate(snapshot.data() as Record<string, unknown>).body : null,\n )\n }\n return run.templates.get(templateId) ?? null\n}\n\nasync function runEmailStep(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n run: MailboxRun,\n input: {\n enrollment: OutreachEnrollment\n sequence: OutreachSequence\n person: OutreachEnrollCandidate | null\n lookups: OutreachGateLookups | null\n siteName: string\n },\n): Promise<void> {\n const { enrollment, sequence, person } = input\n const { report, mailbox } = run\n const nowMs = deps.now()\n const stop = async (event: OutreachEnrollmentEvent, counter: 'stopped' | 'failed') => {\n const outcome = await applyOutreachEvent(firestore, { orgId: run.orgId, enrollmentId: enrollment.id, event, nowMs })\n if (outcome.changed) report[counter] += 1\n }\n\n // Every gate again, on the person as they stand now.\n if (!person?.contact) {\n return stop(\n {\n type: 'stop',\n atMs: nowMs,\n byUid: null,\n reason: 'gate',\n detail:\n enrollment.target === 'lead'\n ? \"This lead no longer exists in the sequence's site's CRM.\"\n : 'This contact no longer exists in the CRM.',\n },\n 'stopped',\n )\n }\n if (person.convertedContactId && enrollment.target === 'lead') {\n // The lead converted and the enrollment has not followed it yet: the\n // conversion's listener re-points it, and the next run sends as the contact.\n report.held += 1\n return\n }\n if (!person.visible) {\n return stop(\n { type: 'stop', atMs: nowMs, byUid: null, reason: 'gate', detail: \"This contact is no longer in the sequence's site's CRM.\" },\n 'stopped',\n )\n }\n if (uncheckable(input.lookups)) {\n report.held += 1\n return\n }\n const contactGroupId = consentGroupForHost(run.org, enrollment.hostId).groupId\n const gate = evaluateOutreachGates({\n email: enrollment.email,\n contact: person.contact,\n contactGroupId,\n company: person.company,\n settings: {\n ...sequence.settings,\n allowedCountries: effectiveOutreachAllowedCountries(\n sequence.settings.allowedCountries,\n run.settings.allowedCountries,\n ),\n },\n personalLine: enrollment.personalLine,\n attestations: enrollment.attestations,\n enrollmentId: enrollment.id,\n lookups: input.lookups as OutreachGateLookups,\n })\n if (!gate.allowed) return stop(gateEvent(gate.blocks, nowMs), 'stopped')\n\n // The email, with its way out.\n const listUnsubscribeUrl = deps.unsubscribeUrl({ orgId: run.orgId, enrollmentId: enrollment.id })\n const mailto = outreachUnsubscribeMailbox(mailbox.email)\n if (!listUnsubscribeUrl || !mailto) {\n // No link can be minted — no secret, or no HTTPS console origin — and an\n // email without its one-click way out does not leave.\n console.error('[outreach] no unsubscribe link could be minted; nothing sends until one can')\n run.stopped = true\n report.held += 1\n return\n }\n const step = sequence.steps[enrollment.stepIndex]\n const sender = senderOf(mailbox)\n const composed = composeOutreachEmail({\n sequence,\n enrollment,\n orgSettings: run.settings,\n merge: {\n // For a lead, the contact-shaped view of it (`leadAsContact`), so a\n // step written with `{{contact.*}}` reads the lead; `{{lead.*}}` reads\n // the lead's own document beside it.\n contact: person.contact,\n contactGroupId,\n lead: person.lead,\n sender: { name: mailbox.displayName, email: sender.address },\n site: { name: input.siteName },\n },\n templateBody: step?.kind === 'email' && step.templateId ? await templateBody(firestore, run, step.templateId) : null,\n listUnsubscribeUrl,\n listUnsubscribeMailto: mailto.uri,\n })\n if (composed.error || !composed.email) {\n const code = composed.error?.code\n if (code === 'missing_postal_address' || code === 'missing_legal_name') {\n // The organization's footer: nothing of theirs sends until it is set.\n run.stopped = true\n report.held += 1\n return\n }\n if (code === 'invalid_recipient' || code === 'missing_thread' || code === 'not_an_email_step') {\n return stop({ type: 'send_failed', atMs: nowMs, detail: composed.error?.message }, 'failed')\n }\n // The sequence's own words: the rep fixes the step and resumes.\n return stop({ type: 'pause', atMs: nowMs, byUid: null, detail: composed.error?.message }, 'failed')\n }\n if (composed.unresolvedFields.length) {\n const fields = composed.unresolvedFields.map((field) => `{{${field}}}`).join(', ')\n return stop(\n {\n type: 'pause',\n atMs: nowMs,\n byUid: null,\n detail: `Paused before sending: this contact has nothing for ${fields}. Fill it in and resume.`,\n },\n 'failed',\n )\n }\n\n // Claimed, then sent, then recorded.\n const domain = sender.address.slice(sender.address.lastIndexOf('@') + 1)\n const local = randomUUID()\n const messageId = `<${local}@${domain}>`\n const claimed = await claimStep(deps, firestore, run, enrollment, messageId)\n if (claimed !== 'claimed') {\n if (claimed === 'cap') run.stopped = true\n report.held += 1\n return\n }\n const client = await openClient(deps, firestore, run)\n if (!client) {\n await releaseClaim(deps, firestore, run, enrollment, true)\n report.held += 1\n return\n }\n let sent: Awaited<ReturnType<typeof sendComposedOutreachEmail>>\n try {\n sent = await sendComposedOutreachEmail(client, composed.email, sender, {\n messageIdLocalPart: local,\n messageIdDomain: domain,\n })\n } catch (error) {\n if (error instanceof Rfc5322MessageError || (error instanceof GmailTransportError && error.code === 'invalid_request')) {\n await releaseClaim(deps, firestore, run, enrollment, true, {\n type: 'send_failed',\n atMs: deps.now(),\n detail: `Gmail refused the email: ${error.message}`,\n })\n report.failed += 1\n return\n }\n await releaseClaim(deps, firestore, run, enrollment, true)\n report.held += 1\n run.stopped = true\n const code = error instanceof GmailTransportError ? error.code : 'unexpected'\n if (isReconnectRequired(error)) {\n await markOutreachMailboxReconnectRequired(firestore, {\n orgId: run.orgId,\n mailboxId: mailbox.id,\n errorCode: code,\n nowMs: deps.now(),\n })\n } else {\n await mailboxRef(firestore, run.orgId, mailbox.id)\n .update({ 'health.lastErrorAtMs': deps.now(), 'health.lastErrorCode': code })\n .catch(() => undefined)\n }\n console.error(`[outreach] a send from a mailbox failed (${code}); its sends wait for the next run`)\n return\n }\n\n const record: OutreachStepRecord = {\n stepIndex: enrollment.stepIndex,\n stepId: step?.id ?? '',\n kind: 'email',\n atMs: deps.now(),\n gmailMessageId: sent.gmailMessageId,\n gmailThreadId: sent.threadId,\n messageId: sent.messageId,\n subject: sent.subject,\n }\n await completeStep(deps, firestore, run, {\n enrollment,\n sequence,\n record,\n sent: { messageId: sent.messageId, threadId: sent.threadId, subject: sent.subject },\n claimMessageId: messageId,\n })\n report.sent += 1\n await fileOutreachEmail(deps, {\n orgId: run.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n direction: 'outbound',\n subject: sent.subject,\n from: sender.address,\n to: composed.email.to,\n messageId: sent.messageId,\n inReplyTo: composed.email.inReplyTo ?? null,\n body: composed.email.text,\n atMs: record.atMs,\n byUid: mailbox.connectedByUid,\n byName: mailbox.displayName || null,\n })\n}\n\n/**\n * Settles a claim a run died holding. A task step's claim is dropped and\n * the step runs again (its task is filed once however often it is asked).\n * An email's claim looks for the message it minted in the mailbox: found,\n * the send happened and is recorded; not found, it did not, and the claim\n * and its reservation are handed back for the next run.\n */\nasync function recoverClaim(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n run: MailboxRun,\n enrollment: OutreachEnrollment,\n sequence: OutreachSequence,\n): Promise<boolean> {\n const claim = enrollment.sendClaim\n if (!claim) return false\n if (!claim.messageId) {\n await releaseClaim(deps, firestore, run, enrollment, false)\n return true\n }\n const client = await openClient(deps, firestore, run)\n if (!client) return false\n const bare = claim.messageId.replace(/^<|>$/g, '')\n const found = await client.listMessages({ q: `rfc822msgid:${bare}`, maxResults: 1, includeSpamTrash: true })\n const hit = found.messages[0]\n if (!hit) {\n await releaseClaim(deps, firestore, run, enrollment, true)\n return true\n }\n const message = await client.getMessage(hit.id, { metadataHeaders: ['Subject'] })\n const subject = message.headers.find((header) => header.name.toLowerCase() === 'subject')?.value ?? ''\n const step = sequence.steps[enrollment.stepIndex]\n return completeStep(deps, firestore, run, {\n enrollment,\n sequence,\n record: {\n stepIndex: enrollment.stepIndex,\n stepId: step?.id ?? '',\n kind: 'email',\n atMs: deps.now(),\n gmailMessageId: hit.id,\n gmailThreadId: hit.threadId,\n messageId: claim.messageId,\n subject,\n },\n sent: { messageId: claim.messageId, threadId: hit.threadId, subject },\n claimMessageId: claim.messageId,\n })\n}\n"],"names":["consentGroupForHost","CRM_COLLECTIONS","normalizeCrmEmailTemplate","randomUUID","composeOutreachEmail","planOutreachStepCompletion","evaluateOutreachGates","outreachSentToday","outreachTickAllowance","selectDueOutreachEnrollments","readOutreachEnrollCandidates","readOutreachGateLookups","mailboxRef","outreachEffectiveDailyCap","outreachLocalDay","markOutreachMailboxReconnectRequired","effectiveOutreachAllowedCountries","OUTREACH_COLLECTIONS","readOutreachComplianceSettingsDoc","outreachEnrollmentLink","outreachEnrollmentPerson","outreachOrgCollection","readStoredOutreachEnrollment","readStoredOutreachMailbox","readStoredOutreachSequence","GmailTransportError","isReconnectRequired","Rfc5322MessageError","sendComposedOutreachEmail","applyOutreachEvent","outreachSendDigest","withRecentSend","fileOutreachEmail","fileOutreachTask","OUTREACH_TASK_KIND_TO_RECORD","outreachUnsubscribeMailbox","OUTREACH_SEND_JOB_ID","OUTREACH_SEND_SCAN_LIMIT","OUTREACH_CLAIM_STALE_MS","blankReport","due","sent","tasks","stopped","failed","held","recovered","runOutreachSendJob","deps","context","firestore","report","collectionGroup","enrollments","where","nowMs","orderBy","limit","get","size","byOrg","Map","doc","docs","mailboxes","orgId","ref","parent","id","enrollment","data","set","mailboxId","waiting","values","reduce","sum","list","length","now","deadlineMs","orgSnapshot","collection","org","exists","orgRefusal","settings","runMailbox","unusableMailbox","mailbox","status","member","connectedByUid","input","snapshot","undefined","unusable","outcome","enrollmentId","event","type","atMs","byUid","reason","detail","changed","sendRefusal","uid","run","opened","templates","sequenceIds","Set","map","sequenceId","sequenceSnapshots","getAll","sequences","forEach","sequenceSnapshot","index","sequence","candidates","claim","sendClaim","recoverClaim","push","allowance","selection","deferred","candidate","runTaskStep","emails","people","lookups","byHost","hostId","siteNames","host","contactGroupId","groupId","candidatesRead","lookupsRead","Promise","all","person","personId","contactId","kind","leadId","email","answer","String","runEmailStep","siteName","uncheckable","Object","some","value","gateEvent","blocks","optedOut","find","block","code","join","senderOf","address","sendAs","name","displayName","openClient","openMailbox","ok","client","errorCode","claimStep","messageId","enrollmentRef","boxRef","runTransaction","transaction","current","box","stored","stepIndex","nextDueAtMs","health","timezone","update","token","updatedAtMs","releaseClaim","reserved","extra","sentOnDay","Math","max","Number","sentToday","completeStep","plan","resolve","claimMessageId","completedAtMs","random","patch","stepRecords","record","slice","day","counts","daily","bounces","replies","recentSends","bounced","step","steps","taskId","written","link","dedupeKey","title","notes","taskKind","dueAtMs","assigneeUid","console","warn","error","stepId","templateBody","templateId","has","emailTemplates","body","composed","stop","counter","contact","target","convertedContactId","visible","gate","company","allowedCountries","personalLine","attestations","allowed","listUnsubscribeUrl","unsubscribeUrl","mailto","sender","orgSettings","merge","lead","site","listUnsubscribeMailto","uri","message","unresolvedFields","fields","field","domain","lastIndexOf","local","claimed","messageIdLocalPart","messageIdDomain","catch","gmailMessageId","gmailThreadId","threadId","subject","direction","from","to","inReplyTo","text","byName","bare","replace","found","listMessages","q","maxResults","includeSpamTrash","hit","messages","getMessage","metadataHeaders","headers","header","toLowerCase"],"mappings":"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"}
@@ -24,10 +24,11 @@ import { mailboxRef } from "../mailboxes/mailbox-credentials.js";
24
24
  import { markOutreachMailboxReconnectRequired } from "../mailboxes/mailbox-transport.js";
25
25
  import { OUTREACH_COLLECTIONS } from "../model/outreach.types.js";
26
26
  import { addOutreachDoNotContact } from "../storage/do-not-contact-store.js";
27
- import { outreachOrgCollection, readStoredOutreachEnrollment, readStoredOutreachMailbox, readStoredOutreachSequence } from "../storage/outreach-records.js";
27
+ import { outreachEnrollmentLink, outreachOrgCollection, readStoredOutreachEnrollment, readStoredOutreachMailbox, readStoredOutreachSequence } from "../storage/outreach-records.js";
28
28
  import { GmailTransportError, isReconnectRequired } from "../transport/gmail-errors.js";
29
29
  import { applyOutreachEvent, recordOutreachOptOut } from "./enrollment-events.js";
30
30
  import { applyOutreachMailboxHealth, emptyOutreachHealthDelta } from "./mailbox-health-store.js";
31
+ import { markOutreachLeadWorking } from "./lead-records.js";
31
32
  import { fileOutreachEmail, fileOutreachTask } from "./timeline.js";
32
33
  import { outreachUnsubscribeMailbox } from "./unsubscribe-link.js";
33
34
  /*==========================================
@@ -310,7 +311,7 @@ async function syncMailbox(deps, firestore, input) {
310
311
  await fileOutreachEmail(context.deps, {
311
312
  orgId: context.orgId,
312
313
  hostId: enrollment.hostId,
313
- contactId: enrollment.contactId,
314
+ link: outreachEnrollmentLink(enrollment),
314
315
  direction: 'inbound',
315
316
  subject: message.subject,
316
317
  from: (_emailAddressOf = emailAddressOf(message.from)) != null ? _emailAddressOf : message.from,
@@ -388,7 +389,7 @@ async function syncMailbox(deps, firestore, input) {
388
389
  await fileOutreachTask(deps, {
389
390
  orgId: context.orgId,
390
391
  hostId: enrollment.hostId,
391
- contactId: enrollment.contactId,
392
+ link: outreachEnrollmentLink(enrollment),
392
393
  dedupeKey: `reply:${(_messageIdOf = messageIdOf(decidingMessage)) != null ? _messageIdOf : decidingMessage.id}`,
393
394
  title: `Reply from ${enrollment.contactName || enrollment.email}`,
394
395
  notes: crmInboundExcerpt((_ref2 = (_decidingMessage_textBody = decidingMessage.textBody) != null ? _decidingMessage_textBody : decidingMessage.snippet) != null ? _ref2 : '', (_decidingMessage_htmlBody = decidingMessage.htmlBody) != null ? _decidingMessage_htmlBody : '').slice(0, 500),
@@ -400,6 +401,13 @@ async function syncMailbox(deps, firestore, input) {
400
401
  console.error('[outreach] the reply task could not be filed', error);
401
402
  }
402
403
  }
404
+ // A lead somebody wrote back to is being worked (AGL-3234).
405
+ if (enrollment.target === 'lead' && enrollment.leadId) {
406
+ await markOutreachLeadWorking(firestore, {
407
+ hostId: enrollment.hostId,
408
+ leadId: enrollment.leadId
409
+ });
410
+ }
403
411
  context.delta.replies += 1;
404
412
  event = {
405
413
  type: 'reply',
@@ -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 { crmInboundExcerpt, emailAddressOf } from '@aglyn/aglyn/app-utils/crm-inbound'\nimport { outreachMessageIdHeader } from '../engine/compose'\nimport { readOutreachDeliveryReport } from '../engine/delivery-status'\nimport type { OutreachEnrollmentEvent } from '../engine/enrollment-state'\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 { markOutreachMailboxReconnectRequired } from '../mailboxes/mailbox-transport'\nimport {\n OUTREACH_COLLECTIONS,\n type OutreachEnrollment,\n type OutreachMailbox,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport { addOutreachDoNotContact } from '../storage/do-not-contact-store'\nimport {\n outreachOrgCollection,\n readStoredOutreachEnrollment,\n readStoredOutreachMailbox,\n readStoredOutreachSequence,\n} from '../storage/outreach-records'\nimport type { GmailClient } from '../transport/gmail-client'\nimport { GmailTransportError, isReconnectRequired } from '../transport/gmail-errors'\nimport { applyOutreachEvent, recordOutreachOptOut } from './enrollment-events'\nimport {\n applyOutreachMailboxHealth,\n emptyOutreachHealthDelta,\n type OutreachMailboxHealthDelta,\n} from './mailbox-health-store'\nimport type { OutreachRuntimeDeps } from './runtime-deps'\nimport { fileOutreachEmail, fileOutreachTask } 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.\n *\n * Four reads find those, each narrowed by Gmail rather than read in full:\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 postponed: number\n paused: number\n}\n\ninterface SyncContext {\n deps: OutreachRuntimeDeps\n firestore: Firestore\n orgId: string\n mailbox: OutreachMailbox\n client: GmailClient\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 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 markOutreachMailboxReconnectRequired(firestore, {\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(\n client: GmailClient,\n q: string,\n): Promise<Array<{ id: string; threadId: string }>> {\n const found: Array<{ id: string; threadId: string }> = []\n let pageToken: string | null = null\n for (let page = 0; page < SEARCH_PAGES; page += 1) {\n const list = await client.listMessages({ q, maxResults: 100, pageToken, includeSpamTrash: true })\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 markOutreachMailboxReconnectRequired(firestore, {\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 for (const doc of watched.docs) {\n const enrollment = readStoredOutreachEnrollment(doc.id, doc.data())\n if (!enrollment) continue\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 const after = `after:${Math.floor(sinceMs / 1000)}`\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: { id: string; threadId: string }) => !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, `${after} -from:me`)\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, `${after} from:(mailer-daemon OR 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, `${after} 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, `${after} from:(${chunk.join(' OR ')})`)) {\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 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 contactId: enrollment.contactId,\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: crmInboundExcerpt(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 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 })\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 contactId: enrollment.contactId,\n dedupeKey: `reply:${messageIdOf(decidingMessage) ?? decidingMessage.id}`,\n title: `Reply from ${enrollment.contactName || enrollment.email}`,\n notes: crmInboundExcerpt(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 context.delta.replies += 1\n event = { type: 'reply', atMs: decidedBy?.atMs || nowMs, detail: decidedBy?.evidence ?? null }\n report.replies += 1\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 event = { type: 'bounce', atMs: decidedBy?.atMs || nowMs, detail: diagnostic }\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 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","crmInboundExcerpt","emailAddressOf","outreachMessageIdHeader","readOutreachDeliveryReport","effectiveOutreachWindow","postponeOutreachForAutoReply","decideOutreachThread","outreachHeader","outreachThreadMessageFromGmail","mailboxRef","markOutreachMailboxReconnectRequired","OUTREACH_COLLECTIONS","addOutreachDoNotContact","outreachOrgCollection","readStoredOutreachEnrollment","readStoredOutreachMailbox","readStoredOutreachSequence","GmailTransportError","isReconnectRequired","applyOutreachEvent","recordOutreachOptOut","applyOutreachMailboxHealth","emptyOutreachHealthDelta","fileOutreachEmail","fileOutreachTask","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","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","q","found","pageToken","page","list","listMessages","maxResults","includeSpamTrash","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","enrollment","known","threadId","gmailThreadIds","lastSentAtMs","sinceMs","Math","max","Number","sync","throughMs","after","floor","handled","Set","handledMessageIds","newlyHandled","markHandled","has","add","fresh","stub","inbound","thread","getFullThread","applyMessages","getFullMessage","delivery","kind","failedAddresses","metadata","getMessage","metadataHeaders","from","headers","find","header","name","toLowerCase","value","includes","source","hostIds","hostId","detail","addresses","keys","start","length","chunk","slice","join","update","sequenceOf","sequenceId","snapshot","undefined","messageIdOf","scrubbedOutreachDiagnostic","diagnostic","pattern","RegExp","replace","fileInbound","messageId","contactId","direction","subject","to","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","dedupeKey","title","contactName","notes","dueAtMs","assigneeUid","connectedByUid","type","suppressBouncedEmail","enrollmentId","sequence","nextDueAtMs","receivedAtMs","timeZone","timezone","window","settings","random","extra","current","waiting","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 { crmInboundExcerpt, emailAddressOf } from '@aglyn/aglyn/app-utils/crm-inbound'\nimport { outreachMessageIdHeader } from '../engine/compose'\nimport { readOutreachDeliveryReport } from '../engine/delivery-status'\nimport type { OutreachEnrollmentEvent } from '../engine/enrollment-state'\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 { markOutreachMailboxReconnectRequired } from '../mailboxes/mailbox-transport'\nimport {\n OUTREACH_COLLECTIONS,\n type OutreachEnrollment,\n type OutreachMailbox,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport { addOutreachDoNotContact } from '../storage/do-not-contact-store'\nimport {\n outreachEnrollmentLink,\n outreachOrgCollection,\n readStoredOutreachEnrollment,\n readStoredOutreachMailbox,\n readStoredOutreachSequence,\n} from '../storage/outreach-records'\nimport type { GmailClient } from '../transport/gmail-client'\nimport { GmailTransportError, isReconnectRequired } from '../transport/gmail-errors'\nimport { applyOutreachEvent, recordOutreachOptOut } from './enrollment-events'\nimport {\n applyOutreachMailboxHealth,\n emptyOutreachHealthDelta,\n type OutreachMailboxHealthDelta,\n} from './mailbox-health-store'\nimport type { OutreachRuntimeDeps } from './runtime-deps'\nimport { markOutreachLeadWorking } from './lead-records'\nimport { fileOutreachEmail, fileOutreachTask } 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.\n *\n * Four reads find those, each narrowed by Gmail rather than read in full:\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 postponed: number\n paused: number\n}\n\ninterface SyncContext {\n deps: OutreachRuntimeDeps\n firestore: Firestore\n orgId: string\n mailbox: OutreachMailbox\n client: GmailClient\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 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 markOutreachMailboxReconnectRequired(firestore, {\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(\n client: GmailClient,\n q: string,\n): Promise<Array<{ id: string; threadId: string }>> {\n const found: Array<{ id: string; threadId: string }> = []\n let pageToken: string | null = null\n for (let page = 0; page < SEARCH_PAGES; page += 1) {\n const list = await client.listMessages({ q, maxResults: 100, pageToken, includeSpamTrash: true })\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 markOutreachMailboxReconnectRequired(firestore, {\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 for (const doc of watched.docs) {\n const enrollment = readStoredOutreachEnrollment(doc.id, doc.data())\n if (!enrollment) continue\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 const after = `after:${Math.floor(sinceMs / 1000)}`\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: { id: string; threadId: string }) => !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, `${after} -from:me`)\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, `${after} from:(mailer-daemon OR 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, `${after} 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, `${after} from:(${chunk.join(' OR ')})`)) {\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 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: crmInboundExcerpt(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 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 })\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: crmInboundExcerpt(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).\n if (enrollment.target === 'lead' && enrollment.leadId) {\n await markOutreachLeadWorking(firestore, { hostId: enrollment.hostId, leadId: enrollment.leadId })\n }\n context.delta.replies += 1\n event = { type: 'reply', atMs: decidedBy?.atMs || nowMs, detail: decidedBy?.evidence ?? null }\n report.replies += 1\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 event = { type: 'bounce', atMs: decidedBy?.atMs || nowMs, detail: diagnostic }\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 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","crmInboundExcerpt","emailAddressOf","outreachMessageIdHeader","readOutreachDeliveryReport","effectiveOutreachWindow","postponeOutreachForAutoReply","decideOutreachThread","outreachHeader","outreachThreadMessageFromGmail","mailboxRef","markOutreachMailboxReconnectRequired","OUTREACH_COLLECTIONS","addOutreachDoNotContact","outreachEnrollmentLink","outreachOrgCollection","readStoredOutreachEnrollment","readStoredOutreachMailbox","readStoredOutreachSequence","GmailTransportError","isReconnectRequired","applyOutreachEvent","recordOutreachOptOut","applyOutreachMailboxHealth","emptyOutreachHealthDelta","markOutreachLeadWorking","fileOutreachEmail","fileOutreachTask","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","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","q","found","pageToken","page","list","listMessages","maxResults","includeSpamTrash","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","enrollment","known","threadId","gmailThreadIds","lastSentAtMs","sinceMs","Math","max","Number","sync","throughMs","after","floor","handled","Set","handledMessageIds","newlyHandled","markHandled","has","add","fresh","stub","inbound","thread","getFullThread","applyMessages","getFullMessage","delivery","kind","failedAddresses","metadata","getMessage","metadataHeaders","from","headers","find","header","name","toLowerCase","value","includes","source","hostIds","hostId","detail","addresses","keys","start","length","chunk","slice","join","update","sequenceOf","sequenceId","snapshot","undefined","messageIdOf","scrubbedOutreachDiagnostic","diagnostic","pattern","RegExp","replace","fileInbound","messageId","link","direction","subject","to","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","dedupeKey","title","contactName","notes","dueAtMs","assigneeUid","connectedByUid","target","leadId","type","suppressBouncedEmail","enrollmentId","sequence","nextDueAtMs","receivedAtMs","timeZone","timezone","window","settings","random","extra","current","waiting","Object","assign"],"mappings":"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"}
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import type { PluginRecordTaskKind, PluginRecordWrite } from '@aglyn/aglyn/plugin-manager/plugin-record-timeline';
17
+ import type { PluginRecordLink, PluginRecordTaskKind, PluginRecordWrite } from '@aglyn/aglyn/plugin-manager/plugin-record-timeline';
18
18
  import type { OutreachTaskKind } from '../model/outreach.types';
19
19
  import type { OutreachRuntimeDeps } from './runtime-deps';
20
20
  /**
@@ -33,11 +33,12 @@ import type { OutreachRuntimeDeps } from './runtime-deps';
33
33
  */
34
34
  /** The record system's word for a step's task kind. */
35
35
  export declare const OUTREACH_TASK_KIND_TO_RECORD: Record<OutreachTaskKind, PluginRecordTaskKind>;
36
- /** Files one email on a contact; never throws. */
36
+ /** Files one email on a person's record — the contact, or the lead while they are one (AGL-3234); never throws. */
37
37
  export declare function fileOutreachEmail(deps: Pick<OutreachRuntimeDeps, 'timeline'>, input: {
38
38
  orgId: string;
39
39
  hostId: string;
40
- contactId: string;
40
+ /** The record the entry lands on: `{ contactId }` or `{ leadId }`. */
41
+ link: PluginRecordLink;
41
42
  direction: 'outbound' | 'inbound';
42
43
  subject: string;
43
44
  from: string;
@@ -57,7 +58,8 @@ export declare function fileOutreachEmail(deps: Pick<OutreachRuntimeDeps, 'timel
57
58
  export declare function fileOutreachTask(deps: Pick<OutreachRuntimeDeps, 'timeline'>, input: {
58
59
  orgId: string;
59
60
  hostId: string;
60
- contactId: string;
61
+ /** The record the task lands on: `{ contactId }` or `{ leadId }`. */
62
+ link: PluginRecordLink;
61
63
  dedupeKey: string;
62
64
  title: string;
63
65
  notes?: string | null;
@@ -32,7 +32,7 @@
32
32
  linkedin: 'todo',
33
33
  todo: 'todo'
34
34
  };
35
- /** Files one email on a contact; never throws. */ export async function fileOutreachEmail(deps, input) {
35
+ /** Files one email on a person's record — the contact, or the lead while they are one (AGL-3234); never throws. */ export async function fileOutreachEmail(deps, input) {
36
36
  const writer = deps.timeline();
37
37
  if (!writer) return null;
38
38
  try {
@@ -40,9 +40,7 @@
40
40
  const written = await writer.logActivity({
41
41
  orgId: input.orgId,
42
42
  hostId: input.hostId,
43
- link: {
44
- contactId: input.contactId
45
- },
43
+ link: input.link,
46
44
  sourcePluginId: OUTREACH_PLUGIN_ID,
47
45
  kind: 'email',
48
46
  atMs: input.atMs,
@@ -76,9 +74,7 @@
76
74
  return writer.createTask({
77
75
  orgId: input.orgId,
78
76
  hostId: input.hostId,
79
- link: {
80
- contactId: input.contactId
81
- },
77
+ link: input.link,
82
78
  sourcePluginId: OUTREACH_PLUGIN_ID,
83
79
  dedupeKey: input.dedupeKey,
84
80
  title: input.title,