@aglyn/plugins-outreach 1.0.0-beta.195 → 1.0.0-beta.197
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -14
- package/src/lib/components/curate-step-dialog.d.ts +50 -0
- package/src/lib/components/curate-step-dialog.js +235 -0
- package/src/lib/components/curate-step-dialog.js.map +1 -0
- package/src/lib/components/curated-step-editor.d.ts +80 -0
- package/src/lib/components/curated-step-editor.js +255 -0
- package/src/lib/components/curated-step-editor.js.map +1 -0
- package/src/lib/components/enroll-dialog.d.ts +15 -0
- package/src/lib/components/enroll-dialog.js +214 -14
- package/src/lib/components/enroll-dialog.js.map +1 -1
- package/src/lib/components/enrollments-table.d.ts +2 -0
- package/src/lib/components/enrollments-table.js +62 -8
- package/src/lib/components/enrollments-table.js.map +1 -1
- package/src/lib/components/mailbox-card.js +11 -2
- package/src/lib/components/mailbox-card.js.map +1 -1
- package/src/lib/components/sequence-detail.js +1 -0
- package/src/lib/components/sequence-detail.js.map +1 -1
- package/src/lib/components/sequence-editor.d.ts +2 -0
- package/src/lib/components/sequence-editor.js +26 -2
- package/src/lib/components/sequence-editor.js.map +1 -1
- package/src/lib/components/sequence-step-card.d.ts +7 -0
- package/src/lib/components/sequence-step-card.js +37 -14
- package/src/lib/components/sequence-step-card.js.map +1 -1
- package/src/lib/components/sequences-section.js +12 -3
- package/src/lib/components/sequences-section.js.map +1 -1
- package/src/lib/components/step-test-dialog.d.ts +31 -0
- package/src/lib/components/step-test-dialog.js +247 -0
- package/src/lib/components/step-test-dialog.js.map +1 -0
- package/src/lib/components/use-outreach-api.d.ts +15 -1
- package/src/lib/components/use-outreach-api.js +16 -0
- package/src/lib/components/use-outreach-api.js.map +1 -1
- package/src/lib/components/use-outreach-data.d.ts +6 -1
- package/src/lib/components/use-outreach-data.js +6 -3
- package/src/lib/components/use-outreach-data.js.map +1 -1
- package/src/lib/constants/api-routes.d.ts +15 -0
- package/src/lib/constants/api-routes.js +12 -0
- package/src/lib/constants/api-routes.js.map +1 -1
- package/src/lib/engine/compose.d.ts +12 -2
- package/src/lib/engine/compose.js +32 -6
- package/src/lib/engine/compose.js.map +1 -1
- package/src/lib/engine/curation-prompt.d.ts +73 -0
- package/src/lib/engine/curation-prompt.js +151 -0
- package/src/lib/engine/curation-prompt.js.map +1 -0
- package/src/lib/engine/enrollment-activity.d.ts +24 -0
- package/src/lib/engine/enrollment-activity.js +25 -0
- package/src/lib/engine/enrollment-activity.js.map +1 -1
- package/src/lib/engine/sequence-validation.d.ts +10 -1
- package/src/lib/engine/sequence-validation.js +54 -0
- package/src/lib/engine/sequence-validation.js.map +1 -1
- package/src/lib/enrollment/step-overrides.d.ts +53 -0
- package/src/lib/enrollment/step-overrides.js +140 -0
- package/src/lib/enrollment/step-overrides.js.map +1 -0
- package/src/lib/model/outreach-api.d.ts +89 -2
- package/src/lib/model/outreach-api.js.map +1 -1
- package/src/lib/model/outreach.types.d.ts +42 -0
- package/src/lib/model/outreach.types.js +18 -0
- package/src/lib/model/outreach.types.js.map +1 -1
- package/src/lib/model/step-preview.d.ts +10 -2
- package/src/lib/model/step-preview.js +9 -5
- package/src/lib/model/step-preview.js.map +1 -1
- package/src/lib/model/stored-records.d.ts +9 -1
- package/src/lib/model/stored-records.js +47 -2
- package/src/lib/model/stored-records.js.map +1 -1
- package/src/lib/routes/curate-routes.d.ts +63 -0
- package/src/lib/routes/curate-routes.js +424 -0
- package/src/lib/routes/curate-routes.js.map +1 -0
- package/src/lib/routes/enroll-routes.d.ts +7 -0
- package/src/lib/routes/enroll-routes.js +58 -4
- package/src/lib/routes/enroll-routes.js.map +1 -1
- package/src/lib/routes/platform-deps.d.ts +2 -2
- package/src/lib/routes/platform-deps.js +2 -2
- package/src/lib/routes/platform-deps.js.map +1 -1
- package/src/lib/routes/preview-routes.d.ts +24 -0
- package/src/lib/routes/preview-routes.js +103 -85
- package/src/lib/routes/preview-routes.js.map +1 -1
- package/src/lib/routes/register-routes.d.ts +7 -0
- package/src/lib/routes/register-routes.js +41 -0
- package/src/lib/routes/register-routes.js.map +1 -1
- package/src/lib/routes/step-test-routes.d.ts +88 -0
- package/src/lib/routes/step-test-routes.js +275 -0
- package/src/lib/routes/step-test-routes.js.map +1 -0
- package/src/lib/runtime/click-events.js +10 -0
- package/src/lib/runtime/click-events.js.map +1 -1
- package/src/lib/runtime/click-link.d.ts +11 -0
- package/src/lib/runtime/click-link.js +9 -4
- package/src/lib/runtime/click-link.js.map +1 -1
- package/src/lib/runtime/click-route.js +3 -0
- package/src/lib/runtime/click-route.js.map +1 -1
- package/src/lib/runtime/send-job.js +2 -0
- package/src/lib/runtime/send-job.js.map +1 -1
|
@@ -0,0 +1,140 @@
|
|
|
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
|
+
* WHAT A REQUEST MAY STORE AS ONE PERSON'S COPY OF A STEP (AGL-3324).
|
|
19
|
+
*
|
|
20
|
+
* The enroll route, the preview route and the curate-save route all take
|
|
21
|
+
* the console's `OutreachStepOverrideRequest`, and all three read it the
|
|
22
|
+
* same way: a step index that names an EMAIL step of the sequence, a body,
|
|
23
|
+
* a subject only where the email starts a thread, and the validator's
|
|
24
|
+
* verdict on each. A request that names a task step, a step the sequence
|
|
25
|
+
* does not have, or the same step twice is refused whole — nothing here is
|
|
26
|
+
* guessed, because what is read here is what gets sent.
|
|
27
|
+
*
|
|
28
|
+
* The prompt and the model ride through untouched, for the audit log; who
|
|
29
|
+
* confirmed and when are the route's to stamp, never the request's to
|
|
30
|
+
* claim.
|
|
31
|
+
*/ import { CRM_EMAIL_BODY_MAX } from "@aglyn/aglyn/app-utils/crm";
|
|
32
|
+
import { hasOutreachValidationErrors, isInThreadEmailStep, validateOutreachCuratedStep } from "../engine/sequence-validation.js";
|
|
33
|
+
import { OUTREACH_STEP_OVERRIDE_SOURCES } from "../model/outreach.types.js";
|
|
34
|
+
/** The most of a prompt an override keeps for the audit log. */ export const OUTREACH_OVERRIDE_PROMPT_MAX = 12000;
|
|
35
|
+
/**
|
|
36
|
+
* One override request as a stored override, before the caller's stamp.
|
|
37
|
+
* `null` when the request is not one.
|
|
38
|
+
*/ export function readOutreachStepOverrideRequest(raw, steps) {
|
|
39
|
+
if (!raw || typeof raw !== 'object') return {
|
|
40
|
+
refusal: 'A curated step is an object.'
|
|
41
|
+
};
|
|
42
|
+
const entry = raw;
|
|
43
|
+
const stepIndex = Number(entry['stepIndex']);
|
|
44
|
+
const step = Number.isInteger(stepIndex) && stepIndex >= 0 ? steps[stepIndex] : undefined;
|
|
45
|
+
if ((step == null ? void 0 : step.kind) !== 'email') {
|
|
46
|
+
return {
|
|
47
|
+
refusal: 'A curated step names one of the sequence’s email steps.'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const startsThread = !isInThreadEmailStep(steps, stepIndex);
|
|
51
|
+
const body = typeof entry['body'] === 'string' ? entry['body'].replace(/\r\n?/g, '\n') : '';
|
|
52
|
+
const subject = startsThread && typeof entry['subject'] === 'string' ? entry['subject'] : undefined;
|
|
53
|
+
const source = entry['source'];
|
|
54
|
+
if (!OUTREACH_STEP_OVERRIDE_SOURCES.includes(source)) {
|
|
55
|
+
return {
|
|
56
|
+
refusal: 'A curated step says who wrote it: the AI, or the member.'
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const issues = validateOutreachCuratedStep({
|
|
60
|
+
subject,
|
|
61
|
+
body,
|
|
62
|
+
startsThread
|
|
63
|
+
}, `stepOverrides.${stepIndex}`);
|
|
64
|
+
const prompt = typeof entry['prompt'] === 'string' ? entry['prompt'].slice(0, OUTREACH_OVERRIDE_PROMPT_MAX) : '';
|
|
65
|
+
const model = typeof entry['model'] === 'string' ? entry['model'].trim().slice(0, 200) : '';
|
|
66
|
+
return {
|
|
67
|
+
stepIndex,
|
|
68
|
+
issues,
|
|
69
|
+
override: _extends({}, subject !== undefined ? {
|
|
70
|
+
subject: subject.replace(/\s+/g, ' ').trim()
|
|
71
|
+
} : {}, {
|
|
72
|
+
body: body.slice(0, CRM_EMAIL_BODY_MAX),
|
|
73
|
+
source: source
|
|
74
|
+
}, source === 'ai' && entry['edited'] === true ? {
|
|
75
|
+
edited: true
|
|
76
|
+
} : {}, source === 'ai' && prompt ? {
|
|
77
|
+
prompt
|
|
78
|
+
} : {}, source === 'ai' && model ? {
|
|
79
|
+
model
|
|
80
|
+
} : {})
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Every override a request carries, read and validated, and stamped with
|
|
85
|
+
* who confirmed them and when. An empty or absent list reads as no
|
|
86
|
+
* overrides and no issues.
|
|
87
|
+
*/ export function readOutreachStepOverrideRequests(raw, steps, stamp) {
|
|
88
|
+
const list = Array.isArray(raw) ? raw : [];
|
|
89
|
+
const overrides = {};
|
|
90
|
+
const issues = [];
|
|
91
|
+
for (const entry of list){
|
|
92
|
+
const read = readOutreachStepOverrideRequest(entry, steps);
|
|
93
|
+
if ('refusal' in read) return {
|
|
94
|
+
overrides: {},
|
|
95
|
+
issues: [],
|
|
96
|
+
refusal: read.refusal
|
|
97
|
+
};
|
|
98
|
+
if (overrides[String(read.stepIndex)]) {
|
|
99
|
+
return {
|
|
100
|
+
overrides: {},
|
|
101
|
+
issues: [],
|
|
102
|
+
refusal: 'A step is curated once per person.'
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
issues.push(...read.issues);
|
|
106
|
+
overrides[String(read.stepIndex)] = _extends({}, read.override, {
|
|
107
|
+
draftedAtMs: stamp.nowMs,
|
|
108
|
+
draftedByUid: stamp.uid
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
overrides,
|
|
113
|
+
issues,
|
|
114
|
+
refusal: null
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Whether a read carries an error that keeps its overrides from being stored. */ export function outreachStepOverridesRefused(read) {
|
|
118
|
+
return read.refusal !== null || hasOutreachValidationErrors(read.issues);
|
|
119
|
+
}
|
|
120
|
+
/** The requests' shape, for a caller that builds one from stored overrides. */ export function outreachStepOverrideRequests(overrides) {
|
|
121
|
+
return Object.entries(overrides != null ? overrides : {}).map(([key, override])=>{
|
|
122
|
+
var _override_body;
|
|
123
|
+
return _extends({
|
|
124
|
+
stepIndex: Number(key)
|
|
125
|
+
}, typeof override.subject === 'string' ? {
|
|
126
|
+
subject: override.subject
|
|
127
|
+
} : {}, {
|
|
128
|
+
body: (_override_body = override.body) != null ? _override_body : '',
|
|
129
|
+
source: override.source
|
|
130
|
+
}, override.edited ? {
|
|
131
|
+
edited: true
|
|
132
|
+
} : {}, override.prompt ? {
|
|
133
|
+
prompt: override.prompt
|
|
134
|
+
} : {}, override.model ? {
|
|
135
|
+
model: override.model
|
|
136
|
+
} : {});
|
|
137
|
+
}).sort((a, b)=>a.stepIndex - b.stepIndex);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
//# sourceMappingURL=step-overrides.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/enrollment/step-overrides.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * WHAT A REQUEST MAY STORE AS ONE PERSON'S COPY OF A STEP (AGL-3324).\n *\n * The enroll route, the preview route and the curate-save route all take\n * the console's `OutreachStepOverrideRequest`, and all three read it the\n * same way: a step index that names an EMAIL step of the sequence, a body,\n * a subject only where the email starts a thread, and the validator's\n * verdict on each. A request that names a task step, a step the sequence\n * does not have, or the same step twice is refused whole — nothing here is\n * guessed, because what is read here is what gets sent.\n *\n * The prompt and the model ride through untouched, for the audit log; who\n * confirmed and when are the route's to stamp, never the request's to\n * claim.\n */\n\nimport { CRM_EMAIL_BODY_MAX } from '@aglyn/aglyn/app-utils/crm'\nimport {\n hasOutreachValidationErrors,\n isInThreadEmailStep,\n validateOutreachCuratedStep,\n type OutreachValidationIssue,\n} from '../engine/sequence-validation'\nimport type { OutreachStepOverrideRequest } from '../model/outreach-api'\nimport {\n OUTREACH_STEP_OVERRIDE_SOURCES,\n type OutreachSequenceStep,\n type OutreachStepOverride,\n type OutreachStepOverrides,\n} from '../model/outreach.types'\n\n/** The most of a prompt an override keeps for the audit log. */\nexport const OUTREACH_OVERRIDE_PROMPT_MAX = 12_000\n\nexport interface OutreachStepOverrideRead {\n /** The overrides as they would be stored, keyed by step index, stamped by the caller. */\n overrides: OutreachStepOverrides\n /** Every issue, each path prefixed `stepOverrides.<index>`. */\n issues: OutreachValidationIssue[]\n /** Why the request could not be read at all, in a sentence; `null` when it could. */\n refusal: string | null\n}\n\n/**\n * One override request as a stored override, before the caller's stamp.\n * `null` when the request is not one.\n */\nexport function readOutreachStepOverrideRequest(\n raw: unknown,\n steps: readonly OutreachSequenceStep[],\n): { stepIndex: number; override: Omit<OutreachStepOverride, 'draftedAtMs' | 'draftedByUid'>; issues: OutreachValidationIssue[] } | { refusal: string } {\n if (!raw || typeof raw !== 'object') return { refusal: 'A curated step is an object.' }\n const entry = raw as Record<string, unknown>\n const stepIndex = Number(entry['stepIndex'])\n const step = Number.isInteger(stepIndex) && stepIndex >= 0 ? steps[stepIndex] : undefined\n if (step?.kind !== 'email') {\n return { refusal: 'A curated step names one of the sequence’s email steps.' }\n }\n const startsThread = !isInThreadEmailStep(steps, stepIndex)\n const body = typeof entry['body'] === 'string' ? entry['body'].replace(/\\r\\n?/g, '\\n') : ''\n const subject = startsThread && typeof entry['subject'] === 'string' ? entry['subject'] : undefined\n const source = entry['source']\n if (!(OUTREACH_STEP_OVERRIDE_SOURCES as readonly unknown[]).includes(source)) {\n return { refusal: 'A curated step says who wrote it: the AI, or the member.' }\n }\n const issues = validateOutreachCuratedStep({ subject, body, startsThread }, `stepOverrides.${stepIndex}`)\n const prompt = typeof entry['prompt'] === 'string' ? entry['prompt'].slice(0, OUTREACH_OVERRIDE_PROMPT_MAX) : ''\n const model = typeof entry['model'] === 'string' ? entry['model'].trim().slice(0, 200) : ''\n return {\n stepIndex,\n issues,\n override: {\n ...(subject !== undefined ? { subject: subject.replace(/\\s+/g, ' ').trim() } : {}),\n body: body.slice(0, CRM_EMAIL_BODY_MAX),\n source: source as OutreachStepOverride['source'],\n ...(source === 'ai' && entry['edited'] === true ? { edited: true } : {}),\n ...(source === 'ai' && prompt ? { prompt } : {}),\n ...(source === 'ai' && model ? { model } : {}),\n },\n }\n}\n\n/**\n * Every override a request carries, read and validated, and stamped with\n * who confirmed them and when. An empty or absent list reads as no\n * overrides and no issues.\n */\nexport function readOutreachStepOverrideRequests(\n raw: unknown,\n steps: readonly OutreachSequenceStep[],\n stamp: { uid: string; nowMs: number },\n): OutreachStepOverrideRead {\n const list = Array.isArray(raw) ? (raw as unknown[]) : []\n const overrides: OutreachStepOverrides = {}\n const issues: OutreachValidationIssue[] = []\n for (const entry of list) {\n const read = readOutreachStepOverrideRequest(entry, steps)\n if ('refusal' in read) return { overrides: {}, issues: [], refusal: read.refusal }\n if (overrides[String(read.stepIndex)]) {\n return { overrides: {}, issues: [], refusal: 'A step is curated once per person.' }\n }\n issues.push(...read.issues)\n overrides[String(read.stepIndex)] = {\n ...read.override,\n draftedAtMs: stamp.nowMs,\n draftedByUid: stamp.uid,\n }\n }\n return { overrides, issues, refusal: null }\n}\n\n/** Whether a read carries an error that keeps its overrides from being stored. */\nexport function outreachStepOverridesRefused(read: OutreachStepOverrideRead): boolean {\n return read.refusal !== null || hasOutreachValidationErrors(read.issues)\n}\n\n/** The requests' shape, for a caller that builds one from stored overrides. */\nexport function outreachStepOverrideRequests(\n overrides: OutreachStepOverrides | null | undefined,\n): OutreachStepOverrideRequest[] {\n return Object.entries(overrides ?? {})\n .map(([key, override]) => ({\n stepIndex: Number(key),\n ...(typeof override.subject === 'string' ? { subject: override.subject } : {}),\n body: override.body ?? '',\n source: override.source,\n ...(override.edited ? { edited: true } : {}),\n ...(override.prompt ? { prompt: override.prompt } : {}),\n ...(override.model ? { model: override.model } : {}),\n }))\n .sort((a, b) => a.stepIndex - b.stepIndex)\n}\n"],"names":["CRM_EMAIL_BODY_MAX","hasOutreachValidationErrors","isInThreadEmailStep","validateOutreachCuratedStep","OUTREACH_STEP_OVERRIDE_SOURCES","OUTREACH_OVERRIDE_PROMPT_MAX","readOutreachStepOverrideRequest","raw","steps","refusal","entry","stepIndex","Number","step","isInteger","undefined","kind","startsThread","body","replace","subject","source","includes","issues","prompt","slice","model","trim","override","edited","readOutreachStepOverrideRequests","stamp","list","Array","isArray","overrides","read","String","push","draftedAtMs","nowMs","draftedByUid","uid","outreachStepOverridesRefused","outreachStepOverrideRequests","Object","entries","map","key","sort","a","b"],"mappings":"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"}
|
|
@@ -24,11 +24,12 @@
|
|
|
24
24
|
* surface that names no site.
|
|
25
25
|
*/
|
|
26
26
|
import type { OutreachComposeError } from '../engine/compose';
|
|
27
|
+
import type { OutreachValidationIssue } from '../engine/sequence-validation';
|
|
27
28
|
import type { OutreachComplianceIssue } from './compliance-settings';
|
|
28
|
-
import type { OutreachAttestationKind, OutreachComplianceSettingsDocument, OutreachDoNotContactDomainEntry, OutreachEnrollment, OutreachSequence } from './outreach.types';
|
|
29
|
+
import type { OutreachAttestationKind, OutreachComplianceSettingsDocument, OutreachDoNotContactDomainEntry, OutreachEnrollment, OutreachSequence, OutreachStepOverrideSource } from './outreach.types';
|
|
29
30
|
import type { OutreachSequenceDraft, OutreachSequenceIssue } from './sequence-draft';
|
|
30
31
|
/** Every reason these routes refuse with, for a caller to branch on. */
|
|
31
|
-
export type OutreachRouteRefusalReason = 'unauthenticated' | 'email-unverified' | 'org-required' | 'not-a-member' | 'not-org-wide' | 'permission' | 'entitlement' | 'method-not-allowed' | 'invalid-request' | 'invalid-domain' | 'invalid-settings' | 'invalid-sequence' | 'sequence-not-found' | 'sequence-archived' | 'sequence-not-active' | 'activation-refused' | 'delete-refused' | 'mailbox-unavailable' | 'enrollment-not-found' | 'transition-refused' | 'view-not-found' | 'view-unsupported' | 'contact-not-found' | 'link-domain-refused';
|
|
32
|
+
export type OutreachRouteRefusalReason = 'unauthenticated' | 'email-unverified' | 'org-required' | 'not-a-member' | 'not-org-wide' | 'permission' | 'entitlement' | 'method-not-allowed' | 'invalid-request' | 'invalid-domain' | 'invalid-settings' | 'invalid-sequence' | 'sequence-not-found' | 'sequence-archived' | 'sequence-not-active' | 'activation-refused' | 'delete-refused' | 'mailbox-unavailable' | 'enrollment-not-found' | 'transition-refused' | 'view-not-found' | 'view-unsupported' | 'contact-not-found' | 'link-domain-refused' | 'rate-limited' | 'google-unavailable' | 'curation-unavailable' | 'curation-refused' | 'invalid-override';
|
|
32
33
|
/** A refusal, in the one shape every route answers with. */
|
|
33
34
|
export interface OutreachRouteRefusal {
|
|
34
35
|
/** A sentence written for the person reading it. */
|
|
@@ -166,12 +167,32 @@ export interface OutreachEnrollPreviewResponse {
|
|
|
166
167
|
/** True when the source named more people than one enrollment reads. */
|
|
167
168
|
truncated: boolean;
|
|
168
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* One person's confirmed copy of one step, as the console sends it
|
|
172
|
+
* (AGL-3324): what the member read and ticked the confirmation for. The
|
|
173
|
+
* route validates it again and stamps who and when.
|
|
174
|
+
*/
|
|
175
|
+
export interface OutreachStepOverrideRequest {
|
|
176
|
+
stepIndex: number;
|
|
177
|
+
/** Read only where the email starts a thread. */
|
|
178
|
+
subject?: string;
|
|
179
|
+
body: string;
|
|
180
|
+
source: OutreachStepOverrideSource;
|
|
181
|
+
/** An AI draft the member changed before confirming it. */
|
|
182
|
+
edited?: boolean;
|
|
183
|
+
/** The prompt the AI was given, returned by the draft route, for the audit log. */
|
|
184
|
+
prompt?: string;
|
|
185
|
+
/** The model that drafted it, returned by the draft route. */
|
|
186
|
+
model?: string;
|
|
187
|
+
}
|
|
169
188
|
/** One person to enroll, with what the rep supplied for them: a contact by id, or a lead by key. */
|
|
170
189
|
export interface OutreachEnrollPersonRequest {
|
|
171
190
|
contactId?: string;
|
|
172
191
|
leadId?: string;
|
|
173
192
|
personalLine?: string;
|
|
174
193
|
attestations?: OutreachAttestationKind[];
|
|
194
|
+
/** The steps the member curated for this person before enrolling them (AGL-3324). */
|
|
195
|
+
stepOverrides?: OutreachStepOverrideRequest[];
|
|
175
196
|
}
|
|
176
197
|
/** `POST outreach/enroll` — the gates are checked again, here, before anything is written. */
|
|
177
198
|
export interface OutreachEnrollRequest {
|
|
@@ -295,6 +316,8 @@ export interface OutreachPreviewRequest {
|
|
|
295
316
|
personalLine?: string;
|
|
296
317
|
/** Which step; the first email when absent. */
|
|
297
318
|
stepIndex?: number;
|
|
319
|
+
/** The person's copies of steps as the dialog holds them (AGL-3324), so the preview shows the curated version. */
|
|
320
|
+
stepOverrides?: OutreachStepOverrideRequest[];
|
|
298
321
|
}
|
|
299
322
|
export interface OutreachPreviewResponse {
|
|
300
323
|
ok: true;
|
|
@@ -306,4 +329,68 @@ export interface OutreachPreviewResponse {
|
|
|
306
329
|
/** Why it could not be written, when it could not. */
|
|
307
330
|
error: OutreachComposeError | null;
|
|
308
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* `POST outreach/steps/test` — one step of a saved sequence, rendered
|
|
334
|
+
* exactly as `outreach/preview` renders it (the same person, personal
|
|
335
|
+
* line and step) and sent through the sequence's mailbox to the member.
|
|
336
|
+
*/
|
|
337
|
+
export interface OutreachStepTestRequest extends OutreachPreviewRequest {
|
|
338
|
+
/** Where the test goes; the member's own address when absent or empty. */
|
|
339
|
+
to?: string;
|
|
340
|
+
}
|
|
341
|
+
export interface OutreachStepTestResponse {
|
|
342
|
+
ok: true;
|
|
343
|
+
stepIndex: number;
|
|
344
|
+
/** The address the test went to. */
|
|
345
|
+
sentTo: string;
|
|
346
|
+
/** The subject as sent, `[Test]` prefix included. */
|
|
347
|
+
subject: string;
|
|
348
|
+
sentAtMs: number;
|
|
349
|
+
/** How many tests the mailbox has sent on its local day, this one included. */
|
|
350
|
+
testsToday: number;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* `POST outreach/curate/draft` — this person's own copies of the sequence's
|
|
354
|
+
* email steps, drafted by the workspace's AI. Names EITHER a person about to
|
|
355
|
+
* be enrolled (the sequence, a contact or a lead, the personal line the rep
|
|
356
|
+
* is writing) OR an enrollment, whose next email step is drafted. Nothing is
|
|
357
|
+
* stored: the answer is what the member reads before confirming.
|
|
358
|
+
*/
|
|
359
|
+
export interface OutreachCurateDraftRequest {
|
|
360
|
+
orgId: string;
|
|
361
|
+
sequenceId?: string;
|
|
362
|
+
contactId?: string;
|
|
363
|
+
leadId?: string;
|
|
364
|
+
personalLine?: string;
|
|
365
|
+
enrollmentId?: string;
|
|
366
|
+
/** Which email steps; every email step for a person to enroll, the next one for an enrollment, when absent. */
|
|
367
|
+
stepIndexes?: number[];
|
|
368
|
+
}
|
|
369
|
+
/** One drafted step, with what the validator said about it. */
|
|
370
|
+
export interface OutreachCurateDraft {
|
|
371
|
+
stepIndex: number;
|
|
372
|
+
/** `null` for an in-thread email, which is sent as `Re:` the thread's subject. */
|
|
373
|
+
subject: string | null;
|
|
374
|
+
body: string;
|
|
375
|
+
/** Errors and warnings; a draft with an error cannot be confirmed as it stands. */
|
|
376
|
+
issues: OutreachValidationIssue[];
|
|
377
|
+
}
|
|
378
|
+
export interface OutreachCurateDraftResponse {
|
|
379
|
+
ok: true;
|
|
380
|
+
drafts: OutreachCurateDraft[];
|
|
381
|
+
/** The prompt the AI was given and the model that answered, for the override's audit record. */
|
|
382
|
+
prompt: string;
|
|
383
|
+
model: string;
|
|
384
|
+
}
|
|
385
|
+
/** `POST outreach/curate/save` — stores a confirmed copy of one step on an enrollment, or clears it with `override: null`. */
|
|
386
|
+
export interface OutreachCurateSaveRequest {
|
|
387
|
+
orgId: string;
|
|
388
|
+
enrollmentId: string;
|
|
389
|
+
stepIndex: number;
|
|
390
|
+
override: Omit<OutreachStepOverrideRequest, 'stepIndex'> | null;
|
|
391
|
+
}
|
|
392
|
+
export interface OutreachCurateSaveResponse {
|
|
393
|
+
ok: true;
|
|
394
|
+
enrollment: OutreachEnrollment;
|
|
395
|
+
}
|
|
309
396
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/model/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\n/**\n * THE CONTRACT OF OUTREACH'S SETTINGS, SEQUENCE AND ENROLLMENT ROUTES\n * (AGL-2980): what the console sends, what the routes answer, and every\n * reason one refuses. Client-safe — types and the batch limit only.\n *\n * Every request names its organization (`orgId`, in the query of a `GET`\n * and the JSON body of a `POST`): the routes serve an organization-level\n * surface that names no site.\n */\n\nimport type { OutreachComposeError } from '../engine/compose'\nimport type { OutreachComplianceIssue } from './compliance-settings'\nimport type {\n OutreachAttestationKind,\n OutreachComplianceSettingsDocument,\n OutreachDoNotContactDomainEntry,\n OutreachEnrollment,\n OutreachSequence,\n} from './outreach.types'\nimport type { OutreachSequenceDraft, OutreachSequenceIssue } from './sequence-draft'\n\n/** Every reason these routes refuse with, for a caller to branch on. */\nexport type OutreachRouteRefusalReason =\n | 'unauthenticated'\n | 'email-unverified'\n | 'org-required'\n | 'not-a-member'\n | 'not-org-wide'\n | 'permission'\n | 'entitlement'\n | 'method-not-allowed'\n | 'invalid-request'\n | 'invalid-domain'\n | 'invalid-settings'\n | 'invalid-sequence'\n | 'sequence-not-found'\n | 'sequence-archived'\n | 'sequence-not-active'\n | 'activation-refused'\n | 'delete-refused'\n | 'mailbox-unavailable'\n | 'enrollment-not-found'\n | 'transition-refused'\n | 'view-not-found'\n | 'view-unsupported'\n | 'contact-not-found'\n | 'link-domain-refused'\n\n/** A refusal, in the one shape every route answers with. */\nexport interface OutreachRouteRefusal {\n /** A sentence written for the person reading it. */\n error: string\n reason: OutreachRouteRefusalReason\n /** The field-level reasons behind an `invalid-*` or `activation-refused` refusal. */\n issues?: ReadonlyArray<OutreachComplianceIssue | OutreachSequenceIssue>\n}\n\n/*==========================================\n * COMPLIANCE SETTINGS\n *==========================================*/\n\n/** `GET outreach/settings?orgId` — and the answer to a save. */\nexport interface OutreachSettingsResponse {\n ok: true\n settings: OutreachComplianceSettingsDocument\n}\n\n/** `POST outreach/settings` */\nexport interface OutreachSettingsSaveRequest {\n orgId: string\n legalName: string\n brandName: string\n postalAddress: string\n allowedCountries: string[]\n}\n\n/** The answer to a save: the settings as stored, and whether anything changed. */\nexport interface OutreachSettingsSaveResponse extends OutreachSettingsResponse {\n changed: boolean\n}\n\n/*==========================================\n * SEQUENCES\n *==========================================*/\n\n/** `POST outreach/sequences/save` — a new sequence without `sequenceId`, an edit with one. */\nexport interface OutreachSequenceSaveRequest {\n orgId: string\n sequenceId?: string\n sequence: OutreachSequenceDraft\n}\n\nexport interface OutreachSequenceSaveResponse {\n ok: true\n sequence: OutreachSequence\n created: boolean\n /** What the save allowed but probably isn't what was meant. */\n warnings: OutreachSequenceIssue[]\n}\n\n/** What `outreach/sequences/status` does to a sequence. */\nexport type OutreachSequenceAction = 'activate' | 'pause' | 'archive'\n\n/** `POST outreach/sequences/status` */\nexport interface OutreachSequenceStatusRequest {\n orgId: string\n sequenceId: string\n action: OutreachSequenceAction\n}\n\nexport interface OutreachSequenceStatusResponse {\n ok: true\n sequence: OutreachSequence\n /** The open enrollments an archive stopped; `0` for anything else. */\n stoppedEnrollments: number\n}\n\n/** `POST outreach/sequences/delete` — a draft nobody was ever enrolled in. */\nexport interface OutreachSequenceDeleteRequest {\n orgId: string\n sequenceId: string\n}\n\n/*==========================================\n * ENROLLING\n *==========================================*/\n\n/** The most people one preview or one enroll reads. */\nexport const OUTREACH_ENROLL_BATCH_MAX = 50\n\n/**\n * Which CRM record a person to enroll is (AGL-3234): a contact by its\n * document id, or a lead the sequence's site holds by its person key.\n */\nexport type OutreachPersonRef = { kind: 'contact'; id: string } | { kind: 'lead'; id: string }\n\n/**\n * Where the people to enroll come from: a saved Contacts or Leads view,\n * contacts picked by search, or leads picked from the sequence's site.\n */\nexport type OutreachEnrollSource =\n | { kind: 'view'; viewId: string }\n | { kind: 'contacts'; contactIds: string[] }\n | { kind: 'leads'; leadIds: string[] }\n\n/** `POST outreach/enroll/preview` */\nexport interface OutreachEnrollPreviewRequest {\n orgId: string\n sequenceId: string\n source: OutreachEnrollSource\n}\n\n/**\n * Where one person stands:\n *\n * - `eligible` — enrolled on Confirm as they are;\n * - `needs_confirmation` — enrolled once the rep writes the personal line\n * and ticks the attestations `requires` names;\n * - `blocked` — refused whatever the rep confirms, for the `blocks` given.\n */\nexport type OutreachEnrollPreviewStatus = 'eligible' | 'needs_confirmation' | 'blocked'\n\n/** One reason a person can't be enrolled, in a sentence a rep can act on. */\nexport interface OutreachEnrollBlock {\n code: string\n reason: string\n}\n\nexport interface OutreachEnrollPreviewPerson {\n /** The key the dialog and the confirm name this person by: the record's own id. */\n personId: string\n /** The record the person is (AGL-3234). */\n target: 'contact' | 'lead'\n /** The contact's id; `''` for a lead. */\n contactId: string\n /** The lead's person key; `null` for a contact. */\n leadId: string | null\n name: string\n /** The address the steps would go to, or `null` when the contact has none. */\n email: string | null\n /** No inbound capture behind them — the heavier rules apply. */\n cold: boolean\n /** ISO-3166-1 alpha-2, when anything names one. */\n country: string | null\n status: OutreachEnrollPreviewStatus\n /** Why a `blocked` person is refused; empty otherwise. */\n blocks: OutreachEnrollBlock[]\n /** What the rep must supply before a `needs_confirmation` person is enrolled. */\n requires: {\n personalLine: boolean\n attestations: OutreachAttestationKind[]\n }\n}\n\nexport interface OutreachEnrollPreviewResponse {\n ok: true\n people: OutreachEnrollPreviewPerson[]\n /** Everyone the source named, before the batch limit. */\n total: number\n /** True when the source named more people than one enrollment reads. */\n truncated: boolean\n}\n\n/** One person to enroll, with what the rep supplied for them: a contact by id, or a lead by key. */\nexport interface OutreachEnrollPersonRequest {\n contactId?: string\n leadId?: string\n personalLine?: string\n attestations?: OutreachAttestationKind[]\n}\n\n/** `POST outreach/enroll` — the gates are checked again, here, before anything is written. */\nexport interface OutreachEnrollRequest {\n orgId: string\n sequenceId: string\n people: OutreachEnrollPersonRequest[]\n}\n\ninterface OutreachEnrollOutcomePerson {\n personId: string\n target: 'contact' | 'lead'\n contactId: string\n leadId: string | null\n email: string | null\n}\n\nexport type OutreachEnrollOutcome =\n | (OutreachEnrollOutcomePerson & { outcome: 'enrolled'; enrollmentId: string })\n | (OutreachEnrollOutcomePerson & { outcome: 'blocked'; blocks: OutreachEnrollBlock[] })\n\nexport interface OutreachEnrollResponse {\n ok: true\n results: OutreachEnrollOutcome[]\n enrolled: number\n}\n\n/*==========================================\n * ENROLLMENTS\n *==========================================*/\n\n/** What a member may do to one enrollment. */\nexport type OutreachEnrollmentAction = 'pause' | 'resume' | 'stop' | 'do_not_contact'\n\n/** `POST outreach/enrollments/action` */\nexport interface OutreachEnrollmentActionRequest {\n orgId: string\n enrollmentId: string\n action: OutreachEnrollmentAction\n /** Why, for a stop or a do-not-contact. */\n detail?: string\n}\n\nexport interface OutreachEnrollmentActionResponse {\n ok: true\n /** False when the enrollment was already where the action would put it. */\n changed: boolean\n enrollment: OutreachEnrollment\n /** Other open enrollments of the same address a do-not-contact stopped. */\n stoppedOthers: number\n}\n\n/*==========================================\n * DO NOT CONTACT: DOMAINS (AGL-3244)\n *==========================================*/\n\n/** `GET outreach/do-not-contact/domains?orgId` — and the answer to a change. */\nexport interface OutreachDoNotContactDomainsResponse {\n ok: true\n /** Every domain on the list, alphabetically. */\n domains: OutreachDoNotContactDomainEntry[]\n}\n\n/** `POST outreach/do-not-contact/domains` */\nexport interface OutreachDoNotContactDomainRequest {\n orgId: string\n action: 'add' | 'remove'\n /** The domain, or an address at it. */\n domain: string\n /** Why, for an add. */\n detail?: string\n}\n\n/** The answer to a change: the list as it stands, and whether anything changed. */\nexport interface OutreachDoNotContactDomainResponse extends OutreachDoNotContactDomainsResponse {\n changed: boolean\n /** The domain as the list spells it. */\n domain: string\n}\n\n/*==========================================\n * LINK DOMAINS (AGL-3306)\n *==========================================*/\n\n/** Where a mailbox domain's click-tracking host stands. */\nexport type OutreachLinkDomainStatus = 'not-set-up' | 'requested' | 'records-issued' | 'verified' | 'failed'\n\n/** One DNS record the member publishes, as the sending-domain cards print them. */\nexport interface OutreachLinkDomainRecord {\n type: string\n name: string\n value: string\n required: boolean\n note: string\n}\n\n/**\n * One sending domain of the organization's connected mailboxes, and its\n * `links.` host: what tracked links in mail from it read now, and what is\n * left to do for them to read `https://links.<domain>/<id>`.\n */\nexport interface OutreachLinkDomain {\n /** The mailboxes' sending domain. */\n domain: string\n /** `links.<domain>`. */\n host: string\n status: OutreachLinkDomainStatus\n /** The records to publish; empty until the host is set up. */\n records: OutreachLinkDomainRecord[]\n /** Why it is not verified, in a sentence; `null` when nothing is wrong. */\n detail: string | null\n /** How a tracked link in mail from this domain starts today. */\n linkPrefix: string | null\n checkedAtMs: number | null\n verifiedAtMs: number | null\n}\n\n/** `GET outreach/link-domains?orgId` */\nexport interface OutreachLinkDomainsResponse {\n ok: true\n domains: OutreachLinkDomain[]\n /** Whether the member may set a host up, check it, or remove it. */\n canManage: boolean\n}\n\n/** `POST outreach/link-domains` */\nexport interface OutreachLinkDomainRequest {\n orgId: string\n domain: string\n action: 'set-up' | 'check' | 'remove'\n}\n\nexport interface OutreachLinkDomainResponse extends OutreachLinkDomainsResponse {\n /** The domain the action was for. */\n domain: string\n}\n\n/*==========================================\n * PREVIEW\n *==========================================*/\n\n/** `POST outreach/preview` — one email of a saved sequence, as a person would get it. */\nexport interface OutreachPreviewRequest {\n orgId: string\n sequenceId: string\n /** The contact to write it to; a sample person when absent. */\n contactId?: string\n /** Or a lead the sequence's site holds (AGL-3234), written to as the contact it would be. */\n leadId?: string\n personalLine?: string\n /** Which step; the first email when absent. */\n stepIndex?: number\n}\n\nexport interface OutreachPreviewResponse {\n ok: true\n stepIndex: number\n subject: string\n /** The plain text, footer included. */\n text: string\n unresolvedFields: string[]\n /** Why it could not be written, when it could not. */\n error: OutreachComposeError | null\n}\n"],"names":["OUTREACH_ENROLL_BATCH_MAX"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;CAQC,GAmHD;;4CAE4C,GAE5C,qDAAqD,GACrD,OAAO,MAAMA,4BAA4B,GAAE"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/model/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\n/**\n * THE CONTRACT OF OUTREACH'S SETTINGS, SEQUENCE AND ENROLLMENT ROUTES\n * (AGL-2980): what the console sends, what the routes answer, and every\n * reason one refuses. Client-safe — types and the batch limit only.\n *\n * Every request names its organization (`orgId`, in the query of a `GET`\n * and the JSON body of a `POST`): the routes serve an organization-level\n * surface that names no site.\n */\n\nimport type { OutreachComposeError } from '../engine/compose'\nimport type { OutreachValidationIssue } from '../engine/sequence-validation'\nimport type { OutreachComplianceIssue } from './compliance-settings'\nimport type {\n OutreachAttestationKind,\n OutreachComplianceSettingsDocument,\n OutreachDoNotContactDomainEntry,\n OutreachEnrollment,\n OutreachSequence,\n OutreachStepOverrideSource,\n} from './outreach.types'\nimport type { OutreachSequenceDraft, OutreachSequenceIssue } from './sequence-draft'\n\n/** Every reason these routes refuse with, for a caller to branch on. */\nexport type OutreachRouteRefusalReason =\n | 'unauthenticated'\n | 'email-unverified'\n | 'org-required'\n | 'not-a-member'\n | 'not-org-wide'\n | 'permission'\n | 'entitlement'\n | 'method-not-allowed'\n | 'invalid-request'\n | 'invalid-domain'\n | 'invalid-settings'\n | 'invalid-sequence'\n | 'sequence-not-found'\n | 'sequence-archived'\n | 'sequence-not-active'\n | 'activation-refused'\n | 'delete-refused'\n | 'mailbox-unavailable'\n | 'enrollment-not-found'\n | 'transition-refused'\n | 'view-not-found'\n | 'view-unsupported'\n | 'contact-not-found'\n | 'link-domain-refused'\n | 'rate-limited'\n | 'google-unavailable'\n | 'curation-unavailable'\n | 'curation-refused'\n | 'invalid-override'\n\n/** A refusal, in the one shape every route answers with. */\nexport interface OutreachRouteRefusal {\n /** A sentence written for the person reading it. */\n error: string\n reason: OutreachRouteRefusalReason\n /** The field-level reasons behind an `invalid-*` or `activation-refused` refusal. */\n issues?: ReadonlyArray<OutreachComplianceIssue | OutreachSequenceIssue>\n}\n\n/*==========================================\n * COMPLIANCE SETTINGS\n *==========================================*/\n\n/** `GET outreach/settings?orgId` — and the answer to a save. */\nexport interface OutreachSettingsResponse {\n ok: true\n settings: OutreachComplianceSettingsDocument\n}\n\n/** `POST outreach/settings` */\nexport interface OutreachSettingsSaveRequest {\n orgId: string\n legalName: string\n brandName: string\n postalAddress: string\n allowedCountries: string[]\n}\n\n/** The answer to a save: the settings as stored, and whether anything changed. */\nexport interface OutreachSettingsSaveResponse extends OutreachSettingsResponse {\n changed: boolean\n}\n\n/*==========================================\n * SEQUENCES\n *==========================================*/\n\n/** `POST outreach/sequences/save` — a new sequence without `sequenceId`, an edit with one. */\nexport interface OutreachSequenceSaveRequest {\n orgId: string\n sequenceId?: string\n sequence: OutreachSequenceDraft\n}\n\nexport interface OutreachSequenceSaveResponse {\n ok: true\n sequence: OutreachSequence\n created: boolean\n /** What the save allowed but probably isn't what was meant. */\n warnings: OutreachSequenceIssue[]\n}\n\n/** What `outreach/sequences/status` does to a sequence. */\nexport type OutreachSequenceAction = 'activate' | 'pause' | 'archive'\n\n/** `POST outreach/sequences/status` */\nexport interface OutreachSequenceStatusRequest {\n orgId: string\n sequenceId: string\n action: OutreachSequenceAction\n}\n\nexport interface OutreachSequenceStatusResponse {\n ok: true\n sequence: OutreachSequence\n /** The open enrollments an archive stopped; `0` for anything else. */\n stoppedEnrollments: number\n}\n\n/** `POST outreach/sequences/delete` — a draft nobody was ever enrolled in. */\nexport interface OutreachSequenceDeleteRequest {\n orgId: string\n sequenceId: string\n}\n\n/*==========================================\n * ENROLLING\n *==========================================*/\n\n/** The most people one preview or one enroll reads. */\nexport const OUTREACH_ENROLL_BATCH_MAX = 50\n\n/**\n * Which CRM record a person to enroll is (AGL-3234): a contact by its\n * document id, or a lead the sequence's site holds by its person key.\n */\nexport type OutreachPersonRef = { kind: 'contact'; id: string } | { kind: 'lead'; id: string }\n\n/**\n * Where the people to enroll come from: a saved Contacts or Leads view,\n * contacts picked by search, or leads picked from the sequence's site.\n */\nexport type OutreachEnrollSource =\n | { kind: 'view'; viewId: string }\n | { kind: 'contacts'; contactIds: string[] }\n | { kind: 'leads'; leadIds: string[] }\n\n/** `POST outreach/enroll/preview` */\nexport interface OutreachEnrollPreviewRequest {\n orgId: string\n sequenceId: string\n source: OutreachEnrollSource\n}\n\n/**\n * Where one person stands:\n *\n * - `eligible` — enrolled on Confirm as they are;\n * - `needs_confirmation` — enrolled once the rep writes the personal line\n * and ticks the attestations `requires` names;\n * - `blocked` — refused whatever the rep confirms, for the `blocks` given.\n */\nexport type OutreachEnrollPreviewStatus = 'eligible' | 'needs_confirmation' | 'blocked'\n\n/** One reason a person can't be enrolled, in a sentence a rep can act on. */\nexport interface OutreachEnrollBlock {\n code: string\n reason: string\n}\n\nexport interface OutreachEnrollPreviewPerson {\n /** The key the dialog and the confirm name this person by: the record's own id. */\n personId: string\n /** The record the person is (AGL-3234). */\n target: 'contact' | 'lead'\n /** The contact's id; `''` for a lead. */\n contactId: string\n /** The lead's person key; `null` for a contact. */\n leadId: string | null\n name: string\n /** The address the steps would go to, or `null` when the contact has none. */\n email: string | null\n /** No inbound capture behind them — the heavier rules apply. */\n cold: boolean\n /** ISO-3166-1 alpha-2, when anything names one. */\n country: string | null\n status: OutreachEnrollPreviewStatus\n /** Why a `blocked` person is refused; empty otherwise. */\n blocks: OutreachEnrollBlock[]\n /** What the rep must supply before a `needs_confirmation` person is enrolled. */\n requires: {\n personalLine: boolean\n attestations: OutreachAttestationKind[]\n }\n}\n\nexport interface OutreachEnrollPreviewResponse {\n ok: true\n people: OutreachEnrollPreviewPerson[]\n /** Everyone the source named, before the batch limit. */\n total: number\n /** True when the source named more people than one enrollment reads. */\n truncated: boolean\n}\n\n/**\n * One person's confirmed copy of one step, as the console sends it\n * (AGL-3324): what the member read and ticked the confirmation for. The\n * route validates it again and stamps who and when.\n */\nexport interface OutreachStepOverrideRequest {\n stepIndex: number\n /** Read only where the email starts a thread. */\n subject?: string\n body: string\n source: OutreachStepOverrideSource\n /** An AI draft the member changed before confirming it. */\n edited?: boolean\n /** The prompt the AI was given, returned by the draft route, for the audit log. */\n prompt?: string\n /** The model that drafted it, returned by the draft route. */\n model?: string\n}\n\n/** One person to enroll, with what the rep supplied for them: a contact by id, or a lead by key. */\nexport interface OutreachEnrollPersonRequest {\n contactId?: string\n leadId?: string\n personalLine?: string\n attestations?: OutreachAttestationKind[]\n /** The steps the member curated for this person before enrolling them (AGL-3324). */\n stepOverrides?: OutreachStepOverrideRequest[]\n}\n\n/** `POST outreach/enroll` — the gates are checked again, here, before anything is written. */\nexport interface OutreachEnrollRequest {\n orgId: string\n sequenceId: string\n people: OutreachEnrollPersonRequest[]\n}\n\ninterface OutreachEnrollOutcomePerson {\n personId: string\n target: 'contact' | 'lead'\n contactId: string\n leadId: string | null\n email: string | null\n}\n\nexport type OutreachEnrollOutcome =\n | (OutreachEnrollOutcomePerson & { outcome: 'enrolled'; enrollmentId: string })\n | (OutreachEnrollOutcomePerson & { outcome: 'blocked'; blocks: OutreachEnrollBlock[] })\n\nexport interface OutreachEnrollResponse {\n ok: true\n results: OutreachEnrollOutcome[]\n enrolled: number\n}\n\n/*==========================================\n * ENROLLMENTS\n *==========================================*/\n\n/** What a member may do to one enrollment. */\nexport type OutreachEnrollmentAction = 'pause' | 'resume' | 'stop' | 'do_not_contact'\n\n/** `POST outreach/enrollments/action` */\nexport interface OutreachEnrollmentActionRequest {\n orgId: string\n enrollmentId: string\n action: OutreachEnrollmentAction\n /** Why, for a stop or a do-not-contact. */\n detail?: string\n}\n\nexport interface OutreachEnrollmentActionResponse {\n ok: true\n /** False when the enrollment was already where the action would put it. */\n changed: boolean\n enrollment: OutreachEnrollment\n /** Other open enrollments of the same address a do-not-contact stopped. */\n stoppedOthers: number\n}\n\n/*==========================================\n * DO NOT CONTACT: DOMAINS (AGL-3244)\n *==========================================*/\n\n/** `GET outreach/do-not-contact/domains?orgId` — and the answer to a change. */\nexport interface OutreachDoNotContactDomainsResponse {\n ok: true\n /** Every domain on the list, alphabetically. */\n domains: OutreachDoNotContactDomainEntry[]\n}\n\n/** `POST outreach/do-not-contact/domains` */\nexport interface OutreachDoNotContactDomainRequest {\n orgId: string\n action: 'add' | 'remove'\n /** The domain, or an address at it. */\n domain: string\n /** Why, for an add. */\n detail?: string\n}\n\n/** The answer to a change: the list as it stands, and whether anything changed. */\nexport interface OutreachDoNotContactDomainResponse extends OutreachDoNotContactDomainsResponse {\n changed: boolean\n /** The domain as the list spells it. */\n domain: string\n}\n\n/*==========================================\n * LINK DOMAINS (AGL-3306)\n *==========================================*/\n\n/** Where a mailbox domain's click-tracking host stands. */\nexport type OutreachLinkDomainStatus = 'not-set-up' | 'requested' | 'records-issued' | 'verified' | 'failed'\n\n/** One DNS record the member publishes, as the sending-domain cards print them. */\nexport interface OutreachLinkDomainRecord {\n type: string\n name: string\n value: string\n required: boolean\n note: string\n}\n\n/**\n * One sending domain of the organization's connected mailboxes, and its\n * `links.` host: what tracked links in mail from it read now, and what is\n * left to do for them to read `https://links.<domain>/<id>`.\n */\nexport interface OutreachLinkDomain {\n /** The mailboxes' sending domain. */\n domain: string\n /** `links.<domain>`. */\n host: string\n status: OutreachLinkDomainStatus\n /** The records to publish; empty until the host is set up. */\n records: OutreachLinkDomainRecord[]\n /** Why it is not verified, in a sentence; `null` when nothing is wrong. */\n detail: string | null\n /** How a tracked link in mail from this domain starts today. */\n linkPrefix: string | null\n checkedAtMs: number | null\n verifiedAtMs: number | null\n}\n\n/** `GET outreach/link-domains?orgId` */\nexport interface OutreachLinkDomainsResponse {\n ok: true\n domains: OutreachLinkDomain[]\n /** Whether the member may set a host up, check it, or remove it. */\n canManage: boolean\n}\n\n/** `POST outreach/link-domains` */\nexport interface OutreachLinkDomainRequest {\n orgId: string\n domain: string\n action: 'set-up' | 'check' | 'remove'\n}\n\nexport interface OutreachLinkDomainResponse extends OutreachLinkDomainsResponse {\n /** The domain the action was for. */\n domain: string\n}\n\n/*==========================================\n * PREVIEW\n *==========================================*/\n\n/** `POST outreach/preview` — one email of a saved sequence, as a person would get it. */\nexport interface OutreachPreviewRequest {\n orgId: string\n sequenceId: string\n /** The contact to write it to; a sample person when absent. */\n contactId?: string\n /** Or a lead the sequence's site holds (AGL-3234), written to as the contact it would be. */\n leadId?: string\n personalLine?: string\n /** Which step; the first email when absent. */\n stepIndex?: number\n /** The person's copies of steps as the dialog holds them (AGL-3324), so the preview shows the curated version. */\n stepOverrides?: OutreachStepOverrideRequest[]\n}\n\nexport interface OutreachPreviewResponse {\n ok: true\n stepIndex: number\n subject: string\n /** The plain text, footer included. */\n text: string\n unresolvedFields: string[]\n /** Why it could not be written, when it could not. */\n error: OutreachComposeError | null\n}\n\n/*==========================================\n * A TEST OF ONE STEP (AGL-3325)\n *==========================================*/\n\n/**\n * `POST outreach/steps/test` — one step of a saved sequence, rendered\n * exactly as `outreach/preview` renders it (the same person, personal\n * line and step) and sent through the sequence's mailbox to the member.\n */\nexport interface OutreachStepTestRequest extends OutreachPreviewRequest {\n /** Where the test goes; the member's own address when absent or empty. */\n to?: string\n}\n\nexport interface OutreachStepTestResponse {\n ok: true\n stepIndex: number\n /** The address the test went to. */\n sentTo: string\n /** The subject as sent, `[Test]` prefix included. */\n subject: string\n sentAtMs: number\n /** How many tests the mailbox has sent on its local day, this one included. */\n testsToday: number\n}\n\n/*==========================================\n * CURATING (AGL-3324)\n *==========================================*/\n\n/**\n * `POST outreach/curate/draft` — this person's own copies of the sequence's\n * email steps, drafted by the workspace's AI. Names EITHER a person about to\n * be enrolled (the sequence, a contact or a lead, the personal line the rep\n * is writing) OR an enrollment, whose next email step is drafted. Nothing is\n * stored: the answer is what the member reads before confirming.\n */\nexport interface OutreachCurateDraftRequest {\n orgId: string\n sequenceId?: string\n contactId?: string\n leadId?: string\n personalLine?: string\n enrollmentId?: string\n /** Which email steps; every email step for a person to enroll, the next one for an enrollment, when absent. */\n stepIndexes?: number[]\n}\n\n/** One drafted step, with what the validator said about it. */\nexport interface OutreachCurateDraft {\n stepIndex: number\n /** `null` for an in-thread email, which is sent as `Re:` the thread's subject. */\n subject: string | null\n body: string\n /** Errors and warnings; a draft with an error cannot be confirmed as it stands. */\n issues: OutreachValidationIssue[]\n}\n\nexport interface OutreachCurateDraftResponse {\n ok: true\n drafts: OutreachCurateDraft[]\n /** The prompt the AI was given and the model that answered, for the override's audit record. */\n prompt: string\n model: string\n}\n\n/** `POST outreach/curate/save` — stores a confirmed copy of one step on an enrollment, or clears it with `override: null`. */\nexport interface OutreachCurateSaveRequest {\n orgId: string\n enrollmentId: string\n stepIndex: number\n override: Omit<OutreachStepOverrideRequest, 'stepIndex'> | null\n}\n\nexport interface OutreachCurateSaveResponse {\n ok: true\n enrollment: OutreachEnrollment\n}\n"],"names":["OUTREACH_ENROLL_BATCH_MAX"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;CAQC,GA0HD;;4CAE4C,GAE5C,qDAAqD,GACrD,OAAO,MAAMA,4BAA4B,GAAE"}
|
|
@@ -128,6 +128,12 @@ export interface OutreachMailboxDailyHealth {
|
|
|
128
128
|
sent: number;
|
|
129
129
|
bounces: number;
|
|
130
130
|
replies: number;
|
|
131
|
+
/**
|
|
132
|
+
* Tests of a sequence step sent to a member (AGL-3325). Counted apart from
|
|
133
|
+
* `sent`: a test is a real Gmail send, so it spends the account's own
|
|
134
|
+
* limits, but it is not a person emailed and never moves the daily cap.
|
|
135
|
+
*/
|
|
136
|
+
tests?: number;
|
|
131
137
|
}
|
|
132
138
|
/**
|
|
133
139
|
* The counters a mailbox's sending is judged by.
|
|
@@ -512,6 +518,31 @@ export interface OutreachAttestation {
|
|
|
512
518
|
export type OutreachAttestations = Partial<Record<OutreachAttestationKind, OutreachAttestation>>;
|
|
513
519
|
/** The longest personal line an enrollment keeps: one sentence, not a letter. */
|
|
514
520
|
export declare const OUTREACH_PERSONAL_LINE_MAX = 300;
|
|
521
|
+
/** Who wrote the override: the AI, or the member by hand. */
|
|
522
|
+
export declare const OUTREACH_STEP_OVERRIDE_SOURCES: readonly ["ai", "member"];
|
|
523
|
+
export type OutreachStepOverrideSource = (typeof OUTREACH_STEP_OVERRIDE_SOURCES)[number];
|
|
524
|
+
/** One person's own copy of one step. */
|
|
525
|
+
export interface OutreachStepOverride {
|
|
526
|
+
/** The subject, merge fields allowed; absent keeps the step's own. Not read on an in-thread email. */
|
|
527
|
+
subject?: string;
|
|
528
|
+
/** The plain-text body, merge fields allowed; absent keeps the step's own (or its template's). */
|
|
529
|
+
body?: string;
|
|
530
|
+
source: OutreachStepOverrideSource;
|
|
531
|
+
/** When the member confirmed it — the moment it was stored, never before. */
|
|
532
|
+
draftedAtMs: number;
|
|
533
|
+
/** The member who confirmed it. */
|
|
534
|
+
draftedByUid?: string;
|
|
535
|
+
/** An AI draft the member changed before confirming it. */
|
|
536
|
+
edited?: boolean;
|
|
537
|
+
/** The prompt the AI was given, for the audit log; absent on a member's own words. */
|
|
538
|
+
prompt?: string;
|
|
539
|
+
/** The model that drafted it, for the audit log. */
|
|
540
|
+
model?: string;
|
|
541
|
+
}
|
|
542
|
+
/** The overrides an enrollment carries, keyed by the step's index as text. */
|
|
543
|
+
export type OutreachStepOverrides = Record<string, OutreachStepOverride>;
|
|
544
|
+
/** The sentence a member ticks before a curated draft is stored — a fourth attestation. */
|
|
545
|
+
export declare const OUTREACH_CURATION_CONFIRMATION_LABEL = "I've read this draft, and it goes out to this person as written";
|
|
515
546
|
/**
|
|
516
547
|
* Which CRM record an enrollment names (AGL-3234). A person is a LEAD until
|
|
517
548
|
* somebody qualifies them and a CONTACT after, and a sequence works either:
|
|
@@ -612,6 +643,12 @@ export interface OutreachEnrollment extends OutreachTimestamps {
|
|
|
612
643
|
* in it, and one it leaves keeps what it was credited with.
|
|
613
644
|
*/
|
|
614
645
|
campaignIds?: string[];
|
|
646
|
+
/**
|
|
647
|
+
* This person's own copies of steps (AGL-3324), by step index. Absent on
|
|
648
|
+
* an enrollment nobody curated, which sends the sequence's steps as
|
|
649
|
+
* written; a step without an entry sends the same.
|
|
650
|
+
*/
|
|
651
|
+
stepOverrides?: OutreachStepOverrides;
|
|
615
652
|
}
|
|
616
653
|
/** A sending run's claim on one enrollment's step (AGL-2981). */
|
|
617
654
|
export interface OutreachSendClaim {
|
|
@@ -652,6 +689,11 @@ export interface OutreachStepRecord {
|
|
|
652
689
|
* read afterwards for what it actually offered them.
|
|
653
690
|
*/
|
|
654
691
|
links?: string[];
|
|
692
|
+
/**
|
|
693
|
+
* The email went out as this person's curated copy (AGL-3324), and who
|
|
694
|
+
* wrote it. Absent on a step sent as the sequence wrote it.
|
|
695
|
+
*/
|
|
696
|
+
curated?: OutreachStepOverrideSource;
|
|
655
697
|
}
|
|
656
698
|
/**
|
|
657
699
|
* What every Outreach email's footer says about who sent it — the
|
|
@@ -234,6 +234,24 @@ export const OUTREACH_TASK_KIND_LABELS = {
|
|
|
234
234
|
verified_deliverable: 'This address was verified as deliverable'
|
|
235
235
|
};
|
|
236
236
|
/** The longest personal line an enrollment keeps: one sentence, not a letter. */ export const OUTREACH_PERSONAL_LINE_MAX = 300;
|
|
237
|
+
/*==========================================
|
|
238
|
+
* CURATED STEPS (AGL-3324).
|
|
239
|
+
*
|
|
240
|
+
* A step is one template for everyone, plus the personal line. Curating
|
|
241
|
+
* rewrites ONE person's copy of a step — drafted by the workspace's AI from
|
|
242
|
+
* their record and the step's own body as the skeleton, or written by the
|
|
243
|
+
* member — and keeps it on the enrollment, never on the sequence. The send
|
|
244
|
+
* job reads the override in place of the step's subject and body; the
|
|
245
|
+
* tokens, the footer, the click tracking and the plain-text rules still
|
|
246
|
+
* apply to it exactly as they apply to the step.
|
|
247
|
+
*
|
|
248
|
+
* Nothing is stored that a member has not read: a draft lands on the
|
|
249
|
+
* enrollment only when the member confirms it, with who and when.
|
|
250
|
+
*==========================================*/ /** Who wrote the override: the AI, or the member by hand. */ export const OUTREACH_STEP_OVERRIDE_SOURCES = [
|
|
251
|
+
'ai',
|
|
252
|
+
'member'
|
|
253
|
+
];
|
|
254
|
+
/** The sentence a member ticks before a curated draft is stored — a fourth attestation. */ export const OUTREACH_CURATION_CONFIRMATION_LABEL = "I've read this draft, and it goes out to this person as written";
|
|
237
255
|
/**
|
|
238
256
|
* Which CRM record an enrollment names (AGL-3234). A person is a LEAD until
|
|
239
257
|
* somebody qualifies them and a CONTACT after, and a sequence works either:
|