@aglyn/plugins-outreach 1.0.0-beta.273 → 1.0.0-beta.275
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 +7 -2
- package/package.json +14 -14
- package/src/lib/components/enrollment-actions.d.ts +11 -4
- package/src/lib/components/enrollment-actions.js +16 -0
- package/src/lib/components/enrollment-actions.js.map +1 -1
- package/src/lib/components/enrollment-detail.js +40 -13
- package/src/lib/components/enrollment-detail.js.map +1 -1
- package/src/lib/components/mailbox-card.js +13 -2
- package/src/lib/components/mailbox-card.js.map +1 -1
- package/src/lib/components/record-reply-button.d.ts +52 -0
- package/src/lib/components/record-reply-button.js +92 -0
- package/src/lib/components/record-reply-button.js.map +1 -0
- package/src/lib/components/reply-dialog.d.ts +66 -0
- package/src/lib/components/reply-dialog.js +353 -0
- package/src/lib/components/reply-dialog.js.map +1 -0
- package/src/lib/components/use-outreach-api.d.ts +5 -1
- package/src/lib/components/use-outreach-api.js +10 -0
- package/src/lib/components/use-outreach-api.js.map +1 -1
- package/src/lib/constants/api-routes.d.ts +8 -0
- package/src/lib/constants/api-routes.js +6 -0
- package/src/lib/constants/api-routes.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/manual-reply.d.ts +108 -0
- package/src/lib/engine/manual-reply.js +301 -0
- package/src/lib/engine/manual-reply.js.map +1 -0
- package/src/lib/model/enrollment-timeline.d.ts +3 -1
- package/src/lib/model/enrollment-timeline.js +83 -7
- package/src/lib/model/enrollment-timeline.js.map +1 -1
- package/src/lib/model/outreach-api.d.ts +66 -2
- package/src/lib/model/outreach-api.js.map +1 -1
- package/src/lib/model/outreach.types.d.ts +85 -3
- package/src/lib/model/outreach.types.js +6 -0
- package/src/lib/model/outreach.types.js.map +1 -1
- package/src/lib/model/stored-records.d.ts +7 -1
- package/src/lib/model/stored-records.js +37 -1
- package/src/lib/model/stored-records.js.map +1 -1
- package/src/lib/plugin.d.ts +4 -0
- package/src/lib/plugin.js +17 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/routes/register-routes.d.ts +6 -0
- package/src/lib/routes/register-routes.js +18 -0
- package/src/lib/routes/register-routes.js.map +1 -1
- package/src/lib/routes/reply-routes.d.ts +76 -0
- package/src/lib/routes/reply-routes.js +563 -0
- package/src/lib/routes/reply-routes.js.map +1 -0
- package/src/lib/runtime/sync-job.js +14 -0
- package/src/lib/runtime/sync-job.js.map +1 -1
- package/src/lib/transport/send-message.d.ts +11 -0
- package/src/lib/transport/send-message.js +22 -0
- package/src/lib/transport/send-message.js.map +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OutreachComplianceIssue } from '../model/compliance-settings';
|
|
2
|
-
import type { OutreachCurateDraftRequest, OutreachCurateDraftResponse, OutreachCurateSaveRequest, OutreachCurateSaveResponse, OutreachDoNotContactDomainRequest, OutreachDoNotContactDomainResponse, OutreachDoNotContactDomainsResponse, OutreachEnrollmentAction, OutreachEnrollmentActionResponse, OutreachEnrollmentGatewayResponse, OutreachEnrollPersonRequest, OutreachEnrollPreviewResponse, OutreachEnrollResponse, OutreachEnrollSource, OutreachLinkDomainRequest, OutreachLinkDomainResponse, OutreachLinkDomainsResponse, OutreachPreviewRequest, OutreachPreviewResponse, OutreachRouteRefusalReason, OutreachSequenceAction, OutreachSequenceSaveResponse, OutreachSequenceStatusResponse, OutreachSettingsResponse, OutreachSettingsSaveRequest, OutreachSettingsSaveResponse, OutreachStepTestRequest, OutreachStepTestResponse } from '../model/outreach-api';
|
|
2
|
+
import type { OutreachCurateDraftRequest, OutreachCurateDraftResponse, OutreachCurateSaveRequest, OutreachCurateSaveResponse, OutreachDoNotContactDomainRequest, OutreachDoNotContactDomainResponse, OutreachDoNotContactDomainsResponse, OutreachEnrollmentAction, OutreachEnrollmentActionResponse, OutreachEnrollmentGatewayResponse, OutreachEnrollmentReplyRequest, OutreachEnrollmentReplyResponse, OutreachEnrollmentThreadResponse, OutreachEnrollPersonRequest, OutreachEnrollPreviewResponse, OutreachEnrollResponse, OutreachEnrollSource, OutreachLinkDomainRequest, OutreachLinkDomainResponse, OutreachLinkDomainsResponse, OutreachPreviewRequest, OutreachPreviewResponse, OutreachRouteRefusalReason, OutreachSequenceAction, OutreachSequenceSaveResponse, OutreachSequenceStatusResponse, OutreachSettingsResponse, OutreachSettingsSaveRequest, OutreachSettingsSaveResponse, OutreachStepTestRequest, OutreachStepTestResponse } from '../model/outreach-api';
|
|
3
3
|
import type { OutreachSequenceDraft, OutreachSequenceIssue } from '../model/sequence-draft';
|
|
4
4
|
/**
|
|
5
5
|
* A route's refusal, carrying its sentence, its stable reason and any
|
|
@@ -29,6 +29,10 @@ export interface OutreachApi {
|
|
|
29
29
|
actOnEnrollment(enrollmentId: string, action: OutreachEnrollmentAction, detail?: string): Promise<OutreachEnrollmentActionResponse>;
|
|
30
30
|
/** The mail gateway in front of one enrolled person's domain (AGL-3332). */
|
|
31
31
|
readEnrollmentGateway(enrollmentId: string): Promise<OutreachEnrollmentGatewayResponse>;
|
|
32
|
+
/** What the person wrote back, and where an answer would go (AGL-4020). */
|
|
33
|
+
readEnrollmentThread(enrollmentId: string): Promise<OutreachEnrollmentThreadResponse>;
|
|
34
|
+
/** Sends a member's answer in the person's thread, from the enrollment's mailbox (AGL-4020). */
|
|
35
|
+
replyToEnrollment(input: Omit<OutreachEnrollmentReplyRequest, 'orgId'>): Promise<OutreachEnrollmentReplyResponse>;
|
|
32
36
|
previewEmail(input: Omit<OutreachPreviewRequest, 'orgId'>): Promise<OutreachPreviewResponse>;
|
|
33
37
|
/**
|
|
34
38
|
* Sends one step of a saved sequence to the member as a test (AGL-3325):
|
|
@@ -135,6 +135,16 @@ import { OUTREACH_API_ROUTES } from "../constants/api-routes.js";
|
|
|
135
135
|
enrollmentId
|
|
136
136
|
}
|
|
137
137
|
}),
|
|
138
|
+
readEnrollmentThread: (enrollmentId)=>call(OUTREACH_API_ROUTES.enrollmentsThread, {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
body: {
|
|
141
|
+
enrollmentId
|
|
142
|
+
}
|
|
143
|
+
}),
|
|
144
|
+
replyToEnrollment: (input)=>call(OUTREACH_API_ROUTES.enrollmentsReply, {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
body: _extends({}, input)
|
|
147
|
+
}),
|
|
138
148
|
previewEmail: (input)=>call(OUTREACH_API_ROUTES.preview, {
|
|
139
149
|
method: 'POST',
|
|
140
150
|
body: _extends({}, input)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/use-outreach-api.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'use client'\n\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { useCallback, useMemo } from 'react'\nimport { OUTREACH_API_ROUTES } from '../constants/api-routes'\nimport type { OutreachComplianceIssue } from '../model/compliance-settings'\nimport type {\n OutreachCurateDraftRequest,\n OutreachCurateDraftResponse,\n OutreachCurateSaveRequest,\n OutreachCurateSaveResponse,\n OutreachDoNotContactDomainRequest,\n OutreachDoNotContactDomainResponse,\n OutreachDoNotContactDomainsResponse,\n OutreachEnrollmentAction,\n OutreachEnrollmentActionResponse,\n OutreachEnrollmentGatewayResponse,\n OutreachEnrollPersonRequest,\n OutreachEnrollPreviewResponse,\n OutreachEnrollResponse,\n OutreachEnrollSource,\n OutreachLinkDomainRequest,\n OutreachLinkDomainResponse,\n OutreachLinkDomainsResponse,\n OutreachPreviewRequest,\n OutreachPreviewResponse,\n OutreachRouteRefusal,\n OutreachRouteRefusalReason,\n OutreachSequenceAction,\n OutreachSequenceSaveResponse,\n OutreachSequenceStatusResponse,\n OutreachSettingsResponse,\n OutreachSettingsSaveRequest,\n OutreachSettingsSaveResponse,\n OutreachStepTestRequest,\n OutreachStepTestResponse,\n} from '../model/outreach-api'\nimport type {\n OutreachSequenceDraft,\n OutreachSequenceIssue,\n} from '../model/sequence-draft'\n\n/**\n * A route's refusal, carrying its sentence, its stable reason and any\n * field-level issues. `refused` is the one a page shows as \"you can't use\n * this here\" rather than as something that went wrong: no permission, no\n * entitlement, or the release gate answering as if the route did not exist.\n */\nexport class OutreachRouteError extends Error {\n readonly reason: OutreachRouteRefusalReason | 'unreachable' | 'not-found'\n readonly status: number\n readonly issues: ReadonlyArray<\n OutreachComplianceIssue | OutreachSequenceIssue\n >\n\n constructor(\n message: string,\n reason: OutreachRouteError['reason'],\n status: number,\n issues: OutreachRouteError['issues'] = [],\n ) {\n super(message)\n this.name = 'OutreachRouteError'\n this.reason = reason\n this.status = status\n this.issues = issues\n }\n\n get refused(): boolean {\n return (\n this.reason === 'permission' ||\n this.reason === 'entitlement' ||\n this.reason === 'not-org-wide' ||\n this.reason === 'not-a-member' ||\n // The dispatcher answers a route whose plugin is not released to this\n // workspace as though it did not exist.\n this.reason === 'not-found'\n )\n }\n}\n\n/** The routes, as the Outreach pages call them. */\nexport interface OutreachApi {\n readSettings(): Promise<OutreachSettingsResponse>\n saveSettings(\n input: Omit<OutreachSettingsSaveRequest, 'orgId'>,\n ): Promise<OutreachSettingsSaveResponse>\n saveSequence(\n sequenceId: string | null,\n sequence: OutreachSequenceDraft,\n ): Promise<OutreachSequenceSaveResponse>\n setSequenceStatus(\n sequenceId: string,\n action: OutreachSequenceAction,\n ): Promise<OutreachSequenceStatusResponse>\n deleteSequence(sequenceId: string): Promise<{ ok: true }>\n previewEnrollment(\n sequenceId: string,\n source: OutreachEnrollSource,\n ): Promise<OutreachEnrollPreviewResponse>\n /** `startStepIndex` (AGL-3228) is the step everyone begins at, zero-based; the first when absent. */\n enroll(\n sequenceId: string,\n people: OutreachEnrollPersonRequest[],\n startStepIndex?: number,\n ): Promise<OutreachEnrollResponse>\n actOnEnrollment(\n enrollmentId: string,\n action: OutreachEnrollmentAction,\n detail?: string,\n ): Promise<OutreachEnrollmentActionResponse>\n /** The mail gateway in front of one enrolled person's domain (AGL-3332). */\n readEnrollmentGateway(enrollmentId: string): Promise<OutreachEnrollmentGatewayResponse>\n previewEmail(\n input: Omit<OutreachPreviewRequest, 'orgId'>,\n ): Promise<OutreachPreviewResponse>\n /**\n * Sends one step of a saved sequence to the member as a test (AGL-3325):\n * the preview's email, through the sequence's mailbox, `[Test]` in front.\n */\n sendStepTest(\n input: Omit<OutreachStepTestRequest, 'orgId'>,\n ): Promise<OutreachStepTestResponse & { unresolvedFields?: string[] }>\n /**\n * One person's own copies of the sequence's email steps, drafted by the\n * workspace's AI (AGL-3324); nothing is stored until the member confirms.\n */\n curateDrafts(\n input: Omit<OutreachCurateDraftRequest, 'orgId'>,\n ): Promise<OutreachCurateDraftResponse>\n /** Stores a confirmed copy of one step on an enrollment, or clears it with `null`. */\n saveCuratedStep(\n enrollmentId: string,\n stepIndex: number,\n override: OutreachCurateSaveRequest['override'],\n ): Promise<OutreachCurateSaveResponse>\n /** The domains on the do-not-contact list (AGL-3244). */\n readDoNotContactDomains(): Promise<OutreachDoNotContactDomainsResponse>\n /** Puts a domain on the list, or takes one off. */\n changeDoNotContactDomain(\n action: OutreachDoNotContactDomainRequest['action'],\n domain: string,\n detail?: string,\n ): Promise<OutreachDoNotContactDomainResponse>\n /** Each mailbox domain's click-tracking host (AGL-3306). */\n readLinkDomains(): Promise<OutreachLinkDomainsResponse>\n /** Sets a link domain up, checks it, or removes it. */\n changeLinkDomain(\n action: OutreachLinkDomainRequest['action'],\n domain: string,\n ): Promise<OutreachLinkDomainResponse>\n}\n\n/**\n * The Outreach pages' one door to the settings, sequence and enrollment\n * routes (AGL-2980), with the member's session attached and the\n * organization named on every call. A refusal is thrown as an\n * {@link OutreachRouteError} carrying the route's own sentence, which is\n * written for the person reading it.\n */\nexport function useOutreachApi(orgId: string | null): OutreachApi {\n const { data: user } = useUser()\n\n const call = useCallback(\n async <T>(\n route: string,\n init: { method: 'GET' | 'POST'; body?: Record<string, unknown> },\n ): Promise<T> => {\n if (!orgId)\n throw new OutreachRouteError(\n 'Open an organization first.',\n 'org-required',\n 400,\n )\n const path =\n init.method === 'GET'\n ? `/api/${route}?orgId=${encodeURIComponent(orgId)}`\n : `/api/${route}`\n let response: Response\n try {\n response = await authorizedFetch(user, path, {\n method: init.method,\n ...(init.method === 'POST'\n ? {\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, ...init.body }),\n }\n : {}),\n })\n } catch {\n throw new OutreachRouteError(\n 'Sequences could not be reached. Try again.',\n 'unreachable',\n 0,\n )\n }\n const payload = (await response.json().catch(() => null)) as\n (T & { error?: undefined }) | Partial<OutreachRouteRefusal> | null\n if (!response.ok || !payload) {\n const refusal = (payload ?? {}) as Partial<OutreachRouteRefusal>\n throw new OutreachRouteError(\n refusal.error && refusal.reason\n ? refusal.error\n : response.status === 404\n ? \"Sequences isn't available to this workspace yet.\"\n : 'Sequences could not be reached. Try again.',\n refusal.reason ??\n (response.status === 404 ? 'not-found' : 'unreachable'),\n response.status,\n refusal.issues ?? [],\n )\n }\n return payload as T\n },\n [user, orgId],\n )\n\n return useMemo<OutreachApi>(\n () => ({\n readSettings: () => call(OUTREACH_API_ROUTES.settings, { method: 'GET' }),\n saveSettings: (input) =>\n call(OUTREACH_API_ROUTES.settings, {\n method: 'POST',\n body: { ...input },\n }),\n saveSequence: (sequenceId, sequence) =>\n call(OUTREACH_API_ROUTES.sequencesSave, {\n method: 'POST',\n body: { ...(sequenceId ? { sequenceId } : {}), sequence },\n }),\n setSequenceStatus: (sequenceId, action) =>\n call(OUTREACH_API_ROUTES.sequencesStatus, {\n method: 'POST',\n body: { sequenceId, action },\n }),\n deleteSequence: (sequenceId) =>\n call(OUTREACH_API_ROUTES.sequencesDelete, {\n method: 'POST',\n body: { sequenceId },\n }),\n previewEnrollment: (sequenceId, source) =>\n call(OUTREACH_API_ROUTES.enrollPreview, {\n method: 'POST',\n body: { sequenceId, source },\n }),\n enroll: (sequenceId, people, startStepIndex) =>\n call(OUTREACH_API_ROUTES.enroll, {\n method: 'POST',\n body: { sequenceId, people, ...(startStepIndex ? { startStepIndex } : {}) },\n }),\n actOnEnrollment: (enrollmentId, action, detail) =>\n call(OUTREACH_API_ROUTES.enrollmentsAction, {\n method: 'POST',\n body: { enrollmentId, action, ...(detail ? { detail } : {}) },\n }),\n readEnrollmentGateway: (enrollmentId) =>\n call(OUTREACH_API_ROUTES.enrollmentsGateway, {\n method: 'POST',\n body: { enrollmentId },\n }),\n previewEmail: (input) =>\n call(OUTREACH_API_ROUTES.preview, {\n method: 'POST',\n body: { ...input },\n }),\n sendStepTest: (input) =>\n call(OUTREACH_API_ROUTES.stepTest, {\n method: 'POST',\n body: { ...input },\n }),\n curateDrafts: (input) =>\n call(OUTREACH_API_ROUTES.curateDraft, {\n method: 'POST',\n body: { ...input },\n }),\n saveCuratedStep: (enrollmentId, stepIndex, override) =>\n call(OUTREACH_API_ROUTES.curateSave, {\n method: 'POST',\n body: { enrollmentId, stepIndex, override },\n }),\n readDoNotContactDomains: () =>\n call(OUTREACH_API_ROUTES.doNotContactDomains, { method: 'GET' }),\n changeDoNotContactDomain: (action, domain, detail) =>\n call(OUTREACH_API_ROUTES.doNotContactDomains, {\n method: 'POST',\n body: { action, domain, ...(detail ? { detail } : {}) },\n }),\n readLinkDomains: () => call(OUTREACH_API_ROUTES.linkDomains, { method: 'GET' }),\n changeLinkDomain: (action, domain) =>\n call(OUTREACH_API_ROUTES.linkDomains, {\n method: 'POST',\n body: { action, domain },\n }),\n }),\n [call],\n )\n}\n"],"names":["authorizedFetch","useUser","useCallback","useMemo","OUTREACH_API_ROUTES","OutreachRouteError","Error","refused","reason","message","status","issues","name","useOutreachApi","orgId","data","user","call","route","init","path","method","encodeURIComponent","response","headers","body","JSON","stringify","payload","json","catch","ok","refusal","error","readSettings","settings","saveSettings","input","saveSequence","sequenceId","sequence","sequencesSave","setSequenceStatus","action","sequencesStatus","deleteSequence","sequencesDelete","previewEnrollment","source","enrollPreview","enroll","people","startStepIndex","actOnEnrollment","enrollmentId","detail","enrollmentsAction","readEnrollmentGateway","enrollmentsGateway","previewEmail","preview","sendStepTest","stepTest","curateDrafts","curateDraft","saveCuratedStep","stepIndex","override","curateSave","readDoNotContactDomains","doNotContactDomains","changeDoNotContactDomain","domain","readLinkDomains","linkDomains","changeLinkDomain"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/use-outreach-api.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'use client'\n\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { useCallback, useMemo } from 'react'\nimport { OUTREACH_API_ROUTES } from '../constants/api-routes'\nimport type { OutreachComplianceIssue } from '../model/compliance-settings'\nimport type {\n OutreachCurateDraftRequest,\n OutreachCurateDraftResponse,\n OutreachCurateSaveRequest,\n OutreachCurateSaveResponse,\n OutreachDoNotContactDomainRequest,\n OutreachDoNotContactDomainResponse,\n OutreachDoNotContactDomainsResponse,\n OutreachEnrollmentAction,\n OutreachEnrollmentActionResponse,\n OutreachEnrollmentGatewayResponse,\n OutreachEnrollmentReplyRequest,\n OutreachEnrollmentReplyResponse,\n OutreachEnrollmentThreadResponse,\n OutreachEnrollPersonRequest,\n OutreachEnrollPreviewResponse,\n OutreachEnrollResponse,\n OutreachEnrollSource,\n OutreachLinkDomainRequest,\n OutreachLinkDomainResponse,\n OutreachLinkDomainsResponse,\n OutreachPreviewRequest,\n OutreachPreviewResponse,\n OutreachRouteRefusal,\n OutreachRouteRefusalReason,\n OutreachSequenceAction,\n OutreachSequenceSaveResponse,\n OutreachSequenceStatusResponse,\n OutreachSettingsResponse,\n OutreachSettingsSaveRequest,\n OutreachSettingsSaveResponse,\n OutreachStepTestRequest,\n OutreachStepTestResponse,\n} from '../model/outreach-api'\nimport type {\n OutreachSequenceDraft,\n OutreachSequenceIssue,\n} from '../model/sequence-draft'\n\n/**\n * A route's refusal, carrying its sentence, its stable reason and any\n * field-level issues. `refused` is the one a page shows as \"you can't use\n * this here\" rather than as something that went wrong: no permission, no\n * entitlement, or the release gate answering as if the route did not exist.\n */\nexport class OutreachRouteError extends Error {\n readonly reason: OutreachRouteRefusalReason | 'unreachable' | 'not-found'\n readonly status: number\n readonly issues: ReadonlyArray<\n OutreachComplianceIssue | OutreachSequenceIssue\n >\n\n constructor(\n message: string,\n reason: OutreachRouteError['reason'],\n status: number,\n issues: OutreachRouteError['issues'] = [],\n ) {\n super(message)\n this.name = 'OutreachRouteError'\n this.reason = reason\n this.status = status\n this.issues = issues\n }\n\n get refused(): boolean {\n return (\n this.reason === 'permission' ||\n this.reason === 'entitlement' ||\n this.reason === 'not-org-wide' ||\n this.reason === 'not-a-member' ||\n // The dispatcher answers a route whose plugin is not released to this\n // workspace as though it did not exist.\n this.reason === 'not-found'\n )\n }\n}\n\n/** The routes, as the Outreach pages call them. */\nexport interface OutreachApi {\n readSettings(): Promise<OutreachSettingsResponse>\n saveSettings(\n input: Omit<OutreachSettingsSaveRequest, 'orgId'>,\n ): Promise<OutreachSettingsSaveResponse>\n saveSequence(\n sequenceId: string | null,\n sequence: OutreachSequenceDraft,\n ): Promise<OutreachSequenceSaveResponse>\n setSequenceStatus(\n sequenceId: string,\n action: OutreachSequenceAction,\n ): Promise<OutreachSequenceStatusResponse>\n deleteSequence(sequenceId: string): Promise<{ ok: true }>\n previewEnrollment(\n sequenceId: string,\n source: OutreachEnrollSource,\n ): Promise<OutreachEnrollPreviewResponse>\n /** `startStepIndex` (AGL-3228) is the step everyone begins at, zero-based; the first when absent. */\n enroll(\n sequenceId: string,\n people: OutreachEnrollPersonRequest[],\n startStepIndex?: number,\n ): Promise<OutreachEnrollResponse>\n actOnEnrollment(\n enrollmentId: string,\n action: OutreachEnrollmentAction,\n detail?: string,\n ): Promise<OutreachEnrollmentActionResponse>\n /** The mail gateway in front of one enrolled person's domain (AGL-3332). */\n readEnrollmentGateway(enrollmentId: string): Promise<OutreachEnrollmentGatewayResponse>\n /** What the person wrote back, and where an answer would go (AGL-4020). */\n readEnrollmentThread(enrollmentId: string): Promise<OutreachEnrollmentThreadResponse>\n /** Sends a member's answer in the person's thread, from the enrollment's mailbox (AGL-4020). */\n replyToEnrollment(\n input: Omit<OutreachEnrollmentReplyRequest, 'orgId'>,\n ): Promise<OutreachEnrollmentReplyResponse>\n previewEmail(\n input: Omit<OutreachPreviewRequest, 'orgId'>,\n ): Promise<OutreachPreviewResponse>\n /**\n * Sends one step of a saved sequence to the member as a test (AGL-3325):\n * the preview's email, through the sequence's mailbox, `[Test]` in front.\n */\n sendStepTest(\n input: Omit<OutreachStepTestRequest, 'orgId'>,\n ): Promise<OutreachStepTestResponse & { unresolvedFields?: string[] }>\n /**\n * One person's own copies of the sequence's email steps, drafted by the\n * workspace's AI (AGL-3324); nothing is stored until the member confirms.\n */\n curateDrafts(\n input: Omit<OutreachCurateDraftRequest, 'orgId'>,\n ): Promise<OutreachCurateDraftResponse>\n /** Stores a confirmed copy of one step on an enrollment, or clears it with `null`. */\n saveCuratedStep(\n enrollmentId: string,\n stepIndex: number,\n override: OutreachCurateSaveRequest['override'],\n ): Promise<OutreachCurateSaveResponse>\n /** The domains on the do-not-contact list (AGL-3244). */\n readDoNotContactDomains(): Promise<OutreachDoNotContactDomainsResponse>\n /** Puts a domain on the list, or takes one off. */\n changeDoNotContactDomain(\n action: OutreachDoNotContactDomainRequest['action'],\n domain: string,\n detail?: string,\n ): Promise<OutreachDoNotContactDomainResponse>\n /** Each mailbox domain's click-tracking host (AGL-3306). */\n readLinkDomains(): Promise<OutreachLinkDomainsResponse>\n /** Sets a link domain up, checks it, or removes it. */\n changeLinkDomain(\n action: OutreachLinkDomainRequest['action'],\n domain: string,\n ): Promise<OutreachLinkDomainResponse>\n}\n\n/**\n * The Outreach pages' one door to the settings, sequence and enrollment\n * routes (AGL-2980), with the member's session attached and the\n * organization named on every call. A refusal is thrown as an\n * {@link OutreachRouteError} carrying the route's own sentence, which is\n * written for the person reading it.\n */\nexport function useOutreachApi(orgId: string | null): OutreachApi {\n const { data: user } = useUser()\n\n const call = useCallback(\n async <T>(\n route: string,\n init: { method: 'GET' | 'POST'; body?: Record<string, unknown> },\n ): Promise<T> => {\n if (!orgId)\n throw new OutreachRouteError(\n 'Open an organization first.',\n 'org-required',\n 400,\n )\n const path =\n init.method === 'GET'\n ? `/api/${route}?orgId=${encodeURIComponent(orgId)}`\n : `/api/${route}`\n let response: Response\n try {\n response = await authorizedFetch(user, path, {\n method: init.method,\n ...(init.method === 'POST'\n ? {\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, ...init.body }),\n }\n : {}),\n })\n } catch {\n throw new OutreachRouteError(\n 'Sequences could not be reached. Try again.',\n 'unreachable',\n 0,\n )\n }\n const payload = (await response.json().catch(() => null)) as\n (T & { error?: undefined }) | Partial<OutreachRouteRefusal> | null\n if (!response.ok || !payload) {\n const refusal = (payload ?? {}) as Partial<OutreachRouteRefusal>\n throw new OutreachRouteError(\n refusal.error && refusal.reason\n ? refusal.error\n : response.status === 404\n ? \"Sequences isn't available to this workspace yet.\"\n : 'Sequences could not be reached. Try again.',\n refusal.reason ??\n (response.status === 404 ? 'not-found' : 'unreachable'),\n response.status,\n refusal.issues ?? [],\n )\n }\n return payload as T\n },\n [user, orgId],\n )\n\n return useMemo<OutreachApi>(\n () => ({\n readSettings: () => call(OUTREACH_API_ROUTES.settings, { method: 'GET' }),\n saveSettings: (input) =>\n call(OUTREACH_API_ROUTES.settings, {\n method: 'POST',\n body: { ...input },\n }),\n saveSequence: (sequenceId, sequence) =>\n call(OUTREACH_API_ROUTES.sequencesSave, {\n method: 'POST',\n body: { ...(sequenceId ? { sequenceId } : {}), sequence },\n }),\n setSequenceStatus: (sequenceId, action) =>\n call(OUTREACH_API_ROUTES.sequencesStatus, {\n method: 'POST',\n body: { sequenceId, action },\n }),\n deleteSequence: (sequenceId) =>\n call(OUTREACH_API_ROUTES.sequencesDelete, {\n method: 'POST',\n body: { sequenceId },\n }),\n previewEnrollment: (sequenceId, source) =>\n call(OUTREACH_API_ROUTES.enrollPreview, {\n method: 'POST',\n body: { sequenceId, source },\n }),\n enroll: (sequenceId, people, startStepIndex) =>\n call(OUTREACH_API_ROUTES.enroll, {\n method: 'POST',\n body: { sequenceId, people, ...(startStepIndex ? { startStepIndex } : {}) },\n }),\n actOnEnrollment: (enrollmentId, action, detail) =>\n call(OUTREACH_API_ROUTES.enrollmentsAction, {\n method: 'POST',\n body: { enrollmentId, action, ...(detail ? { detail } : {}) },\n }),\n readEnrollmentGateway: (enrollmentId) =>\n call(OUTREACH_API_ROUTES.enrollmentsGateway, {\n method: 'POST',\n body: { enrollmentId },\n }),\n readEnrollmentThread: (enrollmentId) =>\n call(OUTREACH_API_ROUTES.enrollmentsThread, {\n method: 'POST',\n body: { enrollmentId },\n }),\n replyToEnrollment: (input) =>\n call(OUTREACH_API_ROUTES.enrollmentsReply, {\n method: 'POST',\n body: { ...input },\n }),\n previewEmail: (input) =>\n call(OUTREACH_API_ROUTES.preview, {\n method: 'POST',\n body: { ...input },\n }),\n sendStepTest: (input) =>\n call(OUTREACH_API_ROUTES.stepTest, {\n method: 'POST',\n body: { ...input },\n }),\n curateDrafts: (input) =>\n call(OUTREACH_API_ROUTES.curateDraft, {\n method: 'POST',\n body: { ...input },\n }),\n saveCuratedStep: (enrollmentId, stepIndex, override) =>\n call(OUTREACH_API_ROUTES.curateSave, {\n method: 'POST',\n body: { enrollmentId, stepIndex, override },\n }),\n readDoNotContactDomains: () =>\n call(OUTREACH_API_ROUTES.doNotContactDomains, { method: 'GET' }),\n changeDoNotContactDomain: (action, domain, detail) =>\n call(OUTREACH_API_ROUTES.doNotContactDomains, {\n method: 'POST',\n body: { action, domain, ...(detail ? { detail } : {}) },\n }),\n readLinkDomains: () => call(OUTREACH_API_ROUTES.linkDomains, { method: 'GET' }),\n changeLinkDomain: (action, domain) =>\n call(OUTREACH_API_ROUTES.linkDomains, {\n method: 'POST',\n body: { action, domain },\n }),\n }),\n [call],\n )\n}\n"],"names":["authorizedFetch","useUser","useCallback","useMemo","OUTREACH_API_ROUTES","OutreachRouteError","Error","refused","reason","message","status","issues","name","useOutreachApi","orgId","data","user","call","route","init","path","method","encodeURIComponent","response","headers","body","JSON","stringify","payload","json","catch","ok","refusal","error","readSettings","settings","saveSettings","input","saveSequence","sequenceId","sequence","sequencesSave","setSequenceStatus","action","sequencesStatus","deleteSequence","sequencesDelete","previewEnrollment","source","enrollPreview","enroll","people","startStepIndex","actOnEnrollment","enrollmentId","detail","enrollmentsAction","readEnrollmentGateway","enrollmentsGateway","readEnrollmentThread","enrollmentsThread","replyToEnrollment","enrollmentsReply","previewEmail","preview","sendStepTest","stepTest","curateDrafts","curateDraft","saveCuratedStep","stepIndex","override","curateSave","readDoNotContactDomains","doNotContactDomains","changeDoNotContactDomain","domain","readLinkDomains","linkDomains","changeLinkDomain"],"mappings":"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"}
|
|
@@ -68,6 +68,14 @@ export declare const OUTREACH_API_ROUTES: {
|
|
|
68
68
|
readonly enrollmentsAction: "outreach/enrollments/action";
|
|
69
69
|
/** `POST` — the mail gateway in front of one enrolled person's domain (AGL-3332). */
|
|
70
70
|
readonly enrollmentsGateway: "outreach/enrollments/gateway";
|
|
71
|
+
/**
|
|
72
|
+
* `POST` — what the person wrote back and where an answer would go
|
|
73
|
+
* (AGL-4020): the reply composer's opening read, which reads the mailbox
|
|
74
|
+
* for a reply synced before replies were kept.
|
|
75
|
+
*/
|
|
76
|
+
readonly enrollmentsThread: "outreach/enrollments/thread";
|
|
77
|
+
/** `POST` — sends a member's answer in the person's thread, from the enrollment's mailbox (AGL-4020). */
|
|
78
|
+
readonly enrollmentsReply: "outreach/enrollments/reply";
|
|
71
79
|
/**
|
|
72
80
|
* `GET ?orgId` — the domains on the organization's do-not-contact list
|
|
73
81
|
* (AGL-3244); `POST` — add one, or take one off.
|
|
@@ -53,6 +53,12 @@
|
|
|
53
53
|
/** `POST` — pause, resume, stop, or mark do-not-contact. */ enrollmentsAction: 'outreach/enrollments/action',
|
|
54
54
|
/** `POST` — the mail gateway in front of one enrolled person's domain (AGL-3332). */ enrollmentsGateway: 'outreach/enrollments/gateway',
|
|
55
55
|
/**
|
|
56
|
+
* `POST` — what the person wrote back and where an answer would go
|
|
57
|
+
* (AGL-4020): the reply composer's opening read, which reads the mailbox
|
|
58
|
+
* for a reply synced before replies were kept.
|
|
59
|
+
*/ enrollmentsThread: 'outreach/enrollments/thread',
|
|
60
|
+
/** `POST` — sends a member's answer in the person's thread, from the enrollment's mailbox (AGL-4020). */ enrollmentsReply: 'outreach/enrollments/reply',
|
|
61
|
+
/**
|
|
56
62
|
* `GET ?orgId` — the domains on the organization's do-not-contact list
|
|
57
63
|
* (AGL-3244); `POST` — add one, or take one off.
|
|
58
64
|
*/ doNotContactDomains: 'outreach/do-not-contact/domains',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/constants/api-routes.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 * Outreach's console API routes, as the dispatcher keys them (AGL-2974).\n *\n * One table both halves import, because the server registers the path and the\n * client fetches it, and a route spelled twice is a route that 404s the day\n * one spelling changes. Every path sits under the `outreach` prefix that\n * `plugins.config.json` gives this plugin, which is what lets the console's\n * `/api/[...pluginApi]` dispatcher gate a request on the plugin being enabled\n * and released before the handler runs. Client-safe: constants only.\n */\nexport const OUTREACH_API_ROUTES = {\n /** `GET` — proves the server bundle loaded and registered; see `server.ts`. */\n ping: 'outreach/ping',\n // Mailboxes (AGL-2978). Every one names its org (`orgId`, query or body)\n // so the dispatcher's release gate asks about that organization.\n /** `GET ?orgId` — whether this deployment can connect a Google mailbox. */\n mailboxesAvailability: 'outreach/mailboxes/availability',\n /** `POST` — Google's consent address for a new connect. */\n mailboxesConnect: 'outreach/mailboxes/connect',\n /**\n * `GET` — Google's redirect back. It carries no bearer token: the signed\n * state names the org and the member, and the route hands the code to the\n * Mailboxes page in a fragment rather than acting on it.\n */\n mailboxesOAuthCallback: 'outreach/mailboxes/oauth/callback',\n /** `POST` — finishes a connect with the member's own session. */\n mailboxesConnectComplete: 'outreach/mailboxes/connect/complete',\n /** `POST` — send-as, display name, daily cap, sending window, timezone. */\n mailboxesSettings: 'outreach/mailboxes/settings',\n /** `POST` — pause or resume. */\n mailboxesStatus: 'outreach/mailboxes/status',\n /** `POST` — a plain-text test to the account's own address. */\n mailboxesTest: 'outreach/mailboxes/test',\n /** `POST` — revoke the grant at Google and delete the mailbox. */\n mailboxesDisconnect: 'outreach/mailboxes/disconnect',\n /**\n * `POST` — SPF, DKIM and DMARC for the domain a mailbox sends as, and\n * whether they align (AGL-3328).\n */\n mailboxesReadiness: 'outreach/mailboxes/readiness',\n // Settings, sequences and enrollments (AGL-2980). Every one names its org\n // (`orgId`, query or body); the contract is `model/outreach-api.ts`.\n /** `GET ?orgId` — the compliance settings; `POST` — save them. */\n settings: 'outreach/settings',\n /** `POST` — create a sequence, or save an edit to one. */\n sequencesSave: 'outreach/sequences/save',\n /** `POST` — activate, pause or archive a sequence. */\n sequencesStatus: 'outreach/sequences/status',\n /** `POST` — delete a draft nobody was enrolled in. */\n sequencesDelete: 'outreach/sequences/delete',\n /** `POST` — who a saved view or a search would enroll, and where each stands. */\n enrollPreview: 'outreach/enroll/preview',\n /** `POST` — enroll them, every gate checked again. */\n enroll: 'outreach/enroll',\n /** `POST` — pause, resume, stop, or mark do-not-contact. */\n enrollmentsAction: 'outreach/enrollments/action',\n /** `POST` — the mail gateway in front of one enrolled person's domain (AGL-3332). */\n enrollmentsGateway: 'outreach/enrollments/gateway',\n /**\n * `GET ?orgId` — the domains on the organization's do-not-contact list\n * (AGL-3244); `POST` — add one, or take one off.\n */\n doNotContactDomains: 'outreach/do-not-contact/domains',\n /**\n * `GET ?orgId` — each connected mailbox domain's click-tracking host\n * (AGL-3306), `links.<domain>`; `POST` — set one up, check it, or remove\n * it. Setting up and removing are an owner's or admin's.\n */\n linkDomains: 'outreach/link-domains',\n /** `POST` — one email of a saved sequence, for a contact or a sample person. */\n preview: 'outreach/preview',\n /**\n * `POST` — one step of a saved sequence, rendered as the preview renders\n * it and sent through the sequence's mailbox to the member (AGL-3325):\n * `[Test]` in the subject, its links minted as test links, and counted\n * on nothing but the mailbox's test tally.\n */\n stepTest: 'outreach/steps/test',\n /**\n * `POST` — one person's own copies of a sequence's email steps, drafted by\n * the workspace's AI (AGL-3324): for a person about to be enrolled, or for\n * the next step of an enrollment. Nothing is stored.\n */\n curateDraft: 'outreach/curate/draft',\n /** `POST` — stores a member's confirmed copy of one step on an enrollment, or clears it. */\n curateSave: 'outreach/curate/save',\n // The one route a RECIPIENT reaches (AGL-2981): no session, a signed\n // token instead, and registered as a recipient link so it answers whether\n // or not Outreach is released for the organization now.\n /** `GET` and the RFC 8058 one-click `POST` — `?t=<signed token>`. */\n unsubscribe: 'outreach/unsubscribe',\n /**\n * `GET` — a link in a tracked sequence email (AGL-3239). `?t=<signed\n * token>`; answers a redirect to the destination the token names, and\n * records the click afterwards. A recipient link like the unsubscribe\n * one: no session, and it answers whether or not Outreach is released to\n * the organization now, because a link in an email that already left must\n * not stop working when a rollout is paused.\n */\n click: 'outreach/click',\n /**\n * `GET` — the short tracking link (AGL-3297): `outreach/l/<id>`, where the\n * id names a stored `outreachLinks` document holding the destination. It\n * replaced the signed `click` link for every send since; `click` keeps\n * answering the links already in inboxes. A recipient link, for `click`'s\n * reasons.\n */\n shortLink: 'outreach/l/:linkId',\n} as const\n"],"names":["OUTREACH_API_ROUTES","ping","mailboxesAvailability","mailboxesConnect","mailboxesOAuthCallback","mailboxesConnectComplete","mailboxesSettings","mailboxesStatus","mailboxesTest","mailboxesDisconnect","mailboxesReadiness","settings","sequencesSave","sequencesStatus","sequencesDelete","enrollPreview","enroll","enrollmentsAction","enrollmentsGateway","doNotContactDomains","linkDomains","preview","stepTest","curateDraft","curateSave","unsubscribe","click","shortLink"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;CASC,GACD,OAAO,MAAMA,sBAAsB;IACjC,6EAA6E,GAC7EC,MAAM;IACN,yEAAyE;IACzE,iEAAiE;IACjE,yEAAyE,GACzEC,uBAAuB;IACvB,yDAAyD,GACzDC,kBAAkB;IAClB;;;;GAIC,GACDC,wBAAwB;IACxB,+DAA+D,GAC/DC,0BAA0B;IAC1B,yEAAyE,GACzEC,mBAAmB;IACnB,8BAA8B,GAC9BC,iBAAiB;IACjB,6DAA6D,GAC7DC,eAAe;IACf,gEAAgE,GAChEC,qBAAqB;IACrB;;;GAGC,GACDC,oBAAoB;IACpB,0EAA0E;IAC1E,qEAAqE;IACrE,gEAAgE,GAChEC,UAAU;IACV,wDAAwD,GACxDC,eAAe;IACf,oDAAoD,GACpDC,iBAAiB;IACjB,oDAAoD,GACpDC,iBAAiB;IACjB,+EAA+E,GAC/EC,eAAe;IACf,oDAAoD,GACpDC,QAAQ;IACR,0DAA0D,GAC1DC,mBAAmB;IACnB,mFAAmF,GACnFC,oBAAoB;IACpB;;;GAGC,GACDC,qBAAqB;IACrB;;;;GAIC,GACDC,aAAa;IACb,8EAA8E,GAC9EC,SAAS;IACT;;;;;GAKC,GACDC,UAAU;IACV;;;;GAIC,GACDC,aAAa;IACb,0FAA0F,GAC1FC,YAAY;IACZ,qEAAqE;IACrE,0EAA0E;IAC1E,wDAAwD;IACxD,mEAAmE,GACnEC,aAAa;IACb;;;;;;;GAOC,GACDC,OAAO;IACP;;;;;;GAMC,GACDC,WAAW;AACb,EAAU"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/constants/api-routes.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 * Outreach's console API routes, as the dispatcher keys them (AGL-2974).\n *\n * One table both halves import, because the server registers the path and the\n * client fetches it, and a route spelled twice is a route that 404s the day\n * one spelling changes. Every path sits under the `outreach` prefix that\n * `plugins.config.json` gives this plugin, which is what lets the console's\n * `/api/[...pluginApi]` dispatcher gate a request on the plugin being enabled\n * and released before the handler runs. Client-safe: constants only.\n */\nexport const OUTREACH_API_ROUTES = {\n /** `GET` — proves the server bundle loaded and registered; see `server.ts`. */\n ping: 'outreach/ping',\n // Mailboxes (AGL-2978). Every one names its org (`orgId`, query or body)\n // so the dispatcher's release gate asks about that organization.\n /** `GET ?orgId` — whether this deployment can connect a Google mailbox. */\n mailboxesAvailability: 'outreach/mailboxes/availability',\n /** `POST` — Google's consent address for a new connect. */\n mailboxesConnect: 'outreach/mailboxes/connect',\n /**\n * `GET` — Google's redirect back. It carries no bearer token: the signed\n * state names the org and the member, and the route hands the code to the\n * Mailboxes page in a fragment rather than acting on it.\n */\n mailboxesOAuthCallback: 'outreach/mailboxes/oauth/callback',\n /** `POST` — finishes a connect with the member's own session. */\n mailboxesConnectComplete: 'outreach/mailboxes/connect/complete',\n /** `POST` — send-as, display name, daily cap, sending window, timezone. */\n mailboxesSettings: 'outreach/mailboxes/settings',\n /** `POST` — pause or resume. */\n mailboxesStatus: 'outreach/mailboxes/status',\n /** `POST` — a plain-text test to the account's own address. */\n mailboxesTest: 'outreach/mailboxes/test',\n /** `POST` — revoke the grant at Google and delete the mailbox. */\n mailboxesDisconnect: 'outreach/mailboxes/disconnect',\n /**\n * `POST` — SPF, DKIM and DMARC for the domain a mailbox sends as, and\n * whether they align (AGL-3328).\n */\n mailboxesReadiness: 'outreach/mailboxes/readiness',\n // Settings, sequences and enrollments (AGL-2980). Every one names its org\n // (`orgId`, query or body); the contract is `model/outreach-api.ts`.\n /** `GET ?orgId` — the compliance settings; `POST` — save them. */\n settings: 'outreach/settings',\n /** `POST` — create a sequence, or save an edit to one. */\n sequencesSave: 'outreach/sequences/save',\n /** `POST` — activate, pause or archive a sequence. */\n sequencesStatus: 'outreach/sequences/status',\n /** `POST` — delete a draft nobody was enrolled in. */\n sequencesDelete: 'outreach/sequences/delete',\n /** `POST` — who a saved view or a search would enroll, and where each stands. */\n enrollPreview: 'outreach/enroll/preview',\n /** `POST` — enroll them, every gate checked again. */\n enroll: 'outreach/enroll',\n /** `POST` — pause, resume, stop, or mark do-not-contact. */\n enrollmentsAction: 'outreach/enrollments/action',\n /** `POST` — the mail gateway in front of one enrolled person's domain (AGL-3332). */\n enrollmentsGateway: 'outreach/enrollments/gateway',\n /**\n * `POST` — what the person wrote back and where an answer would go\n * (AGL-4020): the reply composer's opening read, which reads the mailbox\n * for a reply synced before replies were kept.\n */\n enrollmentsThread: 'outreach/enrollments/thread',\n /** `POST` — sends a member's answer in the person's thread, from the enrollment's mailbox (AGL-4020). */\n enrollmentsReply: 'outreach/enrollments/reply',\n /**\n * `GET ?orgId` — the domains on the organization's do-not-contact list\n * (AGL-3244); `POST` — add one, or take one off.\n */\n doNotContactDomains: 'outreach/do-not-contact/domains',\n /**\n * `GET ?orgId` — each connected mailbox domain's click-tracking host\n * (AGL-3306), `links.<domain>`; `POST` — set one up, check it, or remove\n * it. Setting up and removing are an owner's or admin's.\n */\n linkDomains: 'outreach/link-domains',\n /** `POST` — one email of a saved sequence, for a contact or a sample person. */\n preview: 'outreach/preview',\n /**\n * `POST` — one step of a saved sequence, rendered as the preview renders\n * it and sent through the sequence's mailbox to the member (AGL-3325):\n * `[Test]` in the subject, its links minted as test links, and counted\n * on nothing but the mailbox's test tally.\n */\n stepTest: 'outreach/steps/test',\n /**\n * `POST` — one person's own copies of a sequence's email steps, drafted by\n * the workspace's AI (AGL-3324): for a person about to be enrolled, or for\n * the next step of an enrollment. Nothing is stored.\n */\n curateDraft: 'outreach/curate/draft',\n /** `POST` — stores a member's confirmed copy of one step on an enrollment, or clears it. */\n curateSave: 'outreach/curate/save',\n // The one route a RECIPIENT reaches (AGL-2981): no session, a signed\n // token instead, and registered as a recipient link so it answers whether\n // or not Outreach is released for the organization now.\n /** `GET` and the RFC 8058 one-click `POST` — `?t=<signed token>`. */\n unsubscribe: 'outreach/unsubscribe',\n /**\n * `GET` — a link in a tracked sequence email (AGL-3239). `?t=<signed\n * token>`; answers a redirect to the destination the token names, and\n * records the click afterwards. A recipient link like the unsubscribe\n * one: no session, and it answers whether or not Outreach is released to\n * the organization now, because a link in an email that already left must\n * not stop working when a rollout is paused.\n */\n click: 'outreach/click',\n /**\n * `GET` — the short tracking link (AGL-3297): `outreach/l/<id>`, where the\n * id names a stored `outreachLinks` document holding the destination. It\n * replaced the signed `click` link for every send since; `click` keeps\n * answering the links already in inboxes. A recipient link, for `click`'s\n * reasons.\n */\n shortLink: 'outreach/l/:linkId',\n} as const\n"],"names":["OUTREACH_API_ROUTES","ping","mailboxesAvailability","mailboxesConnect","mailboxesOAuthCallback","mailboxesConnectComplete","mailboxesSettings","mailboxesStatus","mailboxesTest","mailboxesDisconnect","mailboxesReadiness","settings","sequencesSave","sequencesStatus","sequencesDelete","enrollPreview","enroll","enrollmentsAction","enrollmentsGateway","enrollmentsThread","enrollmentsReply","doNotContactDomains","linkDomains","preview","stepTest","curateDraft","curateSave","unsubscribe","click","shortLink"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;CASC,GACD,OAAO,MAAMA,sBAAsB;IACjC,6EAA6E,GAC7EC,MAAM;IACN,yEAAyE;IACzE,iEAAiE;IACjE,yEAAyE,GACzEC,uBAAuB;IACvB,yDAAyD,GACzDC,kBAAkB;IAClB;;;;GAIC,GACDC,wBAAwB;IACxB,+DAA+D,GAC/DC,0BAA0B;IAC1B,yEAAyE,GACzEC,mBAAmB;IACnB,8BAA8B,GAC9BC,iBAAiB;IACjB,6DAA6D,GAC7DC,eAAe;IACf,gEAAgE,GAChEC,qBAAqB;IACrB;;;GAGC,GACDC,oBAAoB;IACpB,0EAA0E;IAC1E,qEAAqE;IACrE,gEAAgE,GAChEC,UAAU;IACV,wDAAwD,GACxDC,eAAe;IACf,oDAAoD,GACpDC,iBAAiB;IACjB,oDAAoD,GACpDC,iBAAiB;IACjB,+EAA+E,GAC/EC,eAAe;IACf,oDAAoD,GACpDC,QAAQ;IACR,0DAA0D,GAC1DC,mBAAmB;IACnB,mFAAmF,GACnFC,oBAAoB;IACpB;;;;GAIC,GACDC,mBAAmB;IACnB,uGAAuG,GACvGC,kBAAkB;IAClB;;;GAGC,GACDC,qBAAqB;IACrB;;;;GAIC,GACDC,aAAa;IACb,8EAA8E,GAC9EC,SAAS;IACT;;;;;GAKC,GACDC,UAAU;IACV;;;;GAIC,GACDC,aAAa;IACb,0FAA0F,GAC1FC,YAAY;IACZ,qEAAqE;IACrE,0EAA0E;IAC1E,wDAAwD;IACxD,mEAAmE,GACnEC,aAAa;IACb;;;;;;;GAOC,GACDC,OAAO;IACP;;;;;;GAMC,GACDC,WAAW;AACb,EAAU"}
|
package/src/lib/engine/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/engine/index.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 * The Outreach engine (AGL-2979): pure decisions, no I/O, safe for the\n * console and the server alike. `./do-not-contact` is deliberately absent —\n * it hashes with `node:crypto` — and is imported by its own path.\n */\nexport * from './sequence-validation'\nexport * from './recipient-country'\nexport * from './gates'\nexport * from './schedule'\nexport * from './sending-capacity'\nexport * from './next-send'\nexport * from './enrollment-state'\nexport * from './compose'\nexport * from './thread-message'\nexport * from './delivery-status'\nexport * from './opt-out-intent'\nexport * from './thread-classification'\nexport * from './mailbox-health'\nexport * from './mailbox-notice'\nexport * from './gateway-block'\nexport * from './do-not-contact-domain'\nexport * from './mailbox-rotation'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;CAIC,GACD,cAAc,2BAAuB;AACrC,cAAc,yBAAqB;AACnC,cAAc,aAAS;AACvB,cAAc,gBAAY;AAC1B,cAAc,wBAAoB;AAClC,cAAc,iBAAa;AAC3B,cAAc,wBAAoB;AAClC,cAAc,eAAW;AACzB,cAAc,sBAAkB;AAChC,cAAc,uBAAmB;AACjC,cAAc,sBAAkB;AAChC,cAAc,6BAAyB;AACvC,cAAc,sBAAkB;AAChC,cAAc,sBAAkB;AAChC,cAAc,qBAAiB;AAC/B,cAAc,6BAAyB;AACvC,cAAc,wBAAoB"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/engine/index.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 * The Outreach engine (AGL-2979): pure decisions, no I/O, safe for the\n * console and the server alike. `./do-not-contact` is deliberately absent —\n * it hashes with `node:crypto` — and is imported by its own path.\n */\nexport * from './sequence-validation'\nexport * from './recipient-country'\nexport * from './gates'\nexport * from './schedule'\nexport * from './sending-capacity'\nexport * from './next-send'\nexport * from './enrollment-state'\nexport * from './compose'\nexport * from './thread-message'\nexport * from './delivery-status'\nexport * from './opt-out-intent'\nexport * from './thread-classification'\nexport * from './mailbox-health'\nexport * from './mailbox-notice'\nexport * from './gateway-block'\nexport * from './do-not-contact-domain'\nexport * from './mailbox-rotation'\nexport * from './manual-reply'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;CAIC,GACD,cAAc,2BAAuB;AACrC,cAAc,yBAAqB;AACnC,cAAc,aAAS;AACvB,cAAc,gBAAY;AAC1B,cAAc,wBAAoB;AAClC,cAAc,iBAAa;AAC3B,cAAc,wBAAoB;AAClC,cAAc,eAAW;AACzB,cAAc,sBAAkB;AAChC,cAAc,uBAAmB;AACjC,cAAc,sBAAkB;AAChC,cAAc,6BAAyB;AACvC,cAAc,sBAAkB;AAChC,cAAc,sBAAkB;AAChC,cAAc,qBAAiB;AAC/B,cAAc,6BAAyB;AACvC,cAAc,wBAAoB;AAClC,cAAc,oBAAgB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type OutreachEnrollment, type OutreachInboundMessage, type OutreachMailbox } from '../model/outreach.types';
|
|
18
|
+
import { type OutreachMessageContext } from './thread-classification';
|
|
19
|
+
import { type OutreachThreadMessage } from './thread-message';
|
|
20
|
+
/** The most a member may write in one answer. */
|
|
21
|
+
export declare const OUTREACH_MANUAL_REPLY_BODY_MAX = 10000;
|
|
22
|
+
/** The most of an answer the enrollment and the record keep: the timeline's excerpt length. */
|
|
23
|
+
export declare const OUTREACH_MANUAL_REPLY_STORED_MAX = 4000;
|
|
24
|
+
/** The display name in a `From` value — `"Ada Lovelace" <ada@example.com>` answers `Ada Lovelace` — or `''`. */
|
|
25
|
+
export declare function outreachDisplayNameOf(value: unknown): string;
|
|
26
|
+
/** Every `Message-ID` a `References` or `In-Reply-To` value names, in header form, in order, once each. */
|
|
27
|
+
export declare function outreachMessageIdList(value: unknown): string[];
|
|
28
|
+
/**
|
|
29
|
+
* The person's own messages among a thread's — see the module note — oldest
|
|
30
|
+
* first. A message with no sender address is left out: it could be neither
|
|
31
|
+
* shown as theirs nor answered.
|
|
32
|
+
*/
|
|
33
|
+
export declare function outreachInboundMessagesOf(messages: readonly OutreachThreadMessage[], context: OutreachMessageContext): OutreachInboundMessage[];
|
|
34
|
+
/**
|
|
35
|
+
* What an enrollment keeps once more of the person's messages are read: one
|
|
36
|
+
* entry per message — a re-read replaces the earlier copy — oldest first,
|
|
37
|
+
* the last {@link OUTREACH_INBOUND_MESSAGES_MAX}.
|
|
38
|
+
*/
|
|
39
|
+
export declare function mergeOutreachInboundMessages(stored: readonly OutreachInboundMessage[] | null | undefined, found: readonly OutreachInboundMessage[]): OutreachInboundMessage[];
|
|
40
|
+
/**
|
|
41
|
+
* The message an answer replies to: the one named, when the enrollment
|
|
42
|
+
* kept it, else the newest the person wrote. `null` when they wrote nothing
|
|
43
|
+
* the enrollment kept.
|
|
44
|
+
*/
|
|
45
|
+
export declare function outreachMessageToAnswer(inbound: readonly OutreachInboundMessage[] | null | undefined, id?: string | null): OutreachInboundMessage | null;
|
|
46
|
+
/** Whether a member may answer this enrollment at all: the person wrote something it kept, or a reply stopped it. */
|
|
47
|
+
export declare function outreachEnrollmentHasInbound(enrollment: Pick<OutreachEnrollment, 'inboundMessages' | 'stopReason' | 'status'>): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The answer's `References` — see the module note: the sequence's own sends,
|
|
50
|
+
* then what the answered message named, then the message itself, each once.
|
|
51
|
+
* A long chain keeps its first id and its latest, as RFC 5322 §3.6.4 allows.
|
|
52
|
+
*/
|
|
53
|
+
export declare function outreachReplyReferences(sentMessageIds: readonly string[], answering: Pick<OutreachInboundMessage, 'references' | 'messageId'>): string[];
|
|
54
|
+
/** `Re:` the subject of the message answered, else of the thread the sequence started. */
|
|
55
|
+
export declare function outreachReplySubject(answering: Pick<OutreachInboundMessage, 'subject'>, enrollmentThreadSubject: string | null | undefined): string;
|
|
56
|
+
/** The answered message beneath the answer, quoted the way mail clients quote: `On …, … wrote:` and `> ` lines. */
|
|
57
|
+
export declare function outreachReplyQuote(answering: Pick<OutreachInboundMessage, 'atMs' | 'from' | 'fromName' | 'body'>, timeZone: string | null | undefined): string;
|
|
58
|
+
/** An answer, written: what the transport sends and the records keep. */
|
|
59
|
+
export interface OutreachManualReply {
|
|
60
|
+
to: string;
|
|
61
|
+
subject: string;
|
|
62
|
+
/** The whole text sent: the member's words, then the quote. */
|
|
63
|
+
text: string;
|
|
64
|
+
/** The member's words alone, bounded, as the records keep them. */
|
|
65
|
+
body: string;
|
|
66
|
+
/** The provider thread it is filed into; `null` lets the provider thread it by its headers. */
|
|
67
|
+
threadId: string | null;
|
|
68
|
+
/** The answered message's `Message-ID`; `null` when it carried none. */
|
|
69
|
+
inReplyTo: string | null;
|
|
70
|
+
references: string[];
|
|
71
|
+
}
|
|
72
|
+
export type OutreachManualReplyErrorCode = 'empty' | 'too_long' | 'invalid_recipient';
|
|
73
|
+
export interface OutreachManualReplyCompose {
|
|
74
|
+
reply: OutreachManualReply | null;
|
|
75
|
+
error: {
|
|
76
|
+
code: OutreachManualReplyErrorCode;
|
|
77
|
+
message: string;
|
|
78
|
+
} | null;
|
|
79
|
+
}
|
|
80
|
+
/** Writes the answer — see the module note. */
|
|
81
|
+
export declare function composeOutreachManualReply(input: {
|
|
82
|
+
enrollment: Pick<OutreachEnrollment, 'messageIds' | 'threadSubject'>;
|
|
83
|
+
answering: OutreachInboundMessage;
|
|
84
|
+
body: unknown;
|
|
85
|
+
/** The mailbox's zone, for the quote's time. */
|
|
86
|
+
timeZone: string | null | undefined;
|
|
87
|
+
}): OutreachManualReplyCompose;
|
|
88
|
+
export type OutreachManualReplyRefusalCode = 'opted-out' | 'bounced' | 'do-not-contact' | 'unchecked' | 'mailbox-unavailable' | 'permission';
|
|
89
|
+
export interface OutreachManualReplyRefusal {
|
|
90
|
+
code: OutreachManualReplyRefusalCode;
|
|
91
|
+
message: string;
|
|
92
|
+
}
|
|
93
|
+
/** The address a mailbox sends as. */
|
|
94
|
+
export declare const outreachMailboxAddress: (mailbox: Pick<OutreachMailbox, "sendAs" | "email">) => string;
|
|
95
|
+
/**
|
|
96
|
+
* Why an answer cannot go now, in the member's words, or `null` — see the
|
|
97
|
+
* module note. The person's own state first, then the lists, then the
|
|
98
|
+
* mailbox, then who may send from it.
|
|
99
|
+
*/
|
|
100
|
+
export declare function outreachManualReplyRefusal(input: {
|
|
101
|
+
enrollment: Pick<OutreachEnrollment, 'status' | 'email'>;
|
|
102
|
+
/** Every address the answer would reach, each looked up. */
|
|
103
|
+
doNotContact: boolean | null;
|
|
104
|
+
doNotContactDomain: boolean | null;
|
|
105
|
+
mailbox: Pick<OutreachMailbox, 'status' | 'sendAs' | 'email'> | null;
|
|
106
|
+
/** The member is the mailbox's own, or an organization owner or admin. */
|
|
107
|
+
mayUseMailbox: boolean;
|
|
108
|
+
}): OutreachManualReplyRefusal | null;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ /*==========================================
|
|
18
|
+
* ANSWERING A PERSON WHO WROTE BACK (AGL-4020).
|
|
19
|
+
*
|
|
20
|
+
* A person who answers a sequence email is answered from the same mailbox,
|
|
21
|
+
* in the same thread, by a member: the reply is a one-to-one email, never a
|
|
22
|
+
* step of the sequence. This module is the engine's half of it — what the
|
|
23
|
+
* person wrote, as the enrollment keeps it, and the answer, as the
|
|
24
|
+
* transport sends it — and decides nothing about storage or delivery.
|
|
25
|
+
*
|
|
26
|
+
* ## What the person wrote
|
|
27
|
+
*
|
|
28
|
+
* {@link outreachInboundMessagesOf} keeps the messages of a thread the
|
|
29
|
+
* classifier reads as theirs: a reply, an automatic answer, an opt-out. Each
|
|
30
|
+
* carries its sender, its time, its `Message-ID` and `References` (what an
|
|
31
|
+
* answer threads by), and its text with the quoted history trimmed, the way
|
|
32
|
+
* the record's timeline keeps it (`emailExcerpt`).
|
|
33
|
+
*
|
|
34
|
+
* ## The answer
|
|
35
|
+
*
|
|
36
|
+
* {@link composeOutreachManualReply} writes it as every mail client writes a
|
|
37
|
+
* reply: `Re:` the subject of the message it answers, `In-Reply-To` that
|
|
38
|
+
* message's `Message-ID`, and `References` the whole chain — the sequence's
|
|
39
|
+
* own sends, what the person's message already named, and the message
|
|
40
|
+
* itself — so it lands in the same thread in the person's mailbox and in
|
|
41
|
+
* ours. Plain text, the member's words first and the message answered quoted
|
|
42
|
+
* beneath. No footer and no `List-Unsubscribe`: the person wrote first, and
|
|
43
|
+
* this is the answer a person would type in their own mail client.
|
|
44
|
+
*
|
|
45
|
+
* ## What still refuses
|
|
46
|
+
*
|
|
47
|
+
* {@link outreachManualReplyRefusal}: an address or a domain on the
|
|
48
|
+
* do-not-contact list, a person who opted out or whose address bounced, and
|
|
49
|
+
* a mailbox that is paused, waiting to be reconnected, or gone. The cold
|
|
50
|
+
* rules — the allowed countries, the company-domain rule, the personal line,
|
|
51
|
+
* the daily cap — do not apply to someone who wrote first, and are not
|
|
52
|
+
* asked.
|
|
53
|
+
*==========================================*/ import { normalizeContactEmail } from "@aglyn/aglyn/app-utils/contacts";
|
|
54
|
+
import { CRM_EMAIL_SUBJECT_MAX } from "@aglyn/aglyn/app-utils/crm";
|
|
55
|
+
import { emailAddressOf, emailExcerpt, threadSubject } from "@aglyn/aglyn/app-utils/email-text";
|
|
56
|
+
import { OUTREACH_INBOUND_KINDS, OUTREACH_INBOUND_MESSAGES_MAX } from "../model/outreach.types.js";
|
|
57
|
+
import { outreachMessageIdHeader } from "./compose.js";
|
|
58
|
+
import { outreachEmailDomain } from "./do-not-contact-domain.js";
|
|
59
|
+
import { classifyOutreachMessage } from "./thread-classification.js";
|
|
60
|
+
import { outreachHeader } from "./thread-message.js";
|
|
61
|
+
/** The most a member may write in one answer. */ export const OUTREACH_MANUAL_REPLY_BODY_MAX = 10000;
|
|
62
|
+
/** The most of an answer the enrollment and the record keep: the timeline's excerpt length. */ export const OUTREACH_MANUAL_REPLY_STORED_MAX = 4000;
|
|
63
|
+
/** The most `Message-ID`s one answer's `References` names: the first, and the latest. */ const REFERENCES_MAX = 20;
|
|
64
|
+
/** The display name in a `From` value — `"Ada Lovelace" <ada@example.com>` answers `Ada Lovelace` — or `''`. */ export function outreachDisplayNameOf(value) {
|
|
65
|
+
const raw = String(value != null ? value : '').trim();
|
|
66
|
+
const angled = raw.lastIndexOf('<');
|
|
67
|
+
if (angled <= 0) return '';
|
|
68
|
+
return raw.slice(0, angled).trim().replace(/^"(.*)"$/, '$1').replace(/\\(.)/g, '$1').trim();
|
|
69
|
+
}
|
|
70
|
+
/** Every `Message-ID` a `References` or `In-Reply-To` value names, in header form, in order, once each. */ export function outreachMessageIdList(value) {
|
|
71
|
+
const found = [];
|
|
72
|
+
for (const match of String(value != null ? value : '').matchAll(/<[^<>\s]+>/g)){
|
|
73
|
+
const id = outreachMessageIdHeader(match[0]);
|
|
74
|
+
if (id && !found.includes(id)) found.push(id);
|
|
75
|
+
}
|
|
76
|
+
return found;
|
|
77
|
+
}
|
|
78
|
+
/** A message's text as the record keeps it: the text part, else the HTML's words, else the preview. */ function bodyOf(message) {
|
|
79
|
+
var _message_textBody, _message_htmlBody, _message_snippet;
|
|
80
|
+
return emailExcerpt((_message_textBody = message.textBody) != null ? _message_textBody : '', (_message_htmlBody = message.htmlBody) != null ? _message_htmlBody : '') || emailExcerpt((_message_snippet = message.snippet) != null ? _message_snippet : '');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The person's own messages among a thread's — see the module note — oldest
|
|
84
|
+
* first. A message with no sender address is left out: it could be neither
|
|
85
|
+
* shown as theirs nor answered.
|
|
86
|
+
*/ export function outreachInboundMessagesOf(messages, context) {
|
|
87
|
+
const kept = [];
|
|
88
|
+
for (const message of messages){
|
|
89
|
+
var _message_threadId, _message_subject;
|
|
90
|
+
if (!(message == null ? void 0 : message.id)) continue;
|
|
91
|
+
const classification = classifyOutreachMessage(message, context);
|
|
92
|
+
if (!OUTREACH_INBOUND_KINDS.includes(classification.kind)) continue;
|
|
93
|
+
const from = emailAddressOf(message.from);
|
|
94
|
+
if (!from) continue;
|
|
95
|
+
const replyTo = emailAddressOf(outreachHeader(message, 'Reply-To'));
|
|
96
|
+
kept.push({
|
|
97
|
+
id: message.id,
|
|
98
|
+
threadId: (_message_threadId = message.threadId) != null ? _message_threadId : null,
|
|
99
|
+
messageId: outreachMessageIdHeader(outreachHeader(message, 'Message-ID')),
|
|
100
|
+
references: outreachMessageIdList(outreachHeader(message, 'References')),
|
|
101
|
+
from,
|
|
102
|
+
fromName: outreachDisplayNameOf(message.from),
|
|
103
|
+
replyTo: replyTo && replyTo !== from ? replyTo : null,
|
|
104
|
+
subject: String((_message_subject = message.subject) != null ? _message_subject : '').replace(/\s+/g, ' ').trim(),
|
|
105
|
+
atMs: classification.atMs,
|
|
106
|
+
kind: classification.kind,
|
|
107
|
+
body: bodyOf(message)
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return kept.sort((a, b)=>a.atMs - b.atMs);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* What an enrollment keeps once more of the person's messages are read: one
|
|
114
|
+
* entry per message — a re-read replaces the earlier copy — oldest first,
|
|
115
|
+
* the last {@link OUTREACH_INBOUND_MESSAGES_MAX}.
|
|
116
|
+
*/ export function mergeOutreachInboundMessages(stored, found) {
|
|
117
|
+
const byId = new Map();
|
|
118
|
+
for (const message of [
|
|
119
|
+
...stored != null ? stored : [],
|
|
120
|
+
...found
|
|
121
|
+
])byId.set(message.id, message);
|
|
122
|
+
return [
|
|
123
|
+
...byId.values()
|
|
124
|
+
].sort((a, b)=>a.atMs - b.atMs).slice(-OUTREACH_INBOUND_MESSAGES_MAX);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The message an answer replies to: the one named, when the enrollment
|
|
128
|
+
* kept it, else the newest the person wrote. `null` when they wrote nothing
|
|
129
|
+
* the enrollment kept.
|
|
130
|
+
*/ export function outreachMessageToAnswer(inbound, id) {
|
|
131
|
+
var _messages_find, _messages_at;
|
|
132
|
+
const messages = [
|
|
133
|
+
...inbound != null ? inbound : []
|
|
134
|
+
].sort((a, b)=>a.atMs - b.atMs);
|
|
135
|
+
if (id) return (_messages_find = messages.find((message)=>message.id === id)) != null ? _messages_find : null;
|
|
136
|
+
return (_messages_at = messages.at(-1)) != null ? _messages_at : null;
|
|
137
|
+
}
|
|
138
|
+
/** Whether a member may answer this enrollment at all: the person wrote something it kept, or a reply stopped it. */ export function outreachEnrollmentHasInbound(enrollment) {
|
|
139
|
+
var _enrollment_inboundMessages;
|
|
140
|
+
return Boolean((_enrollment_inboundMessages = enrollment.inboundMessages) == null ? void 0 : _enrollment_inboundMessages.length) || enrollment.stopReason === 'reply' || enrollment.status === 'replied';
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The answer's `References` — see the module note: the sequence's own sends,
|
|
144
|
+
* then what the answered message named, then the message itself, each once.
|
|
145
|
+
* A long chain keeps its first id and its latest, as RFC 5322 §3.6.4 allows.
|
|
146
|
+
*/ export function outreachReplyReferences(sentMessageIds, answering) {
|
|
147
|
+
const chain = [];
|
|
148
|
+
for (const id of [
|
|
149
|
+
...sentMessageIds,
|
|
150
|
+
...answering.references,
|
|
151
|
+
answering.messageId
|
|
152
|
+
]){
|
|
153
|
+
const header = outreachMessageIdHeader(id);
|
|
154
|
+
if (header && !chain.includes(header)) chain.push(header);
|
|
155
|
+
}
|
|
156
|
+
return chain.length > REFERENCES_MAX ? [
|
|
157
|
+
chain[0],
|
|
158
|
+
...chain.slice(-(REFERENCES_MAX - 1))
|
|
159
|
+
] : chain;
|
|
160
|
+
}
|
|
161
|
+
/** `Re:` the subject of the message answered, else of the thread the sequence started. */ export function outreachReplySubject(answering, enrollmentThreadSubject) {
|
|
162
|
+
const base = threadSubject(answering.subject) || threadSubject(enrollmentThreadSubject);
|
|
163
|
+
return (base ? `Re: ${base}` : 'Re:').slice(0, CRM_EMAIL_SUBJECT_MAX);
|
|
164
|
+
}
|
|
165
|
+
/** "Tue, Oct 21, 2026, 8:02 AM", in the mailbox's zone. */ function quotedTime(atMs, timeZone) {
|
|
166
|
+
const options = {
|
|
167
|
+
weekday: 'short',
|
|
168
|
+
month: 'short',
|
|
169
|
+
day: 'numeric',
|
|
170
|
+
year: 'numeric',
|
|
171
|
+
hour: 'numeric',
|
|
172
|
+
minute: '2-digit'
|
|
173
|
+
};
|
|
174
|
+
// Plain spaces: newer ICU puts a narrow no-break space before "AM".
|
|
175
|
+
const format = (zone)=>new Intl.DateTimeFormat('en-US', _extends({}, options, {
|
|
176
|
+
timeZone: zone
|
|
177
|
+
})).format(atMs).replace(/[\u00a0\u202f]/g, ' ');
|
|
178
|
+
try {
|
|
179
|
+
return format(timeZone || 'UTC');
|
|
180
|
+
} catch (unused) {
|
|
181
|
+
return format('UTC');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** The answered message beneath the answer, quoted the way mail clients quote: `On …, … wrote:` and `> ` lines. */ export function outreachReplyQuote(answering, timeZone) {
|
|
185
|
+
const who = answering.fromName ? `${answering.fromName} <${answering.from}>` : answering.from;
|
|
186
|
+
const lines = answering.body.split('\n').map((line)=>line ? `> ${line}` : '>');
|
|
187
|
+
return [
|
|
188
|
+
`On ${quotedTime(answering.atMs, timeZone)}, ${who} wrote:`,
|
|
189
|
+
...lines
|
|
190
|
+
].join('\n');
|
|
191
|
+
}
|
|
192
|
+
/** Writes the answer — see the module note. */ export function composeOutreachManualReply(input) {
|
|
193
|
+
var _input_body, _input_answering_replyTo, _input_enrollment_messageIds;
|
|
194
|
+
const words = String((_input_body = input.body) != null ? _input_body : '').replace(/\r\n?/g, '\n').replace(/[ \t]+$/gm, '').replace(/^\n+|\n+$/g, '');
|
|
195
|
+
if (!words.trim()) return {
|
|
196
|
+
reply: null,
|
|
197
|
+
error: {
|
|
198
|
+
code: 'empty',
|
|
199
|
+
message: 'Write your answer before sending it.'
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
if (words.length > OUTREACH_MANUAL_REPLY_BODY_MAX) {
|
|
203
|
+
return {
|
|
204
|
+
reply: null,
|
|
205
|
+
error: {
|
|
206
|
+
code: 'too_long',
|
|
207
|
+
message: `An answer can be at most ${OUTREACH_MANUAL_REPLY_BODY_MAX.toLocaleString('en-US')} characters.`
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const to = normalizeContactEmail((_input_answering_replyTo = input.answering.replyTo) != null ? _input_answering_replyTo : input.answering.from);
|
|
212
|
+
if (!to) {
|
|
213
|
+
return {
|
|
214
|
+
reply: null,
|
|
215
|
+
error: {
|
|
216
|
+
code: 'invalid_recipient',
|
|
217
|
+
message: 'Their message names no address to answer.'
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
reply: {
|
|
223
|
+
to,
|
|
224
|
+
subject: outreachReplySubject(input.answering, input.enrollment.threadSubject),
|
|
225
|
+
text: `${words}\n\n${outreachReplyQuote(input.answering, input.timeZone)}`,
|
|
226
|
+
body: words.slice(0, OUTREACH_MANUAL_REPLY_STORED_MAX),
|
|
227
|
+
threadId: input.answering.threadId,
|
|
228
|
+
inReplyTo: input.answering.messageId,
|
|
229
|
+
references: outreachReplyReferences((_input_enrollment_messageIds = input.enrollment.messageIds) != null ? _input_enrollment_messageIds : [], input.answering)
|
|
230
|
+
},
|
|
231
|
+
error: null
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/** The address a mailbox sends as. */ export const outreachMailboxAddress = (mailbox)=>mailbox.sendAs || mailbox.email;
|
|
235
|
+
/**
|
|
236
|
+
* Why an answer cannot go now, in the member's words, or `null` — see the
|
|
237
|
+
* module note. The person's own state first, then the lists, then the
|
|
238
|
+
* mailbox, then who may send from it.
|
|
239
|
+
*/ export function outreachManualReplyRefusal(input) {
|
|
240
|
+
const { enrollment, mailbox } = input;
|
|
241
|
+
if (enrollment.status === 'opted_out') {
|
|
242
|
+
return {
|
|
243
|
+
code: 'opted-out',
|
|
244
|
+
message: 'They asked not to be emailed, so no answer can be sent to them.'
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (enrollment.status === 'bounced') {
|
|
248
|
+
return {
|
|
249
|
+
code: 'bounced',
|
|
250
|
+
message: 'Their address bounced, so an answer would not reach them.'
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (input.doNotContact === true) {
|
|
254
|
+
return {
|
|
255
|
+
code: 'do-not-contact',
|
|
256
|
+
message: `${enrollment.email} is on your do-not-contact list. Take it off the list on the Compliance page to answer them.`
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (input.doNotContactDomain === true) {
|
|
260
|
+
var _outreachEmailDomain;
|
|
261
|
+
const domain = (_outreachEmailDomain = outreachEmailDomain(enrollment.email)) != null ? _outreachEmailDomain : 'Their domain';
|
|
262
|
+
return {
|
|
263
|
+
code: 'do-not-contact',
|
|
264
|
+
message: `${domain} is on your do-not-contact list. Take it off the list on the Compliance page to answer them.`
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (input.doNotContact === null || input.doNotContactDomain === null) {
|
|
268
|
+
return {
|
|
269
|
+
code: 'unchecked',
|
|
270
|
+
message: 'The do-not-contact list could not be checked. Try again in a moment.'
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
if (!mailbox) {
|
|
274
|
+
return {
|
|
275
|
+
code: 'mailbox-unavailable',
|
|
276
|
+
message: 'The mailbox that wrote to them is no longer connected, so the answer cannot go from it.'
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
const address = outreachMailboxAddress(mailbox);
|
|
280
|
+
if (mailbox.status === 'paused') {
|
|
281
|
+
return {
|
|
282
|
+
code: 'mailbox-unavailable',
|
|
283
|
+
message: `${address} is paused. Resume it on the Mailboxes page to answer from it.`
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
if (mailbox.status !== 'connected') {
|
|
287
|
+
return {
|
|
288
|
+
code: 'mailbox-unavailable',
|
|
289
|
+
message: `${address} needs to be connected again. Reconnect it on the Mailboxes page to answer from it.`
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (!input.mayUseMailbox) {
|
|
293
|
+
return {
|
|
294
|
+
code: 'permission',
|
|
295
|
+
message: `Only the member who connected ${address}, or an organization owner or admin, can answer from it.`
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
//# sourceMappingURL=manual-reply.js.map
|