@aglyn/plugins-outreach 1.0.0-beta.224 → 1.0.0-beta.225
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/package.json +14 -14
- package/src/lib/components/enrollment-detail.d.ts +8 -0
- package/src/lib/components/enrollment-detail.js +17 -5
- package/src/lib/components/enrollment-detail.js.map +1 -1
- package/src/lib/components/mailbox-card.d.ts +3 -3
- package/src/lib/components/mailbox-card.js +11 -5
- package/src/lib/components/mailbox-card.js.map +1 -1
- package/src/lib/components/mailboxes-section.d.ts +6 -5
- package/src/lib/components/mailboxes-section.js +61 -26
- package/src/lib/components/mailboxes-section.js.map +1 -1
- package/src/lib/components/sequence-detail.js +1 -1
- package/src/lib/components/sequence-detail.js.map +1 -1
- package/src/lib/components/sequence-editor.js +29 -4
- package/src/lib/components/sequence-editor.js.map +1 -1
- package/src/lib/components/sequence-mailbox-picker.d.ts +21 -0
- package/src/lib/components/sequence-mailbox-picker.js +74 -13
- package/src/lib/components/sequence-mailbox-picker.js.map +1 -1
- package/src/lib/components/use-outreach-mailbox-api.d.ts +10 -2
- package/src/lib/components/use-outreach-mailbox-api.js +7 -2
- package/src/lib/components/use-outreach-mailbox-api.js.map +1 -1
- package/src/lib/engine/enrollment-state.d.ts +6 -0
- package/src/lib/engine/enrollment-state.js +1 -1
- package/src/lib/engine/enrollment-state.js.map +1 -1
- package/src/lib/engine/index.d.ts +1 -0
- package/src/lib/engine/index.js +1 -0
- package/src/lib/engine/index.js.map +1 -1
- package/src/lib/engine/mailbox-notice.d.ts +5 -2
- package/src/lib/engine/mailbox-notice.js +6 -2
- package/src/lib/engine/mailbox-notice.js.map +1 -1
- package/src/lib/engine/mailbox-rotation.d.ts +41 -0
- package/src/lib/engine/mailbox-rotation.js +71 -0
- package/src/lib/engine/mailbox-rotation.js.map +1 -0
- package/src/lib/mailboxes/mailbox-api.d.ts +30 -11
- package/src/lib/mailboxes/mailbox-api.js +7 -3
- package/src/lib/mailboxes/mailbox-api.js.map +1 -1
- package/src/lib/mailboxes/mailbox-credentials.d.ts +15 -12
- package/src/lib/mailboxes/mailbox-credentials.js +12 -8
- package/src/lib/mailboxes/mailbox-credentials.js.map +1 -1
- package/src/lib/mailboxes/mailbox-erasure.js +3 -0
- package/src/lib/mailboxes/mailbox-erasure.js.map +1 -1
- package/src/lib/mailboxes/mailbox-revoke.d.ts +10 -5
- package/src/lib/mailboxes/mailbox-revoke.js +1 -0
- package/src/lib/mailboxes/mailbox-revoke.js.map +1 -1
- package/src/lib/mailboxes/mailbox-routes.d.ts +11 -3
- package/src/lib/mailboxes/mailbox-routes.js +218 -35
- package/src/lib/mailboxes/mailbox-routes.js.map +1 -1
- package/src/lib/mailboxes/mailbox-settings.d.ts +6 -2
- package/src/lib/mailboxes/mailbox-settings.js +5 -1
- package/src/lib/mailboxes/mailbox-settings.js.map +1 -1
- package/src/lib/mailboxes/mailbox-transport.d.ts +15 -9
- package/src/lib/mailboxes/mailbox-transport.js +47 -17
- package/src/lib/mailboxes/mailbox-transport.js.map +1 -1
- package/src/lib/mailboxes/oauth-state.d.ts +11 -3
- package/src/lib/mailboxes/oauth-state.js +17 -8
- package/src/lib/mailboxes/oauth-state.js.map +1 -1
- package/src/lib/mailboxes/outreach-config.d.ts +30 -0
- package/src/lib/mailboxes/outreach-config.js +53 -10
- package/src/lib/mailboxes/outreach-config.js.map +1 -1
- package/src/lib/mailboxes/register-mailbox-routes.js +2 -1
- package/src/lib/mailboxes/register-mailbox-routes.js.map +1 -1
- package/src/lib/model/outreach.types.d.ts +13 -2
- package/src/lib/model/outreach.types.js +7 -2
- package/src/lib/model/outreach.types.js.map +1 -1
- package/src/lib/model/sequence-draft.d.ts +6 -1
- package/src/lib/model/sequence-draft.js +16 -4
- package/src/lib/model/sequence-draft.js.map +1 -1
- package/src/lib/routes/enroll-routes.js +52 -1
- package/src/lib/routes/enroll-routes.js.map +1 -1
- package/src/lib/routes/sequence-routes.js +11 -3
- package/src/lib/routes/sequence-routes.js.map +1 -1
- package/src/lib/runtime/fixtures/fake-gmail.d.ts +18 -3
- package/src/lib/runtime/fixtures/fake-gmail.js +21 -1
- package/src/lib/runtime/fixtures/fake-gmail.js.map +1 -1
- package/src/lib/runtime/mailbox-notices.js +2 -2
- package/src/lib/runtime/mailbox-notices.js.map +1 -1
- package/src/lib/runtime/send-job.js +2 -8
- package/src/lib/runtime/send-job.js.map +1 -1
- package/src/lib/runtime/sync-job.js +24 -11
- package/src/lib/runtime/sync-job.js.map +1 -1
- package/src/lib/subprocessors.d.ts +12 -1
- package/src/lib/subprocessors.js +25 -2
- package/src/lib/subprocessors.js.map +1 -1
- package/src/lib/transfer/sequences-package.js +7 -2
- package/src/lib/transfer/sequences-package.js.map +1 -1
- package/src/lib/transport/gmail-client.d.ts +4 -11
- package/src/lib/transport/gmail-client.js +42 -17
- package/src/lib/transport/gmail-client.js.map +1 -1
- package/src/lib/transport/graph-client.d.ts +87 -0
- package/src/lib/transport/graph-client.js +359 -0
- package/src/lib/transport/graph-client.js.map +1 -0
- package/src/lib/transport/mail-client.d.ts +138 -0
- package/src/lib/transport/mail-client.js +34 -0
- package/src/lib/transport/mail-client.js.map +1 -0
- package/src/lib/transport/microsoft-errors.d.ts +32 -0
- package/src/lib/transport/microsoft-errors.js +94 -0
- package/src/lib/transport/microsoft-errors.js.map +1 -0
- package/src/lib/transport/microsoft-oauth.d.ts +140 -0
- package/src/lib/transport/microsoft-oauth.js +225 -0
- package/src/lib/transport/microsoft-oauth.js.map +1 -0
- package/src/lib/transport/mime-message.d.ts +30 -0
- package/src/lib/transport/mime-message.js +235 -0
- package/src/lib/transport/mime-message.js.map +1 -0
- package/src/lib/transport/send-message.d.ts +15 -8
- package/src/lib/transport/send-message.js +4 -3
- package/src/lib/transport/send-message.js.map +1 -1
|
@@ -71,7 +71,8 @@ import { outreachUnsubscribeMailbox } from "./unsubscribe-link.js";
|
|
|
71
71
|
* platform's stamp, and a bounce or a complaint lands on the send's own
|
|
72
72
|
* timeline entry, so a member reading the record sees what the runtime saw.
|
|
73
73
|
*
|
|
74
|
-
* Four reads find those, each narrowed by
|
|
74
|
+
* Four reads find those, each narrowed by the provider rather than read in
|
|
75
|
+
* full (`OutreachMailSearch`, AGL-3489):
|
|
75
76
|
* the threads of this mailbox's enrollments that have new mail; delivery
|
|
76
77
|
* reports outside those threads (a receiving server's own bounce), by
|
|
77
78
|
* searching for the mail systems that send them; messages to the
|
|
@@ -151,15 +152,13 @@ const ENROLLMENT_HANDLED_MAX = 100;
|
|
|
151
152
|
}
|
|
152
153
|
return report;
|
|
153
154
|
}
|
|
154
|
-
/** Every stub a search finds, a few pages deep. */ async function search(client,
|
|
155
|
+
/** Every stub a search finds, a few pages deep. */ async function search(client, criteria) {
|
|
155
156
|
const found = [];
|
|
156
157
|
let pageToken = null;
|
|
157
158
|
for(let page = 0; page < SEARCH_PAGES; page += 1){
|
|
158
|
-
const list = await client.
|
|
159
|
-
q,
|
|
159
|
+
const list = await client.searchMessages(criteria, {
|
|
160
160
|
maxResults: 100,
|
|
161
|
-
pageToken
|
|
162
|
-
includeSpamTrash: true
|
|
161
|
+
pageToken
|
|
163
162
|
});
|
|
164
163
|
found.push(...list.messages.filter((message)=>message.id));
|
|
165
164
|
pageToken = list.nextPageToken;
|
|
@@ -216,7 +215,6 @@ async function syncMailbox(deps, firestore, input) {
|
|
|
216
215
|
if (!known || ((_enrollment_lastSentAtMs = enrollment.lastSentAtMs) != null ? _enrollment_lastSentAtMs : 0) > ((_known_lastSentAtMs = known.lastSentAtMs) != null ? _known_lastSentAtMs : 0)) byAddress.set(enrollment.email, enrollment);
|
|
217
216
|
}
|
|
218
217
|
const sinceMs = Math.max(Number((_mailbox_sync = mailbox.sync) == null ? void 0 : _mailbox_sync.throughMs) || 0, nowMs - OUTREACH_SYNC_LOOKBACK_MS) - OUTREACH_SYNC_OVERLAP_MS;
|
|
219
|
-
const after = `after:${Math.floor(sinceMs / 1000)}`;
|
|
220
218
|
// Every message this run or an earlier one handled outside an enrollment's
|
|
221
219
|
// thread; one message is handled once however many searches find it.
|
|
222
220
|
const handled = new Set((_ref1 = (_mailbox_sync1 = mailbox.sync) == null ? void 0 : _mailbox_sync1.handledMessageIds) != null ? _ref1 : []);
|
|
@@ -228,7 +226,10 @@ async function syncMailbox(deps, firestore, input) {
|
|
|
228
226
|
};
|
|
229
227
|
const fresh = (stub)=>!handled.has(stub.id) && !byThread.has(stub.threadId);
|
|
230
228
|
// 1. The enrollments' own threads that have new mail.
|
|
231
|
-
const inbound = await search(context.client,
|
|
229
|
+
const inbound = await search(context.client, {
|
|
230
|
+
afterMs: sinceMs,
|
|
231
|
+
notFromSelf: true
|
|
232
|
+
});
|
|
232
233
|
for (const threadId of new Set(inbound.map((stub)=>stub.threadId))){
|
|
233
234
|
const enrollment = byThread.get(threadId);
|
|
234
235
|
if (!enrollment) continue;
|
|
@@ -237,7 +238,13 @@ async function syncMailbox(deps, firestore, input) {
|
|
|
237
238
|
await applyMessages(context, enrollment, thread.messages.map(outreachThreadMessageFromGmail));
|
|
238
239
|
}
|
|
239
240
|
// 2. Delivery reports outside those threads: a receiving server's bounce.
|
|
240
|
-
for (const stub of (await search(context.client,
|
|
241
|
+
for (const stub of (await search(context.client, {
|
|
242
|
+
afterMs: sinceMs,
|
|
243
|
+
from: [
|
|
244
|
+
'mailer-daemon',
|
|
245
|
+
'postmaster'
|
|
246
|
+
]
|
|
247
|
+
}))){
|
|
241
248
|
if (!fresh(stub)) continue;
|
|
242
249
|
const message = outreachThreadMessageFromGmail(await context.client.getFullMessage(stub.id));
|
|
243
250
|
const delivery = readOutreachDeliveryReport(message);
|
|
@@ -253,7 +260,10 @@ async function syncMailbox(deps, firestore, input) {
|
|
|
253
260
|
}
|
|
254
261
|
// 3. Messages to the unsubscribe address: whoever wrote asked to leave.
|
|
255
262
|
if (unsubscribe) {
|
|
256
|
-
for (const stub of (await search(context.client,
|
|
263
|
+
for (const stub of (await search(context.client, {
|
|
264
|
+
afterMs: sinceMs,
|
|
265
|
+
to: unsubscribe.address
|
|
266
|
+
}))){
|
|
257
267
|
var _ref3;
|
|
258
268
|
var _metadata_headers_find;
|
|
259
269
|
if (handled.has(stub.id)) continue;
|
|
@@ -289,7 +299,10 @@ async function syncMailbox(deps, firestore, input) {
|
|
|
289
299
|
];
|
|
290
300
|
for(let start = 0; start < addresses.length; start += FROM_CHUNK){
|
|
291
301
|
const chunk = addresses.slice(start, start + FROM_CHUNK);
|
|
292
|
-
for (const stub of (await search(context.client,
|
|
302
|
+
for (const stub of (await search(context.client, {
|
|
303
|
+
afterMs: sinceMs,
|
|
304
|
+
from: chunk
|
|
305
|
+
}))){
|
|
293
306
|
var _normalizeContactEmail;
|
|
294
307
|
if (!fresh(stub)) continue;
|
|
295
308
|
const message = outreachThreadMessageFromGmail(await context.client.getFullMessage(stub.id));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/runtime/sync-job.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { normalizeContactEmail } from '@aglyn/aglyn/app-utils/contacts'\nimport { isPublicMailboxDomain } from '@aglyn/aglyn/app-utils/crm'\nimport { emailAddressOf, emailExcerpt } from '@aglyn/aglyn/app-utils/email-text'\nimport { outreachMessageIdHeader } from '../engine/compose'\nimport { readOutreachDeliveryReport } from '../engine/delivery-status'\nimport { outreachEmailDomain } from '../engine/do-not-contact-domain'\nimport { outreachStoppedEntry } from '../engine/enrollment-activity'\nimport type { OutreachEnrollmentEvent } from '../engine/enrollment-state'\nimport { isOutreachGatewayBlock, outreachGatewayBlockDetail } from '../engine/gateway-block'\nimport { effectiveOutreachWindow, postponeOutreachForAutoReply } from '../engine/schedule'\nimport {\n decideOutreachThread,\n type OutreachMessageClassification,\n type OutreachThreadDecision,\n} from '../engine/thread-classification'\nimport { outreachHeader, outreachThreadMessageFromGmail, type OutreachThreadMessage } from '../engine/thread-message'\nimport { mailboxRef } from '../mailboxes/mailbox-credentials'\nimport {\n OUTREACH_COLLECTIONS,\n type OutreachEnrollment,\n type OutreachMailbox,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport { addOutreachDoNotContact, addOutreachDoNotContactDomain } from '../storage/do-not-contact-store'\nimport {\n outreachEnrollmentLink,\n outreachOrgCollection,\n readStoredOutreachEnrollment,\n readStoredOutreachMailbox,\n readStoredOutreachSequence,\n} from '../storage/outreach-records'\nimport type { GmailClient } from '../transport/gmail-client'\nimport { GmailTransportError, isReconnectRequired } from '../transport/gmail-errors'\nimport { creditOutreachReply } from './campaign-credit'\nimport { applyOutreachEvent, recordOutreachOptOut } from './enrollment-events'\nimport { creditOutreachGatewayDeliveries, recordOutreachGatewayBlock } from './gateway-ledger'\nimport { outreachMailboxSendingDomain } from '../storage/domain-intel-store'\nimport {\n applyOutreachMailboxHealth,\n emptyOutreachHealthDelta,\n type OutreachMailboxHealthDelta,\n} from './mailbox-health-store'\nimport { noteOutreachMailboxReconnectRequired } from './mailbox-notices'\nimport type { OutreachRuntimeDeps } from './runtime-deps'\nimport { stampRecordEmailReply } from '@aglyn/aglyn/plugin-manager/plugin-record-email-state'\nimport { fileOutreachEmail, fileOutreachNote, fileOutreachTask, markOutreachEmailDelivery } from './timeline'\nimport { outreachUnsubscribeMailbox } from './unsubscribe-link'\n\n/*==========================================\n * THE SYNC JOB (AGL-2981): every fifteen minutes, on the console.\n *\n * For each connected or paused mailbox, what came back since the last run —\n * read with `format=full`, turned into the engine's message shape and\n * classified by `decideOutreachThread`:\n *\n * - a REPLY stops the enrollment, files the message on the contact's\n * timeline (once per `Message-ID`, under the capture address's own id for\n * it) and gives the rep the task \"Reply from <name>\";\n * - an OUT-OF-OFFICE postpones the next step a working week from the reply;\n * - an OPT-OUT — a reply asking to be left alone, or a message to the\n * `+unsubscribe` address the `mailto:` names — puts the address on the\n * organization's do-not-contact list and off the site's `sales` topic,\n * and stops every open enrollment of it;\n * - a HARD BOUNCE stops the enrollment, files the address on the platform's\n * suppression list and the do-not-contact list — and, when its diagnostic\n * reads as the domain's mail gateway refusing the sender rather than one\n * address being unknown (`engine/gateway-block`, AGL-3244), files the\n * DOMAIN on the do-not-contact list too, and says so on the enrollment —\n * and counts the block on the gateway ledger (AGL-3326), under the\n * gateway the bounce's `Remote-MTA` names.\n *\n * And once the mail is read, every email step of a watched enrollment that\n * is a day old with no bounce against it is credited to the ledger as\n * delivered (`./gateway-ledger`): the silence that says a gateway let the\n * sender through.\n *\n * Every verdict a list takes is said on the record the person is as well\n * (AGL-3245): the lead and the contact carry `emailState` through the\n * platform's stamp, and a bounce or a complaint lands on the send's own\n * timeline entry, so a member reading the record sees what the runtime saw.\n *\n * Four reads find those, each narrowed by 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 /** Hard bounces that read as a gateway block, each filing its domain (AGL-3244). */\n gatewayBlocks: number\n /** Email steps a day old with no bounce, credited to the gateway ledger (AGL-3326). */\n deliveries: number\n postponed: number\n paused: number\n}\n\ninterface SyncContext {\n deps: OutreachRuntimeDeps\n firestore: Firestore\n orgId: string\n mailbox: OutreachMailbox\n client: 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 gatewayBlocks: 0,\n deliveries: 0,\n postponed: 0,\n paused: 0,\n }\n const all = await firestore.collectionGroup(OUTREACH_COLLECTIONS.mailboxes).get()\n const byOrg = new Map<string, OutreachMailbox[]>()\n for (const doc of all.docs) {\n const orgId = doc.ref.parent.parent?.id\n const mailbox = readStoredOutreachMailbox(doc.id, doc.data())\n if (!orgId || !mailbox || (mailbox.status !== 'connected' && mailbox.status !== 'paused')) continue\n byOrg.set(orgId, [...(byOrg.get(orgId) ?? []), mailbox])\n }\n for (const [orgId, mailboxes] of byOrg) {\n if (deps.now() >= context.deadlineMs) break\n const orgSnapshot = await firestore.collection('orgs').doc(orgId).get()\n const org = (orgSnapshot.exists ? orgSnapshot.data() : null) as Record<string, unknown> | null\n if (!org || (await deps.orgRefusal(orgId, org))) continue\n for (const mailbox of mailboxes) {\n if (deps.now() >= context.deadlineMs) break\n try {\n await syncMailbox(deps, firestore, { orgId, mailbox, report, nowMs: context.nowMs })\n report.mailboxes += 1\n } catch (error) {\n // One mailbox's failure is its own; the next mailbox is read. A grant\n // Google refused is the mailbox's to reconnect.\n if (isReconnectRequired(error)) {\n await noteOutreachMailboxReconnectRequired(deps, {\n orgId,\n mailboxId: mailbox.id,\n errorCode: error instanceof GmailTransportError ? error.code : 'invalid_grant',\n nowMs: context.nowMs,\n })\n }\n console.error('[outreach] syncing a mailbox failed', error)\n }\n }\n }\n return report\n}\n\n/** Every stub a search finds, a few pages deep. */\nasync function search(\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 noteOutreachMailboxReconnectRequired(deps, {\n orgId,\n mailboxId: mailbox.id,\n errorCode: opened.reason,\n nowMs,\n })\n }\n return\n }\n const unsubscribe = outreachUnsubscribeMailbox(mailbox.email)\n const context: SyncContext = {\n deps,\n firestore,\n orgId,\n mailbox,\n client: opened.client,\n selfAddresses: [\n mailbox.email,\n mailbox.sendAs,\n ...(mailbox.sendAsOptions ?? []).map((option) => option.email),\n ].filter(Boolean),\n unsubscribeAddress: unsubscribe?.address ?? null,\n sequences: new Map(),\n delta: emptyOutreachHealthDelta(),\n report,\n nowMs,\n }\n\n // The enrollments this mailbox is writing to, by thread and by address.\n const watched = await outreachOrgCollection(firestore, orgId, 'enrollments')\n .where('mailboxId', '==', mailbox.id)\n .where('lastSentAtMs', '>=', nowMs - OUTREACH_SYNC_WATCH_MS)\n .get()\n const byThread = new Map<string, OutreachEnrollment>()\n const byAddress = new Map<string, OutreachEnrollment>()\n const watchedEnrollments: OutreachEnrollment[] = []\n for (const doc of watched.docs) {\n const enrollment = readStoredOutreachEnrollment(doc.id, doc.data())\n if (!enrollment) continue\n watchedEnrollments.push(enrollment)\n for (const threadId of enrollment.gmailThreadIds) byThread.set(threadId, enrollment)\n const known = byAddress.get(enrollment.email)\n if (!known || (enrollment.lastSentAtMs ?? 0) > (known.lastSentAtMs ?? 0)) byAddress.set(enrollment.email, enrollment)\n }\n\n const sinceMs = Math.max(Number(mailbox.sync?.throughMs) || 0, nowMs - OUTREACH_SYNC_LOOKBACK_MS) - OUTREACH_SYNC_OVERLAP_MS\n 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 // 5. What went through: a day of silence after a send is a delivery\n // (AGL-3326), judged on the enrollments as this run left them.\n try {\n report.deliveries += await creditOutreachGatewayDeliveries(firestore, {\n orgId,\n enrollments: watchedEnrollments,\n sendingDomain: outreachMailboxSendingDomain(mailbox),\n resolveMx: deps.resolveMx,\n nowMs,\n })\n } catch (error) {\n console.error('[outreach] the gateway ledger could not credit deliveries', error)\n }\n\n const paused = await applyOutreachMailboxHealth(deps, { orgId, mailboxId: mailbox.id, delta: context.delta })\n if (paused) report.paused += 1\n await mailboxRef(firestore, orgId, mailbox.id).update({\n sync: {\n throughMs: nowMs,\n handledMessageIds: [...(mailbox.sync?.handledMessageIds ?? []), ...newlyHandled].slice(-MAILBOX_HANDLED_MAX),\n },\n })\n}\n\n/** The sequence an enrollment runs through, read once per run. */\nasync function sequenceOf(context: SyncContext, sequenceId: string): Promise<OutreachSequence | null> {\n if (!context.sequences.has(sequenceId)) {\n const snapshot = await outreachOrgCollection(context.firestore, context.orgId, 'sequences').doc(sequenceId).get()\n context.sequences.set(sequenceId, readStoredOutreachSequence(sequenceId, snapshot.exists ? snapshot.data() : undefined))\n }\n return context.sequences.get(sequenceId) ?? null\n}\n\n/** A message's `Message-ID` in header form, or `null` when it carries none. */\nconst messageIdOf = (message: OutreachThreadMessage) => outreachMessageIdHeader(outreachHeader(message, 'Message-ID'))\n\n/** A bounce's diagnostic with the recipient's address taken out of it. */\nexport function scrubbedOutreachDiagnostic(diagnostic: string | null, email: string): string | null {\n if (!diagnostic) return null\n const pattern = new RegExp(email.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'gi')\n return diagnostic.replace(pattern, 'the address')\n}\n\n/** Files a message the person wrote on their timeline. */\nasync function fileInbound(context: SyncContext, enrollment: OutreachEnrollment, message: OutreachThreadMessage) {\n const messageId = messageIdOf(message)\n if (!messageId) return\n await fileOutreachEmail(context.deps, {\n orgId: context.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n direction: 'inbound',\n subject: message.subject,\n from: emailAddressOf(message.from) ?? message.from,\n to: context.mailbox.sendAs || context.mailbox.email,\n messageId,\n inReplyTo: outreachMessageIdHeader(outreachHeader(message, 'In-Reply-To')),\n body: emailExcerpt(message.textBody ?? message.snippet ?? '', message.htmlBody ?? ''),\n atMs: message.internalDateMs,\n byUid: '',\n })\n}\n\n/**\n * What the new messages of one enrollment mean, done: the effects first,\n * each idempotent, then the event and the handled ids in one transaction.\n */\nasync function applyMessages(\n context: SyncContext,\n enrollment: OutreachEnrollment,\n messages: readonly OutreachThreadMessage[],\n): Promise<void> {\n const { deps, firestore, report, nowMs } = context\n const decision: OutreachThreadDecision = decideOutreachThread({\n messages,\n recipient: enrollment.email,\n handledMessageIds: enrollment.syncedMessageIds ?? [],\n selfAddresses: context.selfAddresses,\n unsubscribeAddress: context.unsubscribeAddress,\n threadSubject: enrollment.threadSubject,\n })\n const freshIds = decision.classifications.map((classification) => classification.messageId).filter(Boolean)\n if (!freshIds.length) return\n const decidedBy: OutreachMessageClassification | null = decision.decidedBy\n const decidingMessage = decidedBy ? messages.find((message) => message.id === decidedBy.messageId) ?? null : null\n const lastSentMessageId = enrollment.messageIds[enrollment.messageIds.length - 1] ?? null\n\n for (const classification of decision.classifications) {\n const aboutRecipient =\n !classification.bounce?.recipients.length || classification.bounce.recipients.includes(enrollment.email)\n if (classification.kind === 'hard_bounce' && aboutRecipient) {\n context.delta.bounces.push({ atMs: classification.atMs || nowMs, messageId: lastSentMessageId })\n }\n if (classification.complaint) {\n context.delta.complaintAtMs = Math.max(context.delta.complaintAtMs ?? 0, classification.atMs || nowMs)\n }\n }\n\n let event: OutreachEnrollmentEvent | null = null\n let postponedTo: number | null = null\n switch (decision.outcome) {\n case 'opted_out': {\n if (decidingMessage) await fileInbound(context, enrollment, decidingMessage)\n const complaint = decidedBy?.complaint === true\n await recordOutreachOptOut(deps, {\n orgId: context.orgId,\n email: enrollment.email,\n source: decidedBy?.evidence?.startsWith('To:') ? 'unsubscribe' : 'opt_out_reply',\n enrollment,\n hostIds: [enrollment.hostId],\n detail: decidedBy?.evidence ?? null,\n nowMs,\n complaint,\n })\n // A reply that called the email spam marks the send it answered\n // (AGL-3245), as the campaign webhook would mark a reported one.\n if (complaint) {\n await markOutreachEmailDelivery(deps, {\n orgId: context.orgId,\n messageId: lastSentMessageId,\n state: 'complained',\n atMs: decidedBy?.atMs || nowMs,\n detail: decidedBy?.evidence ?? null,\n })\n }\n report.optOuts += 1\n break\n }\n case 'replied': {\n if (decidingMessage) {\n await fileInbound(context, enrollment, decidingMessage)\n try {\n await fileOutreachTask(deps, {\n orgId: context.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n dedupeKey: `reply:${messageIdOf(decidingMessage) ?? decidingMessage.id}`,\n title: `Reply from ${enrollment.contactName || enrollment.email}`,\n notes: emailExcerpt(decidingMessage.textBody ?? decidingMessage.snippet ?? '', decidingMessage.htmlBody ?? '').slice(0, 500),\n kind: 'email',\n dueAtMs: nowMs,\n assigneeUid: context.mailbox.connectedByUid,\n })\n } catch (error) {\n console.error('[outreach] the reply task could not be filed', error)\n }\n }\n // A lead somebody wrote back to is being worked (AGL-3234): the record\n // system hears of the reply, and moves a lead nobody had worked to\n // Working (AGL-3080). Never throws.\n if (enrollment.target === 'lead' && enrollment.leadId && enrollment.email) {\n await stampRecordEmailReply({\n orgId: context.orgId,\n hostId: enrollment.hostId,\n emails: [enrollment.email],\n })\n }\n context.delta.replies += 1\n event = { type: 'reply', atMs: decidedBy?.atMs || nowMs, detail: decidedBy?.evidence ?? null }\n report.replies += 1\n // Once per enrollment (AGL-3254): a reply moves the status to\n // `replied`, and nothing is synced for it again.\n await creditOutreachReply(deps, { enrollment, atMs: event.atMs })\n break\n }\n case 'bounced': {\n const diagnostic = scrubbedOutreachDiagnostic(decidedBy?.bounce?.diagnostic ?? decidedBy?.bounce?.status ?? null, enrollment.email)\n await deps.suppressBouncedEmail({ email: enrollment.email, hostId: enrollment.hostId })\n await addOutreachDoNotContact(firestore, {\n orgId: context.orgId,\n email: enrollment.email,\n reason: 'hard_bounce',\n source: 'runtime',\n nowMs,\n enrollmentId: enrollment.id,\n sequenceId: enrollment.sequenceId,\n detail: diagnostic,\n })\n let detail = diagnostic\n // A gateway block is the domain's verdict on the sender (AGL-3244):\n // the domain is filed beside the address, and the enrollment says so.\n // Never a public mailbox provider's domain — Gmail refusing one\n // message on policy is not a reason to stop writing to Gmail.\n const domain = outreachEmailDomain(enrollment.email)\n const gateway = isOutreachGatewayBlock(decidedBy?.bounce ?? null)\n if (domain && !isPublicMailboxDomain(domain) && gateway) {\n await addOutreachDoNotContactDomain(firestore, {\n orgId: context.orgId,\n domain,\n reason: 'gateway_block',\n source: 'runtime',\n nowMs,\n enrollmentId: enrollment.id,\n sequenceId: enrollment.sequenceId,\n detail: diagnostic,\n })\n detail = outreachGatewayBlockDetail(domain, diagnostic)\n report.gatewayBlocks += 1\n }\n // The ledger learns the refusal (AGL-3326), whichever domain it was\n // at: a public provider refusing on policy is still that provider's\n // verdict on the sender, even though its domain is never listed.\n if (gateway) {\n await recordOutreachGatewayBlock(firestore, {\n orgId: context.orgId,\n email: enrollment.email,\n remoteMta: decidedBy?.bounce?.remoteMta ?? null,\n sendingDomain: outreachMailboxSendingDomain(context.mailbox),\n diagnostic,\n resolveMx: deps.resolveMx,\n nowMs,\n })\n }\n const bouncedAtMs = decidedBy?.atMs || nowMs\n // The record the person is says so too (AGL-3245): the verdict on the\n // lead and the contact, and the bounce on the send's own timeline\n // entry, so the record reads Sent, then Bounced.\n await deps.stampRecordEmailState({\n orgId: context.orgId,\n email: enrollment.email,\n state: {\n status: gateway ? 'blocked' : 'bounced',\n atMs: bouncedAtMs,\n source: 'sequence',\n detail: diagnostic,\n enrollmentId: enrollment.id,\n },\n })\n await markOutreachEmailDelivery(deps, {\n orgId: context.orgId,\n messageId: lastSentMessageId,\n state: 'bounced',\n atMs: bouncedAtMs,\n detail: diagnostic,\n })\n event = { type: 'bounce', atMs: bouncedAtMs, detail }\n report.bounces += 1\n break\n }\n case 'postpone': {\n const sequence = await sequenceOf(context, enrollment.sequenceId)\n postponedTo = postponeOutreachForAutoReply({\n nextDueAtMs: enrollment.nextDueAtMs,\n receivedAtMs: decidedBy?.atMs || nowMs,\n timeZone: context.mailbox.timezone,\n window: effectiveOutreachWindow(sequence?.settings.window, context.mailbox.window),\n random: deps.random,\n })\n break\n }\n default:\n break\n }\n\n const outcome = await applyOutreachEvent(firestore, {\n orgId: context.orgId,\n enrollmentId: enrollment.id,\n event,\n extra: (current) => {\n const extra: Record<string, unknown> = {\n syncedMessageIds: [...(current.syncedMessageIds ?? []), ...freshIds].slice(-ENROLLMENT_HANDLED_MAX),\n }\n const waiting = current.status === 'active' || current.status === 'paused'\n if (postponedTo !== null && waiting && current.nextDueAtMs !== null && postponedTo > current.nextDueAtMs) {\n extra['nextDueAtMs'] = postponedTo\n }\n return extra\n },\n nowMs,\n })\n if (postponedTo !== null && outcome.enrollment?.nextDueAtMs === postponedTo) report.postponed += 1\n // The person's record says the sequence ended (AGL-3274), once, under the\n // reply that ended it — after the transaction moved the status, so a run\n // that reads the same reply again finds nothing to file. An opt-out's\n // line is `recordOutreachOptOut`'s, beside the lists it writes.\n if (event?.type === 'reply' && outcome.changed) {\n const sequence = await sequenceOf(context, enrollment.sequenceId)\n const entry = outreachStoppedEntry({\n enrollmentId: enrollment.id,\n sequenceName: sequence?.name ?? '',\n reason: 'replied',\n })\n await fileOutreachNote(deps, {\n orgId: context.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n dedupeKey: entry.dedupeKey,\n body: entry.body,\n atMs: event.atMs,\n })\n }\n // The run's own view of the enrollment moves with it, so a second message\n // of it in this run is judged against what this one did.\n if (outcome.enrollment) Object.assign(enrollment, outcome.enrollment)\n}\n"],"names":["normalizeContactEmail","isPublicMailboxDomain","emailAddressOf","emailExcerpt","outreachMessageIdHeader","readOutreachDeliveryReport","outreachEmailDomain","outreachStoppedEntry","isOutreachGatewayBlock","outreachGatewayBlockDetail","effectiveOutreachWindow","postponeOutreachForAutoReply","decideOutreachThread","outreachHeader","outreachThreadMessageFromGmail","mailboxRef","OUTREACH_COLLECTIONS","addOutreachDoNotContact","addOutreachDoNotContactDomain","outreachEnrollmentLink","outreachOrgCollection","readStoredOutreachEnrollment","readStoredOutreachMailbox","readStoredOutreachSequence","GmailTransportError","isReconnectRequired","creditOutreachReply","applyOutreachEvent","recordOutreachOptOut","creditOutreachGatewayDeliveries","recordOutreachGatewayBlock","outreachMailboxSendingDomain","applyOutreachMailboxHealth","emptyOutreachHealthDelta","noteOutreachMailboxReconnectRequired","stampRecordEmailReply","fileOutreachEmail","fileOutreachNote","fileOutreachTask","markOutreachEmailDelivery","outreachUnsubscribeMailbox","OUTREACH_SYNC_JOB_ID","OUTREACH_SYNC_LOOKBACK_MS","OUTREACH_SYNC_OVERLAP_MS","OUTREACH_SYNC_WATCH_MS","SEARCH_PAGES","MAILBOX_HANDLED_MAX","ENROLLMENT_HANDLED_MAX","FROM_CHUNK","runOutreachSyncJob","deps","context","firestore","report","mailboxes","threads","replies","optOuts","bounces","gatewayBlocks","deliveries","postponed","paused","all","collectionGroup","get","byOrg","Map","doc","docs","orgId","ref","parent","id","mailbox","data","status","set","now","deadlineMs","orgSnapshot","collection","org","exists","orgRefusal","syncMailbox","nowMs","error","mailboxId","errorCode","code","console","search","client","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","watchedEnrollments","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","enrollments","sendingDomain","resolveMx","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","state","dedupeKey","title","contactName","notes","dueAtMs","assigneeUid","connectedByUid","target","leadId","emails","type","suppressBouncedEmail","enrollmentId","domain","gateway","remoteMta","bouncedAtMs","stampRecordEmailState","sequence","nextDueAtMs","receivedAtMs","timeZone","timezone","window","settings","random","extra","current","waiting","changed","entry","sequenceName","Object","assign"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/runtime/sync-job.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { normalizeContactEmail } from '@aglyn/aglyn/app-utils/contacts'\nimport { isPublicMailboxDomain } from '@aglyn/aglyn/app-utils/crm'\nimport { emailAddressOf, emailExcerpt } from '@aglyn/aglyn/app-utils/email-text'\nimport { outreachMessageIdHeader } from '../engine/compose'\nimport { readOutreachDeliveryReport } from '../engine/delivery-status'\nimport { outreachEmailDomain } from '../engine/do-not-contact-domain'\nimport { outreachStoppedEntry } from '../engine/enrollment-activity'\nimport type { OutreachEnrollmentEvent } from '../engine/enrollment-state'\nimport { isOutreachGatewayBlock, outreachGatewayBlockDetail } from '../engine/gateway-block'\nimport { effectiveOutreachWindow, postponeOutreachForAutoReply } from '../engine/schedule'\nimport {\n decideOutreachThread,\n type OutreachMessageClassification,\n type OutreachThreadDecision,\n} from '../engine/thread-classification'\nimport { outreachHeader, outreachThreadMessageFromGmail, type OutreachThreadMessage } from '../engine/thread-message'\nimport { mailboxRef } from '../mailboxes/mailbox-credentials'\nimport {\n OUTREACH_COLLECTIONS,\n type OutreachEnrollment,\n type OutreachMailbox,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport { addOutreachDoNotContact, addOutreachDoNotContactDomain } from '../storage/do-not-contact-store'\nimport {\n outreachEnrollmentLink,\n outreachOrgCollection,\n readStoredOutreachEnrollment,\n readStoredOutreachMailbox,\n readStoredOutreachSequence,\n} from '../storage/outreach-records'\nimport type { OutreachMailClient, OutreachMailSearch, OutreachMailStub } from '../transport/mail-client'\nimport { GmailTransportError, isReconnectRequired } from '../transport/gmail-errors'\nimport { creditOutreachReply } from './campaign-credit'\nimport { applyOutreachEvent, recordOutreachOptOut } from './enrollment-events'\nimport { creditOutreachGatewayDeliveries, recordOutreachGatewayBlock } from './gateway-ledger'\nimport { outreachMailboxSendingDomain } from '../storage/domain-intel-store'\nimport {\n applyOutreachMailboxHealth,\n emptyOutreachHealthDelta,\n type OutreachMailboxHealthDelta,\n} from './mailbox-health-store'\nimport { noteOutreachMailboxReconnectRequired } from './mailbox-notices'\nimport type { OutreachRuntimeDeps } from './runtime-deps'\nimport { stampRecordEmailReply } from '@aglyn/aglyn/plugin-manager/plugin-record-email-state'\nimport { fileOutreachEmail, fileOutreachNote, fileOutreachTask, markOutreachEmailDelivery } from './timeline'\nimport { outreachUnsubscribeMailbox } from './unsubscribe-link'\n\n/*==========================================\n * THE SYNC JOB (AGL-2981): every fifteen minutes, on the console.\n *\n * For each connected or paused mailbox, what came back since the last run —\n * read with `format=full`, turned into the engine's message shape and\n * classified by `decideOutreachThread`:\n *\n * - a REPLY stops the enrollment, files the message on the contact's\n * timeline (once per `Message-ID`, under the capture address's own id for\n * it) and gives the rep the task \"Reply from <name>\";\n * - an OUT-OF-OFFICE postpones the next step a working week from the reply;\n * - an OPT-OUT — a reply asking to be left alone, or a message to the\n * `+unsubscribe` address the `mailto:` names — puts the address on the\n * organization's do-not-contact list and off the site's `sales` topic,\n * and stops every open enrollment of it;\n * - a HARD BOUNCE stops the enrollment, files the address on the platform's\n * suppression list and the do-not-contact list — and, when its diagnostic\n * reads as the domain's mail gateway refusing the sender rather than one\n * address being unknown (`engine/gateway-block`, AGL-3244), files the\n * DOMAIN on the do-not-contact list too, and says so on the enrollment —\n * and counts the block on the gateway ledger (AGL-3326), under the\n * gateway the bounce's `Remote-MTA` names.\n *\n * And once the mail is read, every email step of a watched enrollment that\n * is a day old with no bounce against it is credited to the ledger as\n * delivered (`./gateway-ledger`): the silence that says a gateway let the\n * sender through.\n *\n * Every verdict a list takes is said on the record the person is as well\n * (AGL-3245): the lead and the contact carry `emailState` through the\n * platform's stamp, and a bounce or a complaint lands on the send's own\n * timeline entry, so a member reading the record sees what the runtime saw.\n *\n * Four reads find those, each narrowed by the provider rather than read in\n * full (`OutreachMailSearch`, AGL-3489):\n * the threads of this mailbox's enrollments that have new mail; delivery\n * reports outside those threads (a receiving server's own bounce), by\n * searching for the mail systems that send them; messages to the\n * unsubscribe address; and messages from the people this mailbox is\n * writing to, in a thread of their own.\n *\n * Every write it makes is idempotent — the do-not-contact list keeps its\n * first entry, a topic opt-out keeps its first moment, a timeline entry is\n * filed once per message — and the messages it handled are marked AFTER\n * their effects, on the enrollment and on the mailbox, so a run that dies\n * part-way repeats harmless work rather than skipping some. The mailbox's\n * health takes the run's bounces and complaints in one write at the end,\n * which is where it pauses itself.\n *==========================================*/\n\nexport { OUTREACH_SYNC_JOB_ID } from '../constants/runtime-jobs'\n\n/** The furthest back a run reads: a mailbox's first run, or one that fell behind. */\nexport const OUTREACH_SYNC_LOOKBACK_MS = 7 * 24 * 60 * 60_000\n\n/** How far a run reads back past the previous one, for mail Gmail indexed late. */\nexport const OUTREACH_SYNC_OVERLAP_MS = 2 * 60 * 60_000\n\n/** How long after its last send an enrollment's mail is still watched. */\nexport const OUTREACH_SYNC_WATCH_MS = 45 * 24 * 60 * 60_000\n\n/** The most pages of one search a run reads. */\nconst SEARCH_PAGES = 5\n/** Handled message ids a mailbox keeps, and an enrollment keeps. */\nconst MAILBOX_HANDLED_MAX = 500\nconst ENROLLMENT_HANDLED_MAX = 100\n/** Addresses in one `from:` search. */\nconst FROM_CHUNK = 15\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport interface OutreachSyncReport {\n [key: string]: number\n mailboxes: number\n threads: number\n replies: number\n optOuts: number\n bounces: number\n /** Hard bounces that read as a gateway block, each filing its domain (AGL-3244). */\n gatewayBlocks: number\n /** Email steps a day old with no bounce, credited to the gateway ledger (AGL-3326). */\n deliveries: number\n postponed: number\n paused: number\n}\n\ninterface SyncContext {\n deps: OutreachRuntimeDeps\n firestore: Firestore\n orgId: string\n mailbox: OutreachMailbox\n client: OutreachMailClient\n selfAddresses: string[]\n unsubscribeAddress: string | null\n sequences: Map<string, OutreachSequence | null>\n delta: OutreachMailboxHealthDelta\n report: OutreachSyncReport\n nowMs: number\n}\n\n/** Runs the sync job once — see the module note. */\nexport async function runOutreachSyncJob(\n deps: OutreachRuntimeDeps,\n context: { nowMs: number; deadlineMs: number },\n): Promise<OutreachSyncReport> {\n const firestore = deps.firestore()\n const report: OutreachSyncReport = {\n mailboxes: 0,\n threads: 0,\n replies: 0,\n optOuts: 0,\n bounces: 0,\n gatewayBlocks: 0,\n deliveries: 0,\n postponed: 0,\n paused: 0,\n }\n const all = await firestore.collectionGroup(OUTREACH_COLLECTIONS.mailboxes).get()\n const byOrg = new Map<string, OutreachMailbox[]>()\n for (const doc of all.docs) {\n const orgId = doc.ref.parent.parent?.id\n const mailbox = readStoredOutreachMailbox(doc.id, doc.data())\n if (!orgId || !mailbox || (mailbox.status !== 'connected' && mailbox.status !== 'paused')) continue\n byOrg.set(orgId, [...(byOrg.get(orgId) ?? []), mailbox])\n }\n for (const [orgId, mailboxes] of byOrg) {\n if (deps.now() >= context.deadlineMs) break\n const orgSnapshot = await firestore.collection('orgs').doc(orgId).get()\n const org = (orgSnapshot.exists ? orgSnapshot.data() : null) as Record<string, unknown> | null\n if (!org || (await deps.orgRefusal(orgId, org))) continue\n for (const mailbox of mailboxes) {\n if (deps.now() >= context.deadlineMs) break\n try {\n await syncMailbox(deps, firestore, { orgId, mailbox, report, nowMs: context.nowMs })\n report.mailboxes += 1\n } catch (error) {\n // One mailbox's failure is its own; the next mailbox is read. A grant\n // Google refused is the mailbox's to reconnect.\n if (isReconnectRequired(error)) {\n await noteOutreachMailboxReconnectRequired(deps, {\n orgId,\n mailboxId: mailbox.id,\n errorCode: error instanceof GmailTransportError ? error.code : 'invalid_grant',\n nowMs: context.nowMs,\n })\n }\n console.error('[outreach] syncing a mailbox failed', error)\n }\n }\n }\n return report\n}\n\n/** Every stub a search finds, a few pages deep. */\nasync function search(client: OutreachMailClient, criteria: OutreachMailSearch): Promise<OutreachMailStub[]> {\n const found: OutreachMailStub[] = []\n let pageToken: string | null = null\n for (let page = 0; page < SEARCH_PAGES; page += 1) {\n const list = await client.searchMessages(criteria, { maxResults: 100, pageToken })\n found.push(...list.messages.filter((message) => message.id))\n pageToken = list.nextPageToken\n if (!pageToken) break\n }\n return found\n}\n\nasync function syncMailbox(\n deps: OutreachRuntimeDeps,\n firestore: Firestore,\n input: { orgId: string; mailbox: OutreachMailbox; report: OutreachSyncReport; nowMs: number },\n): Promise<void> {\n const { orgId, mailbox, report, nowMs } = input\n const opened = await deps.openMailbox(mailbox.id)\n if (opened.ok === false) {\n if (opened.reason !== 'not-configured') {\n await noteOutreachMailboxReconnectRequired(deps, {\n orgId,\n mailboxId: mailbox.id,\n errorCode: opened.reason,\n nowMs,\n })\n }\n return\n }\n const unsubscribe = outreachUnsubscribeMailbox(mailbox.email)\n const context: SyncContext = {\n deps,\n firestore,\n orgId,\n mailbox,\n client: opened.client,\n selfAddresses: [\n mailbox.email,\n mailbox.sendAs,\n ...(mailbox.sendAsOptions ?? []).map((option) => option.email),\n ].filter(Boolean),\n unsubscribeAddress: unsubscribe?.address ?? null,\n sequences: new Map(),\n delta: emptyOutreachHealthDelta(),\n report,\n nowMs,\n }\n\n // The enrollments this mailbox is writing to, by thread and by address.\n const watched = await outreachOrgCollection(firestore, orgId, 'enrollments')\n .where('mailboxId', '==', mailbox.id)\n .where('lastSentAtMs', '>=', nowMs - OUTREACH_SYNC_WATCH_MS)\n .get()\n const byThread = new Map<string, OutreachEnrollment>()\n const byAddress = new Map<string, OutreachEnrollment>()\n const watchedEnrollments: OutreachEnrollment[] = []\n for (const doc of watched.docs) {\n const enrollment = readStoredOutreachEnrollment(doc.id, doc.data())\n if (!enrollment) continue\n watchedEnrollments.push(enrollment)\n for (const threadId of enrollment.gmailThreadIds) byThread.set(threadId, enrollment)\n const known = byAddress.get(enrollment.email)\n if (!known || (enrollment.lastSentAtMs ?? 0) > (known.lastSentAtMs ?? 0)) byAddress.set(enrollment.email, enrollment)\n }\n\n const sinceMs = Math.max(Number(mailbox.sync?.throughMs) || 0, nowMs - OUTREACH_SYNC_LOOKBACK_MS) - OUTREACH_SYNC_OVERLAP_MS\n // Every message this run or an earlier one handled outside an enrollment's\n // thread; one message is handled once however many searches find it.\n const handled = new Set(mailbox.sync?.handledMessageIds ?? [])\n const newlyHandled: string[] = []\n const markHandled = (id: string) => {\n if (handled.has(id)) return\n handled.add(id)\n newlyHandled.push(id)\n }\n const fresh = (stub: OutreachMailStub) => !handled.has(stub.id) && !byThread.has(stub.threadId)\n\n // 1. The enrollments' own threads that have new mail.\n const inbound = await search(context.client, { afterMs: sinceMs, notFromSelf: true })\n for (const threadId of new Set(inbound.map((stub) => stub.threadId))) {\n const enrollment = byThread.get(threadId)\n if (!enrollment) continue\n const thread = await context.client.getFullThread(threadId)\n report.threads += 1\n await applyMessages(context, enrollment, thread.messages.map(outreachThreadMessageFromGmail))\n }\n\n // 2. Delivery reports outside those threads: a receiving server's bounce.\n for (const stub of await search(context.client, { afterMs: sinceMs, from: ['mailer-daemon', 'postmaster'] })) {\n if (!fresh(stub)) continue\n const message = outreachThreadMessageFromGmail(await context.client.getFullMessage(stub.id))\n const delivery = readOutreachDeliveryReport(message)\n if (delivery?.kind === 'hard') {\n for (const address of delivery.failedAddresses) {\n const enrollment = byAddress.get(address)\n if (enrollment) await applyMessages(context, enrollment, [message])\n }\n }\n markHandled(stub.id)\n }\n\n // 3. Messages to the unsubscribe address: whoever wrote asked to leave.\n if (unsubscribe) {\n for (const stub of await search(context.client, { afterMs: sinceMs, to: unsubscribe.address })) {\n if (handled.has(stub.id)) continue\n const metadata = await context.client.getMessage(stub.id, { metadataHeaders: ['From'] })\n const from = emailAddressOf(metadata.headers.find((header) => header.name.toLowerCase() === 'from')?.value ?? '')\n const address = normalizeContactEmail(from)\n if (address && !context.selfAddresses.includes(address)) {\n const enrollment = byAddress.get(address) ?? null\n await recordOutreachOptOut(deps, {\n orgId,\n email: address,\n source: 'unsubscribe',\n enrollment,\n hostIds: enrollment ? [enrollment.hostId] : [],\n detail: 'Wrote to the unsubscribe address.',\n nowMs,\n })\n report.optOuts += 1\n }\n markHandled(stub.id)\n }\n }\n\n // 4. The people this mailbox writes to, answering in a thread of their own.\n const addresses = [...byAddress.keys()]\n for (let start = 0; start < addresses.length; start += FROM_CHUNK) {\n const chunk = addresses.slice(start, start + FROM_CHUNK)\n for (const stub of await search(context.client, { afterMs: sinceMs, from: chunk })) {\n if (!fresh(stub)) continue\n const message = outreachThreadMessageFromGmail(await context.client.getFullMessage(stub.id))\n const enrollment = byAddress.get(normalizeContactEmail(emailAddressOf(message.from)) ?? '')\n if (enrollment) await applyMessages(context, enrollment, [message])\n markHandled(stub.id)\n }\n }\n\n // 5. What went through: a day of silence after a send is a delivery\n // (AGL-3326), judged on the enrollments as this run left them.\n try {\n report.deliveries += await creditOutreachGatewayDeliveries(firestore, {\n orgId,\n enrollments: watchedEnrollments,\n sendingDomain: outreachMailboxSendingDomain(mailbox),\n resolveMx: deps.resolveMx,\n nowMs,\n })\n } catch (error) {\n console.error('[outreach] the gateway ledger could not credit deliveries', error)\n }\n\n const paused = await applyOutreachMailboxHealth(deps, { orgId, mailboxId: mailbox.id, delta: context.delta })\n if (paused) report.paused += 1\n await mailboxRef(firestore, orgId, mailbox.id).update({\n sync: {\n throughMs: nowMs,\n handledMessageIds: [...(mailbox.sync?.handledMessageIds ?? []), ...newlyHandled].slice(-MAILBOX_HANDLED_MAX),\n },\n })\n}\n\n/** The sequence an enrollment runs through, read once per run. */\nasync function sequenceOf(context: SyncContext, sequenceId: string): Promise<OutreachSequence | null> {\n if (!context.sequences.has(sequenceId)) {\n const snapshot = await outreachOrgCollection(context.firestore, context.orgId, 'sequences').doc(sequenceId).get()\n context.sequences.set(sequenceId, readStoredOutreachSequence(sequenceId, snapshot.exists ? snapshot.data() : undefined))\n }\n return context.sequences.get(sequenceId) ?? null\n}\n\n/** A message's `Message-ID` in header form, or `null` when it carries none. */\nconst messageIdOf = (message: OutreachThreadMessage) => outreachMessageIdHeader(outreachHeader(message, 'Message-ID'))\n\n/** A bounce's diagnostic with the recipient's address taken out of it. */\nexport function scrubbedOutreachDiagnostic(diagnostic: string | null, email: string): string | null {\n if (!diagnostic) return null\n const pattern = new RegExp(email.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'gi')\n return diagnostic.replace(pattern, 'the address')\n}\n\n/** Files a message the person wrote on their timeline. */\nasync function fileInbound(context: SyncContext, enrollment: OutreachEnrollment, message: OutreachThreadMessage) {\n const messageId = messageIdOf(message)\n if (!messageId) return\n await fileOutreachEmail(context.deps, {\n orgId: context.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n direction: 'inbound',\n subject: message.subject,\n from: emailAddressOf(message.from) ?? message.from,\n to: context.mailbox.sendAs || context.mailbox.email,\n messageId,\n inReplyTo: outreachMessageIdHeader(outreachHeader(message, 'In-Reply-To')),\n body: emailExcerpt(message.textBody ?? message.snippet ?? '', message.htmlBody ?? ''),\n atMs: message.internalDateMs,\n byUid: '',\n })\n}\n\n/**\n * What the new messages of one enrollment mean, done: the effects first,\n * each idempotent, then the event and the handled ids in one transaction.\n */\nasync function applyMessages(\n context: SyncContext,\n enrollment: OutreachEnrollment,\n messages: readonly OutreachThreadMessage[],\n): Promise<void> {\n const { deps, firestore, report, nowMs } = context\n const decision: OutreachThreadDecision = decideOutreachThread({\n messages,\n recipient: enrollment.email,\n handledMessageIds: enrollment.syncedMessageIds ?? [],\n selfAddresses: context.selfAddresses,\n unsubscribeAddress: context.unsubscribeAddress,\n threadSubject: enrollment.threadSubject,\n })\n const freshIds = decision.classifications.map((classification) => classification.messageId).filter(Boolean)\n if (!freshIds.length) return\n const decidedBy: OutreachMessageClassification | null = decision.decidedBy\n const decidingMessage = decidedBy ? messages.find((message) => message.id === decidedBy.messageId) ?? null : null\n const lastSentMessageId = enrollment.messageIds[enrollment.messageIds.length - 1] ?? null\n\n for (const classification of decision.classifications) {\n const aboutRecipient =\n !classification.bounce?.recipients.length || classification.bounce.recipients.includes(enrollment.email)\n if (classification.kind === 'hard_bounce' && aboutRecipient) {\n context.delta.bounces.push({ atMs: classification.atMs || nowMs, messageId: lastSentMessageId })\n }\n if (classification.complaint) {\n context.delta.complaintAtMs = Math.max(context.delta.complaintAtMs ?? 0, classification.atMs || nowMs)\n }\n }\n\n let event: OutreachEnrollmentEvent | null = null\n let postponedTo: number | null = null\n switch (decision.outcome) {\n case 'opted_out': {\n if (decidingMessage) await fileInbound(context, enrollment, decidingMessage)\n const complaint = decidedBy?.complaint === true\n await recordOutreachOptOut(deps, {\n orgId: context.orgId,\n email: enrollment.email,\n source: decidedBy?.evidence?.startsWith('To:') ? 'unsubscribe' : 'opt_out_reply',\n enrollment,\n hostIds: [enrollment.hostId],\n detail: decidedBy?.evidence ?? null,\n nowMs,\n complaint,\n })\n // A reply that called the email spam marks the send it answered\n // (AGL-3245), as the campaign webhook would mark a reported one.\n if (complaint) {\n await markOutreachEmailDelivery(deps, {\n orgId: context.orgId,\n messageId: lastSentMessageId,\n state: 'complained',\n atMs: decidedBy?.atMs || nowMs,\n detail: decidedBy?.evidence ?? null,\n })\n }\n report.optOuts += 1\n break\n }\n case 'replied': {\n if (decidingMessage) {\n await fileInbound(context, enrollment, decidingMessage)\n try {\n await fileOutreachTask(deps, {\n orgId: context.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n dedupeKey: `reply:${messageIdOf(decidingMessage) ?? decidingMessage.id}`,\n title: `Reply from ${enrollment.contactName || enrollment.email}`,\n notes: emailExcerpt(decidingMessage.textBody ?? decidingMessage.snippet ?? '', decidingMessage.htmlBody ?? '').slice(0, 500),\n kind: 'email',\n dueAtMs: nowMs,\n assigneeUid: context.mailbox.connectedByUid,\n })\n } catch (error) {\n console.error('[outreach] the reply task could not be filed', error)\n }\n }\n // A lead somebody wrote back to is being worked (AGL-3234): the record\n // system hears of the reply, and moves a lead nobody had worked to\n // Working (AGL-3080). Never throws.\n if (enrollment.target === 'lead' && enrollment.leadId && enrollment.email) {\n await stampRecordEmailReply({\n orgId: context.orgId,\n hostId: enrollment.hostId,\n emails: [enrollment.email],\n })\n }\n context.delta.replies += 1\n event = { type: 'reply', atMs: decidedBy?.atMs || nowMs, detail: decidedBy?.evidence ?? null }\n report.replies += 1\n // Once per enrollment (AGL-3254): a reply moves the status to\n // `replied`, and nothing is synced for it again.\n await creditOutreachReply(deps, { enrollment, atMs: event.atMs })\n break\n }\n case 'bounced': {\n const diagnostic = scrubbedOutreachDiagnostic(decidedBy?.bounce?.diagnostic ?? decidedBy?.bounce?.status ?? null, enrollment.email)\n await deps.suppressBouncedEmail({ email: enrollment.email, hostId: enrollment.hostId })\n await addOutreachDoNotContact(firestore, {\n orgId: context.orgId,\n email: enrollment.email,\n reason: 'hard_bounce',\n source: 'runtime',\n nowMs,\n enrollmentId: enrollment.id,\n sequenceId: enrollment.sequenceId,\n detail: diagnostic,\n })\n let detail = diagnostic\n // A gateway block is the domain's verdict on the sender (AGL-3244):\n // the domain is filed beside the address, and the enrollment says so.\n // Never a public mailbox provider's domain — Gmail refusing one\n // message on policy is not a reason to stop writing to Gmail.\n const domain = outreachEmailDomain(enrollment.email)\n const gateway = isOutreachGatewayBlock(decidedBy?.bounce ?? null)\n if (domain && !isPublicMailboxDomain(domain) && gateway) {\n await addOutreachDoNotContactDomain(firestore, {\n orgId: context.orgId,\n domain,\n reason: 'gateway_block',\n source: 'runtime',\n nowMs,\n enrollmentId: enrollment.id,\n sequenceId: enrollment.sequenceId,\n detail: diagnostic,\n })\n detail = outreachGatewayBlockDetail(domain, diagnostic)\n report.gatewayBlocks += 1\n }\n // The ledger learns the refusal (AGL-3326), whichever domain it was\n // at: a public provider refusing on policy is still that provider's\n // verdict on the sender, even though its domain is never listed.\n if (gateway) {\n await recordOutreachGatewayBlock(firestore, {\n orgId: context.orgId,\n email: enrollment.email,\n remoteMta: decidedBy?.bounce?.remoteMta ?? null,\n sendingDomain: outreachMailboxSendingDomain(context.mailbox),\n diagnostic,\n resolveMx: deps.resolveMx,\n nowMs,\n })\n }\n const bouncedAtMs = decidedBy?.atMs || nowMs\n // The record the person is says so too (AGL-3245): the verdict on the\n // lead and the contact, and the bounce on the send's own timeline\n // entry, so the record reads Sent, then Bounced.\n await deps.stampRecordEmailState({\n orgId: context.orgId,\n email: enrollment.email,\n state: {\n status: gateway ? 'blocked' : 'bounced',\n atMs: bouncedAtMs,\n source: 'sequence',\n detail: diagnostic,\n enrollmentId: enrollment.id,\n },\n })\n await markOutreachEmailDelivery(deps, {\n orgId: context.orgId,\n messageId: lastSentMessageId,\n state: 'bounced',\n atMs: bouncedAtMs,\n detail: diagnostic,\n })\n event = { type: 'bounce', atMs: bouncedAtMs, detail }\n report.bounces += 1\n break\n }\n case 'postpone': {\n const sequence = await sequenceOf(context, enrollment.sequenceId)\n postponedTo = postponeOutreachForAutoReply({\n nextDueAtMs: enrollment.nextDueAtMs,\n receivedAtMs: decidedBy?.atMs || nowMs,\n timeZone: context.mailbox.timezone,\n window: effectiveOutreachWindow(sequence?.settings.window, context.mailbox.window),\n random: deps.random,\n })\n break\n }\n default:\n break\n }\n\n const outcome = await applyOutreachEvent(firestore, {\n orgId: context.orgId,\n enrollmentId: enrollment.id,\n event,\n extra: (current) => {\n const extra: Record<string, unknown> = {\n syncedMessageIds: [...(current.syncedMessageIds ?? []), ...freshIds].slice(-ENROLLMENT_HANDLED_MAX),\n }\n const waiting = current.status === 'active' || current.status === 'paused'\n if (postponedTo !== null && waiting && current.nextDueAtMs !== null && postponedTo > current.nextDueAtMs) {\n extra['nextDueAtMs'] = postponedTo\n }\n return extra\n },\n nowMs,\n })\n if (postponedTo !== null && outcome.enrollment?.nextDueAtMs === postponedTo) report.postponed += 1\n // The person's record says the sequence ended (AGL-3274), once, under the\n // reply that ended it — after the transaction moved the status, so a run\n // that reads the same reply again finds nothing to file. An opt-out's\n // line is `recordOutreachOptOut`'s, beside the lists it writes.\n if (event?.type === 'reply' && outcome.changed) {\n const sequence = await sequenceOf(context, enrollment.sequenceId)\n const entry = outreachStoppedEntry({\n enrollmentId: enrollment.id,\n sequenceName: sequence?.name ?? '',\n reason: 'replied',\n })\n await fileOutreachNote(deps, {\n orgId: context.orgId,\n hostId: enrollment.hostId,\n link: outreachEnrollmentLink(enrollment),\n dedupeKey: entry.dedupeKey,\n body: entry.body,\n atMs: event.atMs,\n })\n }\n // The run's own view of the enrollment moves with it, so a second message\n // of it in this run is judged against what this one did.\n if (outcome.enrollment) Object.assign(enrollment, outcome.enrollment)\n}\n"],"names":["normalizeContactEmail","isPublicMailboxDomain","emailAddressOf","emailExcerpt","outreachMessageIdHeader","readOutreachDeliveryReport","outreachEmailDomain","outreachStoppedEntry","isOutreachGatewayBlock","outreachGatewayBlockDetail","effectiveOutreachWindow","postponeOutreachForAutoReply","decideOutreachThread","outreachHeader","outreachThreadMessageFromGmail","mailboxRef","OUTREACH_COLLECTIONS","addOutreachDoNotContact","addOutreachDoNotContactDomain","outreachEnrollmentLink","outreachOrgCollection","readStoredOutreachEnrollment","readStoredOutreachMailbox","readStoredOutreachSequence","GmailTransportError","isReconnectRequired","creditOutreachReply","applyOutreachEvent","recordOutreachOptOut","creditOutreachGatewayDeliveries","recordOutreachGatewayBlock","outreachMailboxSendingDomain","applyOutreachMailboxHealth","emptyOutreachHealthDelta","noteOutreachMailboxReconnectRequired","stampRecordEmailReply","fileOutreachEmail","fileOutreachNote","fileOutreachTask","markOutreachEmailDelivery","outreachUnsubscribeMailbox","OUTREACH_SYNC_JOB_ID","OUTREACH_SYNC_LOOKBACK_MS","OUTREACH_SYNC_OVERLAP_MS","OUTREACH_SYNC_WATCH_MS","SEARCH_PAGES","MAILBOX_HANDLED_MAX","ENROLLMENT_HANDLED_MAX","FROM_CHUNK","runOutreachSyncJob","deps","context","firestore","report","mailboxes","threads","replies","optOuts","bounces","gatewayBlocks","deliveries","postponed","paused","all","collectionGroup","get","byOrg","Map","doc","docs","orgId","ref","parent","id","mailbox","data","status","set","now","deadlineMs","orgSnapshot","collection","org","exists","orgRefusal","syncMailbox","nowMs","error","mailboxId","errorCode","code","console","search","client","criteria","found","pageToken","page","list","searchMessages","maxResults","push","messages","filter","message","nextPageToken","input","opened","openMailbox","ok","reason","unsubscribe","email","selfAddresses","sendAs","sendAsOptions","map","option","Boolean","unsubscribeAddress","address","sequences","delta","watched","where","byThread","byAddress","watchedEnrollments","enrollment","known","threadId","gmailThreadIds","lastSentAtMs","sinceMs","Math","max","Number","sync","throughMs","handled","Set","handledMessageIds","newlyHandled","markHandled","has","add","fresh","stub","inbound","afterMs","notFromSelf","thread","getFullThread","applyMessages","from","getFullMessage","delivery","kind","failedAddresses","to","metadata","getMessage","metadataHeaders","headers","find","header","name","toLowerCase","value","includes","source","hostIds","hostId","detail","addresses","keys","start","length","chunk","slice","enrollments","sendingDomain","resolveMx","update","sequenceOf","sequenceId","snapshot","undefined","messageIdOf","scrubbedOutreachDiagnostic","diagnostic","pattern","RegExp","replace","fileInbound","messageId","link","direction","subject","inReplyTo","body","textBody","snippet","htmlBody","atMs","internalDateMs","byUid","outcome","decision","recipient","syncedMessageIds","threadSubject","freshIds","classifications","classification","decidedBy","decidingMessage","lastSentMessageId","messageIds","aboutRecipient","bounce","recipients","complaint","complaintAtMs","event","postponedTo","evidence","startsWith","state","dedupeKey","title","contactName","notes","dueAtMs","assigneeUid","connectedByUid","target","leadId","emails","type","suppressBouncedEmail","enrollmentId","domain","gateway","remoteMta","bouncedAtMs","stampRecordEmailState","sequence","nextDueAtMs","receivedAtMs","timeZone","timezone","window","settings","random","extra","current","waiting","changed","entry","sequenceName","Object","assign"],"mappings":"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"}
|
|
@@ -31,6 +31,10 @@ import type { PluginEgressHostDeclaration, PluginEgressUseDeclaration, PluginSub
|
|
|
31
31
|
* preview; the consent screen is Internal to the aglyn.com Workspace until
|
|
32
32
|
* then.
|
|
33
33
|
*
|
|
34
|
+
* A rep's Microsoft 365 mailbox (AGL-3489) stands on the same footing:
|
|
35
|
+
* Microsoft Graph is the customer's own mail provider, and Microsoft's
|
|
36
|
+
* identity platform is where the rep grants the access.
|
|
37
|
+
*
|
|
34
38
|
* Google's token endpoint is already declared by the inventory, for the
|
|
35
39
|
* platform's own service-account exchange, so this plugin adds its use of it
|
|
36
40
|
* rather than a second declaration.
|
|
@@ -44,5 +48,12 @@ export declare const OUTREACH_GMAIL_HOST: PluginEgressHostDeclaration;
|
|
|
44
48
|
export declare const OUTREACH_GOOGLE_CONSENT_HOST: PluginEgressHostDeclaration;
|
|
45
49
|
/** Google's token and revocation endpoint, declared by the inventory. */
|
|
46
50
|
export declare const OUTREACH_GOOGLE_TOKEN_USE: PluginEgressUseDeclaration;
|
|
47
|
-
/**
|
|
51
|
+
/** Microsoft Graph: the rep's own Microsoft 365 mailbox (AGL-3489). */
|
|
52
|
+
export declare const OUTREACH_GRAPH_HOST: PluginEgressHostDeclaration;
|
|
53
|
+
/**
|
|
54
|
+
* Microsoft's identity platform: the consent address the rep's browser opens,
|
|
55
|
+
* and the token endpoint the console calls for that same grant.
|
|
56
|
+
*/
|
|
57
|
+
export declare const OUTREACH_MICROSOFT_LOGIN_HOST: PluginEgressHostDeclaration;
|
|
58
|
+
/** The plugin's `subprocessors` entry: no recipient of its own, four hosts and one use. */
|
|
48
59
|
export declare function outreachSubprocessors(): PluginSubprocessorsAnswer;
|
package/src/lib/subprocessors.js
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ import { GMAIL_API_BASE } from "./transport/gmail-client.js";
|
|
17
17
|
import { GOOGLE_OAUTH_ENDPOINTS } from "./transport/google-oauth.js";
|
|
18
|
+
import { GRAPH_API_BASE } from "./transport/graph-client.js";
|
|
19
|
+
import { MICROSOFT_LOGIN_ORIGIN } from "./transport/microsoft-oauth.js";
|
|
18
20
|
/**
|
|
19
21
|
* The hosts this plugin's code names (AGL-2978), declared here rather than in
|
|
20
22
|
* the console's subprocessor inventory. Named under `subprocessors` in
|
|
@@ -31,6 +33,10 @@ import { GOOGLE_OAUTH_ENDPOINTS } from "./transport/google-oauth.js";
|
|
|
31
33
|
* preview; the consent screen is Internal to the aglyn.com Workspace until
|
|
32
34
|
* then.
|
|
33
35
|
*
|
|
36
|
+
* A rep's Microsoft 365 mailbox (AGL-3489) stands on the same footing:
|
|
37
|
+
* Microsoft Graph is the customer's own mail provider, and Microsoft's
|
|
38
|
+
* identity platform is where the rep grants the access.
|
|
39
|
+
*
|
|
34
40
|
* Google's token endpoint is already declared by the inventory, for the
|
|
35
41
|
* platform's own service-account exchange, so this plugin adds its use of it
|
|
36
42
|
* rather than a second declaration.
|
|
@@ -54,12 +60,29 @@ import { GOOGLE_OAUTH_ENDPOINTS } from "./transport/google-oauth.js";
|
|
|
54
60
|
reason: "Since AGL-2978 also the Sequences OAuth token endpoint for a rep's own Google mailbox grant — the code exchange at connect, the access-token refresh before each Gmail call, and the revocation on disconnect, org erasure or account erasure — which is the rep's own account at the rep's own provider, the same footing as `gmail.googleapis.com`.",
|
|
55
61
|
dataReceived: "For Sequences: the deployment's OAuth client credentials and, for the rep's own grant, the authorization code, PKCE verifier, refresh token and access token Google itself issued — credentials, never message content. ⚑ Legal to confirm the Annex III cell needs no change for the Sequences use."
|
|
56
62
|
};
|
|
57
|
-
/**
|
|
63
|
+
/** Microsoft Graph: the rep's own Microsoft 365 mailbox (AGL-3489). */ export const OUTREACH_GRAPH_HOST = {
|
|
64
|
+
host: new URL(GRAPH_API_BASE).host,
|
|
65
|
+
disposition: 'not-a-subprocessor',
|
|
66
|
+
reason: "Customer-chosen destination. Microsoft Graph for the rep's own Microsoft 365 (Exchange Online) mailbox, which the rep connects in Sequences → Mailboxes (`libs/plugins/outreach/src/lib/transport/graph-client.ts`): the account's id, address and display name at connect, a plain-text test the rep sends, the sequence messages the sending runtime sends — each created as a draft in the rep's mailbox and sent from there — and the reads of that same mailbox that find what came back: replies, out-of-office answers, bounces and unsubscribe requests. The provider is the one the customer runs its mail on; nothing is sent to a mailbox the rep did not connect.",
|
|
67
|
+
dataReceived: "The rep's own OAuth access token, and the mail the rep sends from their own mailbox — each recipient's address, the subject and the body. The runtime's reads carry a received-time window, conversation ids and the Message-IDs of mail it sent. What the runtime reads back is the rep's own mail: the ids, senders, recipients and received times of the window's messages, and whole messages — headers and bodies, delivery reports included — of the rep's sequence conversations and of the replies, bounces and unsubscribe requests among them. No other customer record, and nothing about a site visitor."
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Microsoft's identity platform: the consent address the rep's browser opens,
|
|
71
|
+
* and the token endpoint the console calls for that same grant.
|
|
72
|
+
*/ export const OUTREACH_MICROSOFT_LOGIN_HOST = {
|
|
73
|
+
host: new URL(MICROSOFT_LOGIN_ORIGIN).host,
|
|
74
|
+
disposition: 'not-a-subprocessor',
|
|
75
|
+
reason: "Customer-chosen destination. Microsoft's identity platform for the rep's own Microsoft 365 account (`libs/plugins/outreach/src/lib/transport/microsoft-oauth.ts`): the consent address `buildMicrosoftAuthorizationUrl` hands the rep's browser, and the token endpoint the console calls for the code exchange at connect and the access-token refresh before each Graph call — the rep's own account at the rep's own provider, the same footing as `graph.microsoft.com`.",
|
|
76
|
+
dataReceived: "From the rep's browser, the app registration's client id, the requested permissions, a signed state, a PKCE challenge and a login hint. From our servers, the deployment's client credentials and, for the rep's own grant, the authorization code, PKCE verifier and the refresh token Microsoft itself issued — credentials, never message content."
|
|
77
|
+
};
|
|
78
|
+
/** The plugin's `subprocessors` entry: no recipient of its own, four hosts and one use. */ export function outreachSubprocessors() {
|
|
58
79
|
return {
|
|
59
80
|
subprocessors: [],
|
|
60
81
|
hosts: [
|
|
61
82
|
OUTREACH_GMAIL_HOST,
|
|
62
|
-
OUTREACH_GOOGLE_CONSENT_HOST
|
|
83
|
+
OUTREACH_GOOGLE_CONSENT_HOST,
|
|
84
|
+
OUTREACH_GRAPH_HOST,
|
|
85
|
+
OUTREACH_MICROSOFT_LOGIN_HOST
|
|
63
86
|
],
|
|
64
87
|
uses: [
|
|
65
88
|
OUTREACH_GOOGLE_TOKEN_USE
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/outreach/src/lib/subprocessors.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 type {\n PluginEgressHostDeclaration,\n PluginEgressUseDeclaration,\n PluginSubprocessorsAnswer,\n} from '@aglyn/aglyn/plugin-manager/plugin-subprocessors'\nimport { GMAIL_API_BASE } from './transport/gmail-client'\nimport { GOOGLE_OAUTH_ENDPOINTS } from './transport/google-oauth'\n\n/**\n * The hosts this plugin's code names (AGL-2978), declared here rather than in\n * the console's subprocessor inventory. Named under `subprocessors` in\n * `plugins.config.json`; the manifest generator calls\n * {@link outreachSubprocessors} and the inventory folds the answer in.\n *\n * A sequence sends a rep's one-to-one mail from the rep's OWN Google mailbox,\n * through the Gmail API, under an OAuth grant the rep gives from their own\n * Google account. `not-a-subprocessor` on the SECOND admissible reason: the\n * mailbox provider is the customer's, engaged by the customer for their own\n * mail, and a sequence acts in it at the rep's direction — Aglyn selects no\n * mail vendor for this, any more than it selects the video host an author\n * embeds. ⚑ A classification for legal to confirm when Sequences leaves staff\n * preview; the consent screen is Internal to the aglyn.com Workspace until\n * then.\n *\n * Google's token endpoint is already declared by the inventory, for the\n * platform's own service-account exchange, so this plugin adds its use of it\n * rather than a second declaration.\n *\n * Each host is read off the constant the transport calls, so a moved\n * endpoint moves its declaration with it.\n */\n\n/** The Gmail REST API: the rep's own mailbox. */\nexport const OUTREACH_GMAIL_HOST: PluginEgressHostDeclaration = {\n host: new URL(GMAIL_API_BASE).host,\n disposition: 'not-a-subprocessor',\n reason:\n \"Customer-chosen destination. The Gmail REST API of the rep's own Google Workspace mailbox, which the rep connects in Sequences → Mailboxes (`libs/plugins/outreach/src/lib/transport/gmail-client.ts`): the account's profile and verified send-as addresses at connect, a plain-text test the rep sends to themselves, and — for the sending runtime (AGL-2981) — the sequence messages it sends, and the reads and searches of that same mailbox that find what came back: replies, out-of-office answers, bounces and unsubscribe requests. The provider is the one the customer runs its mail on; nothing is sent to a mailbox the rep did not connect.\",\n dataReceived:\n \"The rep's own OAuth access token, and the mail the rep sends from their own mailbox — each recipient's address, the subject and the plain-text body. The runtime's searches carry the addresses of the people the rep is writing to, the rep's `+unsubscribe` address and the Message-IDs of mail it sent. What the runtime reads back is the rep's own mail: the ids of newly received messages, and whole messages — headers and plain-text or HTML bodies, delivery reports included — of the rep's sequence threads and of the replies, bounces and unsubscribe requests its searches find. No other customer record, and nothing about a site visitor.\",\n}\n\n/** Google's consent address, which only the rep's browser opens. */\nexport const OUTREACH_GOOGLE_CONSENT_HOST: PluginEgressHostDeclaration = {\n host: new URL(GOOGLE_OAUTH_ENDPOINTS.authorize).host,\n disposition: 'no-request',\n reason:\n \"Google's OAuth consent address, built by `buildGoogleAuthorizationUrl` in `libs/plugins/outreach/src/lib/transport/google-oauth.ts` and handed to the rep's own browser, which opens it to grant Sequences access to the rep's own mailbox. No server of ours requests it; the browser's visit is between the rep and their Google account.\",\n dataReceived:\n \"Nothing from our servers. The rep's browser carries the OAuth client id, the requested scopes, a signed state, a PKCE challenge and a login hint — the rep's own sign-in address.\",\n}\n\n/** Google's token and revocation endpoint, declared by the inventory. */\nexport const OUTREACH_GOOGLE_TOKEN_USE: PluginEgressUseDeclaration = {\n host: new URL(GOOGLE_OAUTH_ENDPOINTS.token).host,\n reason:\n \"Since AGL-2978 also the Sequences OAuth token endpoint for a rep's own Google mailbox grant — the code exchange at connect, the access-token refresh before each Gmail call, and the revocation on disconnect, org erasure or account erasure — which is the rep's own account at the rep's own provider, the same footing as `gmail.googleapis.com`.\",\n dataReceived:\n \"For Sequences: the deployment's OAuth client credentials and, for the rep's own grant, the authorization code, PKCE verifier, refresh token and access token Google itself issued — credentials, never message content. ⚑ Legal to confirm the Annex III cell needs no change for the Sequences use.\",\n}\n\n/** The plugin's `subprocessors` entry: no recipient of its own, two hosts and one use. */\nexport function outreachSubprocessors(): PluginSubprocessorsAnswer {\n return {\n subprocessors: [],\n hosts: [OUTREACH_GMAIL_HOST, OUTREACH_GOOGLE_CONSENT_HOST],\n uses: [OUTREACH_GOOGLE_TOKEN_USE],\n }\n}\n"],"names":["GMAIL_API_BASE","GOOGLE_OAUTH_ENDPOINTS","OUTREACH_GMAIL_HOST","host","URL","disposition","reason","dataReceived","OUTREACH_GOOGLE_CONSENT_HOST","authorize","OUTREACH_GOOGLE_TOKEN_USE","token","outreachSubprocessors","subprocessors","hosts","uses"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAOD,SAASA,cAAc,QAAQ,8BAA0B;AACzD,SAASC,sBAAsB,QAAQ,8BAA0B;AAEjE;;;;;;;;;;;;;;;;;;;;;;CAsBC,GAED,+CAA+C,GAC/C,OAAO,MAAMC,sBAAmD;IAC9DC,MAAM,IAAIC,IAAIJ,gBAAgBG,IAAI;IAClCE,aAAa;IACbC,QACE;IACFC,cACE;AACJ,EAAC;AAED,kEAAkE,GAClE,OAAO,MAAMC,+BAA4D;IACvEL,MAAM,IAAIC,IAAIH,uBAAuBQ,SAAS,EAAEN,IAAI;IACpDE,aAAa;IACbC,QACE;IACFC,cACE;AACJ,EAAC;AAED,uEAAuE,GACvE,OAAO,MAAMG,4BAAwD;IACnEP,MAAM,IAAIC,IAAIH,uBAAuBU,KAAK,EAAER,IAAI;IAChDG,QACE;IACFC,cACE;AACJ,EAAC;AAED,wFAAwF,GACxF,OAAO,SAASK;IACd,OAAO;QACLC,eAAe,EAAE;QACjBC,OAAO;YAACZ;YAAqBM;SAA6B;QAC1DO,MAAM;YAACL;SAA0B;IACnC;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/outreach/src/lib/subprocessors.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 type {\n PluginEgressHostDeclaration,\n PluginEgressUseDeclaration,\n PluginSubprocessorsAnswer,\n} from '@aglyn/aglyn/plugin-manager/plugin-subprocessors'\nimport { GMAIL_API_BASE } from './transport/gmail-client'\nimport { GOOGLE_OAUTH_ENDPOINTS } from './transport/google-oauth'\nimport { GRAPH_API_BASE } from './transport/graph-client'\nimport { MICROSOFT_LOGIN_ORIGIN } from './transport/microsoft-oauth'\n\n/**\n * The hosts this plugin's code names (AGL-2978), declared here rather than in\n * the console's subprocessor inventory. Named under `subprocessors` in\n * `plugins.config.json`; the manifest generator calls\n * {@link outreachSubprocessors} and the inventory folds the answer in.\n *\n * A sequence sends a rep's one-to-one mail from the rep's OWN Google mailbox,\n * through the Gmail API, under an OAuth grant the rep gives from their own\n * Google account. `not-a-subprocessor` on the SECOND admissible reason: the\n * mailbox provider is the customer's, engaged by the customer for their own\n * mail, and a sequence acts in it at the rep's direction — Aglyn selects no\n * mail vendor for this, any more than it selects the video host an author\n * embeds. ⚑ A classification for legal to confirm when Sequences leaves staff\n * preview; the consent screen is Internal to the aglyn.com Workspace until\n * then.\n *\n * A rep's Microsoft 365 mailbox (AGL-3489) stands on the same footing:\n * Microsoft Graph is the customer's own mail provider, and Microsoft's\n * identity platform is where the rep grants the access.\n *\n * Google's token endpoint is already declared by the inventory, for the\n * platform's own service-account exchange, so this plugin adds its use of it\n * rather than a second declaration.\n *\n * Each host is read off the constant the transport calls, so a moved\n * endpoint moves its declaration with it.\n */\n\n/** The Gmail REST API: the rep's own mailbox. */\nexport const OUTREACH_GMAIL_HOST: PluginEgressHostDeclaration = {\n host: new URL(GMAIL_API_BASE).host,\n disposition: 'not-a-subprocessor',\n reason:\n \"Customer-chosen destination. The Gmail REST API of the rep's own Google Workspace mailbox, which the rep connects in Sequences → Mailboxes (`libs/plugins/outreach/src/lib/transport/gmail-client.ts`): the account's profile and verified send-as addresses at connect, a plain-text test the rep sends to themselves, and — for the sending runtime (AGL-2981) — the sequence messages it sends, and the reads and searches of that same mailbox that find what came back: replies, out-of-office answers, bounces and unsubscribe requests. The provider is the one the customer runs its mail on; nothing is sent to a mailbox the rep did not connect.\",\n dataReceived:\n \"The rep's own OAuth access token, and the mail the rep sends from their own mailbox — each recipient's address, the subject and the plain-text body. The runtime's searches carry the addresses of the people the rep is writing to, the rep's `+unsubscribe` address and the Message-IDs of mail it sent. What the runtime reads back is the rep's own mail: the ids of newly received messages, and whole messages — headers and plain-text or HTML bodies, delivery reports included — of the rep's sequence threads and of the replies, bounces and unsubscribe requests its searches find. No other customer record, and nothing about a site visitor.\",\n}\n\n/** Google's consent address, which only the rep's browser opens. */\nexport const OUTREACH_GOOGLE_CONSENT_HOST: PluginEgressHostDeclaration = {\n host: new URL(GOOGLE_OAUTH_ENDPOINTS.authorize).host,\n disposition: 'no-request',\n reason:\n \"Google's OAuth consent address, built by `buildGoogleAuthorizationUrl` in `libs/plugins/outreach/src/lib/transport/google-oauth.ts` and handed to the rep's own browser, which opens it to grant Sequences access to the rep's own mailbox. No server of ours requests it; the browser's visit is between the rep and their Google account.\",\n dataReceived:\n \"Nothing from our servers. The rep's browser carries the OAuth client id, the requested scopes, a signed state, a PKCE challenge and a login hint — the rep's own sign-in address.\",\n}\n\n/** Google's token and revocation endpoint, declared by the inventory. */\nexport const OUTREACH_GOOGLE_TOKEN_USE: PluginEgressUseDeclaration = {\n host: new URL(GOOGLE_OAUTH_ENDPOINTS.token).host,\n reason:\n \"Since AGL-2978 also the Sequences OAuth token endpoint for a rep's own Google mailbox grant — the code exchange at connect, the access-token refresh before each Gmail call, and the revocation on disconnect, org erasure or account erasure — which is the rep's own account at the rep's own provider, the same footing as `gmail.googleapis.com`.\",\n dataReceived:\n \"For Sequences: the deployment's OAuth client credentials and, for the rep's own grant, the authorization code, PKCE verifier, refresh token and access token Google itself issued — credentials, never message content. ⚑ Legal to confirm the Annex III cell needs no change for the Sequences use.\",\n}\n\n/** Microsoft Graph: the rep's own Microsoft 365 mailbox (AGL-3489). */\nexport const OUTREACH_GRAPH_HOST: PluginEgressHostDeclaration = {\n host: new URL(GRAPH_API_BASE).host,\n disposition: 'not-a-subprocessor',\n reason:\n \"Customer-chosen destination. Microsoft Graph for the rep's own Microsoft 365 (Exchange Online) mailbox, which the rep connects in Sequences → Mailboxes (`libs/plugins/outreach/src/lib/transport/graph-client.ts`): the account's id, address and display name at connect, a plain-text test the rep sends, the sequence messages the sending runtime sends — each created as a draft in the rep's mailbox and sent from there — and the reads of that same mailbox that find what came back: replies, out-of-office answers, bounces and unsubscribe requests. The provider is the one the customer runs its mail on; nothing is sent to a mailbox the rep did not connect.\",\n dataReceived:\n \"The rep's own OAuth access token, and the mail the rep sends from their own mailbox — each recipient's address, the subject and the body. The runtime's reads carry a received-time window, conversation ids and the Message-IDs of mail it sent. What the runtime reads back is the rep's own mail: the ids, senders, recipients and received times of the window's messages, and whole messages — headers and bodies, delivery reports included — of the rep's sequence conversations and of the replies, bounces and unsubscribe requests among them. No other customer record, and nothing about a site visitor.\",\n}\n\n/**\n * Microsoft's identity platform: the consent address the rep's browser opens,\n * and the token endpoint the console calls for that same grant.\n */\nexport const OUTREACH_MICROSOFT_LOGIN_HOST: PluginEgressHostDeclaration = {\n host: new URL(MICROSOFT_LOGIN_ORIGIN).host,\n disposition: 'not-a-subprocessor',\n reason:\n \"Customer-chosen destination. Microsoft's identity platform for the rep's own Microsoft 365 account (`libs/plugins/outreach/src/lib/transport/microsoft-oauth.ts`): the consent address `buildMicrosoftAuthorizationUrl` hands the rep's browser, and the token endpoint the console calls for the code exchange at connect and the access-token refresh before each Graph call — the rep's own account at the rep's own provider, the same footing as `graph.microsoft.com`.\",\n dataReceived:\n \"From the rep's browser, the app registration's client id, the requested permissions, a signed state, a PKCE challenge and a login hint. From our servers, the deployment's client credentials and, for the rep's own grant, the authorization code, PKCE verifier and the refresh token Microsoft itself issued — credentials, never message content.\",\n}\n\n/** The plugin's `subprocessors` entry: no recipient of its own, four hosts and one use. */\nexport function outreachSubprocessors(): PluginSubprocessorsAnswer {\n return {\n subprocessors: [],\n hosts: [OUTREACH_GMAIL_HOST, OUTREACH_GOOGLE_CONSENT_HOST, OUTREACH_GRAPH_HOST, OUTREACH_MICROSOFT_LOGIN_HOST],\n uses: [OUTREACH_GOOGLE_TOKEN_USE],\n }\n}\n"],"names":["GMAIL_API_BASE","GOOGLE_OAUTH_ENDPOINTS","GRAPH_API_BASE","MICROSOFT_LOGIN_ORIGIN","OUTREACH_GMAIL_HOST","host","URL","disposition","reason","dataReceived","OUTREACH_GOOGLE_CONSENT_HOST","authorize","OUTREACH_GOOGLE_TOKEN_USE","token","OUTREACH_GRAPH_HOST","OUTREACH_MICROSOFT_LOGIN_HOST","outreachSubprocessors","subprocessors","hosts","uses"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAOD,SAASA,cAAc,QAAQ,8BAA0B;AACzD,SAASC,sBAAsB,QAAQ,8BAA0B;AACjE,SAASC,cAAc,QAAQ,8BAA0B;AACzD,SAASC,sBAAsB,QAAQ,iCAA6B;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BC,GAED,+CAA+C,GAC/C,OAAO,MAAMC,sBAAmD;IAC9DC,MAAM,IAAIC,IAAIN,gBAAgBK,IAAI;IAClCE,aAAa;IACbC,QACE;IACFC,cACE;AACJ,EAAC;AAED,kEAAkE,GAClE,OAAO,MAAMC,+BAA4D;IACvEL,MAAM,IAAIC,IAAIL,uBAAuBU,SAAS,EAAEN,IAAI;IACpDE,aAAa;IACbC,QACE;IACFC,cACE;AACJ,EAAC;AAED,uEAAuE,GACvE,OAAO,MAAMG,4BAAwD;IACnEP,MAAM,IAAIC,IAAIL,uBAAuBY,KAAK,EAAER,IAAI;IAChDG,QACE;IACFC,cACE;AACJ,EAAC;AAED,qEAAqE,GACrE,OAAO,MAAMK,sBAAmD;IAC9DT,MAAM,IAAIC,IAAIJ,gBAAgBG,IAAI;IAClCE,aAAa;IACbC,QACE;IACFC,cACE;AACJ,EAAC;AAED;;;CAGC,GACD,OAAO,MAAMM,gCAA6D;IACxEV,MAAM,IAAIC,IAAIH,wBAAwBE,IAAI;IAC1CE,aAAa;IACbC,QACE;IACFC,cACE;AACJ,EAAC;AAED,yFAAyF,GACzF,OAAO,SAASO;IACd,OAAO;QACLC,eAAe,EAAE;QACjBC,OAAO;YAACd;YAAqBM;YAA8BI;YAAqBC;SAA8B;QAC9GI,MAAM;YAACP;SAA0B;IACnC;AACF"}
|
|
@@ -42,7 +42,7 @@ import { readOutreachSequenceDraft } from "../model/sequence-draft.js";
|
|
|
42
42
|
return readOutreachSequenceDraft(source);
|
|
43
43
|
}
|
|
44
44
|
/** What a sequence names. */ export function outreachSequenceDependencies(content) {
|
|
45
|
-
var _content_campaignIds, _content_steps;
|
|
45
|
+
var _content_mailboxIds, _content_campaignIds, _content_steps;
|
|
46
46
|
const deps = [];
|
|
47
47
|
const seen = new Set();
|
|
48
48
|
const add = (kind, id)=>{
|
|
@@ -55,6 +55,7 @@ import { readOutreachSequenceDraft } from "../model/sequence-draft.js";
|
|
|
55
55
|
};
|
|
56
56
|
add(TRANSFER_SITE_KIND, content.hostId);
|
|
57
57
|
add(OUTREACH_MAILBOX_REFERENCE_KIND, content.mailboxId);
|
|
58
|
+
for (const mailboxId of (_content_mailboxIds = content.mailboxIds) != null ? _content_mailboxIds : [])add(OUTREACH_MAILBOX_REFERENCE_KIND, mailboxId);
|
|
58
59
|
for (const campaignId of (_content_campaignIds = content.campaignIds) != null ? _content_campaignIds : [])add(OUTREACH_CAMPAIGN_REFERENCE_KIND, campaignId);
|
|
59
60
|
for (const step of (_content_steps = content.steps) != null ? _content_steps : []){
|
|
60
61
|
if (step.kind === 'email') add(OUTREACH_TEMPLATE_REFERENCE_KIND, step.templateId);
|
|
@@ -63,9 +64,13 @@ import { readOutreachSequenceDraft } from "../model/sequence-draft.js";
|
|
|
63
64
|
}
|
|
64
65
|
/** The sequence with every reference moved through `idMap`; a dropped one is emptied. */ export function remapOutreachSequenceIds(content, idMap) {
|
|
65
66
|
var _remapPackageReference, _remapPackageReference1, _content_campaignIds, _content_steps;
|
|
67
|
+
var _content_mailboxIds;
|
|
66
68
|
return _extends({}, content, {
|
|
67
69
|
hostId: (_remapPackageReference = remapPackageReference(idMap, TRANSFER_SITE_KIND, content.hostId)) != null ? _remapPackageReference : '',
|
|
68
|
-
mailboxId: (_remapPackageReference1 = remapPackageReference(idMap, OUTREACH_MAILBOX_REFERENCE_KIND, content.mailboxId)) != null ? _remapPackageReference1 : ''
|
|
70
|
+
mailboxId: (_remapPackageReference1 = remapPackageReference(idMap, OUTREACH_MAILBOX_REFERENCE_KIND, content.mailboxId)) != null ? _remapPackageReference1 : ''
|
|
71
|
+
}, ((_content_mailboxIds = content.mailboxIds) == null ? void 0 : _content_mailboxIds.length) ? {
|
|
72
|
+
mailboxIds: content.mailboxIds.map((id)=>remapPackageReference(idMap, OUTREACH_MAILBOX_REFERENCE_KIND, id)).filter((id)=>Boolean(id))
|
|
73
|
+
} : {}, {
|
|
69
74
|
campaignIds: ((_content_campaignIds = content.campaignIds) != null ? _content_campaignIds : []).map((id)=>remapPackageReference(idMap, OUTREACH_CAMPAIGN_REFERENCE_KIND, id)).filter((id)=>Boolean(id)),
|
|
70
75
|
steps: ((_content_steps = content.steps) != null ? _content_steps : []).map((step)=>step.kind === 'email' ? _extends({}, step, {
|
|
71
76
|
templateId: remapPackageReference(idMap, OUTREACH_TEMPLATE_REFERENCE_KIND, step.templateId)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/transfer/sequences-package.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * SEQUENCES AS PACKAGE ITEMS (AGL-3535) — what a sequence carries in a\n * workspace package, what it names, and how a reference moves.\n *\n * An item is the sequence as the editor holds it (`OutreachSequenceDraft`):\n * its name, site, mailbox, steps, settings and campaigns. Never its status,\n * its counters or anyone enrolled in it — a package carries how a sequence\n * reads, not who it reached — so a sequence exported and imported back is\n * `identical`, and an import never brings sending with it.\n *\n * What it names: its site (`site`), its mailbox (`outreach.mailboxes`, a\n * kind no package carries: mailboxes are connected, never imported), its\n * campaigns (`marketing.campaigns`) and each email step's CRM template\n * (`crm.emailTemplates`). A reference the person drops leaves the site and\n * mailbox empty, the campaign off the list and the step on its own subject\n * and body.\n *\n * Pure: the server half (`sequences-package.server.ts`) reads and writes.\n *=========================================*/\n\nimport type { PackageDependency } from '@aglyn/aglyn/data-transfer/package'\nimport { remapPackageReference, TRANSFER_SITE_KIND } from '@aglyn/aglyn/data-transfer/package-plan'\nimport { readOutreachSequenceDraft, type OutreachSequenceDraft } from '../model/sequence-draft'\n\n/** The resource key, and so the kind every sequence item carries. */\nexport const OUTREACH_SEQUENCES_TRANSFER_KEY = 'outreach.sequences'\n\n/** The kind a sequence's mailbox is named under; the workspace's connected mailboxes answer it. */\nexport const OUTREACH_MAILBOX_REFERENCE_KIND = 'outreach.mailboxes'\n\n/** The marketing plugin's campaigns resource, which a sequence's campaigns name. */\nexport const OUTREACH_CAMPAIGN_REFERENCE_KIND = 'marketing.campaigns'\n\n/** The CRM's email templates resource, which an email step's template names. */\nexport const OUTREACH_TEMPLATE_REFERENCE_KIND = 'crm.email-templates'\n\n/** A sequence as a package carries it. */\nexport type OutreachSequencePackageContent = OutreachSequenceDraft\n\n/** The package content of a stored sequence (or anything shaped like one): the editor's draft, nothing else. */\nexport function outreachSequencePackageContent(source: unknown): OutreachSequencePackageContent {\n return readOutreachSequenceDraft(source)\n}\n\n/** What a sequence names. */\nexport function outreachSequenceDependencies(content: OutreachSequencePackageContent): PackageDependency[] {\n const deps: PackageDependency[] = []\n const seen = new Set<string>()\n const add = (kind: string, id: string | null | undefined) => {\n if (!id || seen.has(`${kind}/${id}`)) return\n seen.add(`${kind}/${id}`)\n deps.push({ kind, id })\n }\n add(TRANSFER_SITE_KIND, content.hostId)\n add(OUTREACH_MAILBOX_REFERENCE_KIND, content.mailboxId)\n for (const campaignId of content.campaignIds ?? []) add(OUTREACH_CAMPAIGN_REFERENCE_KIND, campaignId)\n for (const step of content.steps ?? []) {\n if (step.kind === 'email') add(OUTREACH_TEMPLATE_REFERENCE_KIND, step.templateId)\n }\n return deps\n}\n\n/** The sequence with every reference moved through `idMap`; a dropped one is emptied. */\nexport function remapOutreachSequenceIds(\n content: OutreachSequencePackageContent,\n idMap: ReadonlyMap<string, string>,\n): OutreachSequencePackageContent {\n return {\n ...content,\n hostId: remapPackageReference(idMap, TRANSFER_SITE_KIND, content.hostId) ?? '',\n mailboxId: remapPackageReference(idMap, OUTREACH_MAILBOX_REFERENCE_KIND, content.mailboxId) ?? '',\n campaignIds: (content.campaignIds ?? [])\n .map((id) => remapPackageReference(idMap, OUTREACH_CAMPAIGN_REFERENCE_KIND, id))\n .filter((id): id is string => Boolean(id)),\n steps: (content.steps ?? []).map((step) =>\n step.kind === 'email'\n ? { ...step, templateId: remapPackageReference(idMap, OUTREACH_TEMPLATE_REFERENCE_KIND, step.templateId) }\n : step,\n ),\n }\n}\n\n/** The rules an import of sequences keeps, shown with their reasons. */\nexport const OUTREACH_SEQUENCE_PACKAGE_RULES = [\n {\n id: 'draft',\n label: 'An imported sequence arrives as a draft',\n reason:\n 'A sequence emails people only once someone activates it, and an import never does. A sequence you replace keeps the status it already had.',\n },\n {\n id: 'noEnrollments',\n label: 'Nobody enrolled moves with it',\n reason: 'A package carries how a sequence reads: its steps and settings. Who was enrolled and what they were sent stay where they happened.',\n },\n {\n id: 'mailbox',\n label: 'It sends from a mailbox connected here',\n reason: 'A mailbox is connected by the person who sends from it, so an imported sequence is pointed at one of this workspace’s mailboxes.',\n },\n] as const\n"],"names":["remapPackageReference","TRANSFER_SITE_KIND","readOutreachSequenceDraft","OUTREACH_SEQUENCES_TRANSFER_KEY","OUTREACH_MAILBOX_REFERENCE_KIND","OUTREACH_CAMPAIGN_REFERENCE_KIND","OUTREACH_TEMPLATE_REFERENCE_KIND","outreachSequencePackageContent","source","outreachSequenceDependencies","content","deps","seen","Set","add","kind","id","has","push","hostId","mailboxId","campaignId","campaignIds","step","steps","templateId","remapOutreachSequenceIds","idMap","map","filter","Boolean","OUTREACH_SEQUENCE_PACKAGE_RULES","label","reason"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;2CAkB2C;AAG3C,SAASA,qBAAqB,EAAEC,kBAAkB,QAAQ,0CAAyC;AACnG,SAASC,yBAAyB,QAAoC,6BAAyB;AAE/F,mEAAmE,GACnE,OAAO,MAAMC,kCAAkC,qBAAoB;AAEnE,iGAAiG,GACjG,OAAO,MAAMC,kCAAkC,qBAAoB;AAEnE,kFAAkF,GAClF,OAAO,MAAMC,mCAAmC,sBAAqB;AAErE,8EAA8E,GAC9E,OAAO,MAAMC,mCAAmC,sBAAqB;AAKrE,8GAA8G,GAC9G,OAAO,SAASC,+BAA+BC,MAAe;IAC5D,OAAON,0BAA0BM;AACnC;AAEA,2BAA2B,GAC3B,OAAO,SAASC,6BAA6BC,OAAuC;
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/transfer/sequences-package.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * SEQUENCES AS PACKAGE ITEMS (AGL-3535) — what a sequence carries in a\n * workspace package, what it names, and how a reference moves.\n *\n * An item is the sequence as the editor holds it (`OutreachSequenceDraft`):\n * its name, site, mailbox, steps, settings and campaigns. Never its status,\n * its counters or anyone enrolled in it — a package carries how a sequence\n * reads, not who it reached — so a sequence exported and imported back is\n * `identical`, and an import never brings sending with it.\n *\n * What it names: its site (`site`), its mailbox (`outreach.mailboxes`, a\n * kind no package carries: mailboxes are connected, never imported), its\n * campaigns (`marketing.campaigns`) and each email step's CRM template\n * (`crm.emailTemplates`). A reference the person drops leaves the site and\n * mailbox empty, the campaign off the list and the step on its own subject\n * and body.\n *\n * Pure: the server half (`sequences-package.server.ts`) reads and writes.\n *=========================================*/\n\nimport type { PackageDependency } from '@aglyn/aglyn/data-transfer/package'\nimport { remapPackageReference, TRANSFER_SITE_KIND } from '@aglyn/aglyn/data-transfer/package-plan'\nimport { readOutreachSequenceDraft, type OutreachSequenceDraft } from '../model/sequence-draft'\n\n/** The resource key, and so the kind every sequence item carries. */\nexport const OUTREACH_SEQUENCES_TRANSFER_KEY = 'outreach.sequences'\n\n/** The kind a sequence's mailbox is named under; the workspace's connected mailboxes answer it. */\nexport const OUTREACH_MAILBOX_REFERENCE_KIND = 'outreach.mailboxes'\n\n/** The marketing plugin's campaigns resource, which a sequence's campaigns name. */\nexport const OUTREACH_CAMPAIGN_REFERENCE_KIND = 'marketing.campaigns'\n\n/** The CRM's email templates resource, which an email step's template names. */\nexport const OUTREACH_TEMPLATE_REFERENCE_KIND = 'crm.email-templates'\n\n/** A sequence as a package carries it. */\nexport type OutreachSequencePackageContent = OutreachSequenceDraft\n\n/** The package content of a stored sequence (or anything shaped like one): the editor's draft, nothing else. */\nexport function outreachSequencePackageContent(source: unknown): OutreachSequencePackageContent {\n return readOutreachSequenceDraft(source)\n}\n\n/** What a sequence names. */\nexport function outreachSequenceDependencies(content: OutreachSequencePackageContent): PackageDependency[] {\n const deps: PackageDependency[] = []\n const seen = new Set<string>()\n const add = (kind: string, id: string | null | undefined) => {\n if (!id || seen.has(`${kind}/${id}`)) return\n seen.add(`${kind}/${id}`)\n deps.push({ kind, id })\n }\n add(TRANSFER_SITE_KIND, content.hostId)\n add(OUTREACH_MAILBOX_REFERENCE_KIND, content.mailboxId)\n for (const mailboxId of content.mailboxIds ?? []) add(OUTREACH_MAILBOX_REFERENCE_KIND, mailboxId)\n for (const campaignId of content.campaignIds ?? []) add(OUTREACH_CAMPAIGN_REFERENCE_KIND, campaignId)\n for (const step of content.steps ?? []) {\n if (step.kind === 'email') add(OUTREACH_TEMPLATE_REFERENCE_KIND, step.templateId)\n }\n return deps\n}\n\n/** The sequence with every reference moved through `idMap`; a dropped one is emptied. */\nexport function remapOutreachSequenceIds(\n content: OutreachSequencePackageContent,\n idMap: ReadonlyMap<string, string>,\n): OutreachSequencePackageContent {\n return {\n ...content,\n hostId: remapPackageReference(idMap, TRANSFER_SITE_KIND, content.hostId) ?? '',\n mailboxId: remapPackageReference(idMap, OUTREACH_MAILBOX_REFERENCE_KIND, content.mailboxId) ?? '',\n ...(content.mailboxIds?.length\n ? {\n mailboxIds: content.mailboxIds\n .map((id) => remapPackageReference(idMap, OUTREACH_MAILBOX_REFERENCE_KIND, id))\n .filter((id): id is string => Boolean(id)),\n }\n : {}),\n campaignIds: (content.campaignIds ?? [])\n .map((id) => remapPackageReference(idMap, OUTREACH_CAMPAIGN_REFERENCE_KIND, id))\n .filter((id): id is string => Boolean(id)),\n steps: (content.steps ?? []).map((step) =>\n step.kind === 'email'\n ? { ...step, templateId: remapPackageReference(idMap, OUTREACH_TEMPLATE_REFERENCE_KIND, step.templateId) }\n : step,\n ),\n }\n}\n\n/** The rules an import of sequences keeps, shown with their reasons. */\nexport const OUTREACH_SEQUENCE_PACKAGE_RULES = [\n {\n id: 'draft',\n label: 'An imported sequence arrives as a draft',\n reason:\n 'A sequence emails people only once someone activates it, and an import never does. A sequence you replace keeps the status it already had.',\n },\n {\n id: 'noEnrollments',\n label: 'Nobody enrolled moves with it',\n reason: 'A package carries how a sequence reads: its steps and settings. Who was enrolled and what they were sent stay where they happened.',\n },\n {\n id: 'mailbox',\n label: 'It sends from a mailbox connected here',\n reason: 'A mailbox is connected by the person who sends from it, so an imported sequence is pointed at one of this workspace’s mailboxes.',\n },\n] as const\n"],"names":["remapPackageReference","TRANSFER_SITE_KIND","readOutreachSequenceDraft","OUTREACH_SEQUENCES_TRANSFER_KEY","OUTREACH_MAILBOX_REFERENCE_KIND","OUTREACH_CAMPAIGN_REFERENCE_KIND","OUTREACH_TEMPLATE_REFERENCE_KIND","outreachSequencePackageContent","source","outreachSequenceDependencies","content","deps","seen","Set","add","kind","id","has","push","hostId","mailboxId","mailboxIds","campaignId","campaignIds","step","steps","templateId","remapOutreachSequenceIds","idMap","length","map","filter","Boolean","OUTREACH_SEQUENCE_PACKAGE_RULES","label","reason"],"mappings":"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"}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { GmailApiMessage } from '../engine/thread-message';
|
|
18
18
|
import { type TransportDeps } from './http';
|
|
19
|
+
import { type OutreachMailClient, type OutreachMailMetadata } from './mail-client';
|
|
19
20
|
/**
|
|
20
21
|
* THE GMAIL REST TRANSPORT (AGL-2978): one connected mailbox, as the calls
|
|
21
22
|
* Outreach makes against it.
|
|
@@ -66,14 +67,7 @@ export interface GmailMessageHeader {
|
|
|
66
67
|
value: string;
|
|
67
68
|
}
|
|
68
69
|
/** A message in `format=metadata`: ids, labels, snippet and the headers asked for. */
|
|
69
|
-
export
|
|
70
|
-
id: string;
|
|
71
|
-
threadId: string;
|
|
72
|
-
labelIds: string[];
|
|
73
|
-
snippet: string;
|
|
74
|
-
internalDateMs: number | null;
|
|
75
|
-
headers: GmailMessageHeader[];
|
|
76
|
-
}
|
|
70
|
+
export type GmailMessageMetadata = OutreachMailMetadata;
|
|
77
71
|
export interface GmailThread {
|
|
78
72
|
id: string;
|
|
79
73
|
historyId: string | null;
|
|
@@ -102,9 +96,8 @@ export interface GmailSentMessage {
|
|
|
102
96
|
threadId: string;
|
|
103
97
|
labelIds: string[];
|
|
104
98
|
}
|
|
105
|
-
export interface GmailClient {
|
|
106
|
-
|
|
107
|
-
getAccessToken(): Promise<string>;
|
|
99
|
+
export interface GmailClient extends OutreachMailClient {
|
|
100
|
+
readonly provider: 'google';
|
|
108
101
|
getProfile(): Promise<GmailProfile>;
|
|
109
102
|
listSendAs(): Promise<GmailSendAs[]>;
|
|
110
103
|
/**
|