@aglyn/plugins-outreach 1.0.0-beta.203 → 1.0.0-beta.205

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/enrollment-detail.js +20 -2
  3. package/src/lib/components/enrollment-detail.js.map +1 -1
  4. package/src/lib/components/enrollments-table.d.ts +8 -1
  5. package/src/lib/components/enrollments-table.js +60 -10
  6. package/src/lib/components/enrollments-table.js.map +1 -1
  7. package/src/lib/components/mailbox-card.d.ts +1 -0
  8. package/src/lib/components/mailbox-card.js +67 -1
  9. package/src/lib/components/mailbox-card.js.map +1 -1
  10. package/src/lib/components/next-send.d.ts +22 -0
  11. package/src/lib/components/next-send.js +118 -0
  12. package/src/lib/components/next-send.js.map +1 -0
  13. package/src/lib/components/sequence-detail.js +2 -0
  14. package/src/lib/components/sequence-detail.js.map +1 -1
  15. package/src/lib/components/use-outreach-mailbox-api.d.ts +3 -1
  16. package/src/lib/components/use-outreach-mailbox-api.js +8 -0
  17. package/src/lib/components/use-outreach-mailbox-api.js.map +1 -1
  18. package/src/lib/constants/api-routes.d.ts +5 -0
  19. package/src/lib/constants/api-routes.js +4 -0
  20. package/src/lib/constants/api-routes.js.map +1 -1
  21. package/src/lib/engine/index.d.ts +1 -0
  22. package/src/lib/engine/index.js +1 -0
  23. package/src/lib/engine/index.js.map +1 -1
  24. package/src/lib/engine/next-send.d.ts +59 -0
  25. package/src/lib/engine/next-send.js +100 -0
  26. package/src/lib/engine/next-send.js.map +1 -0
  27. package/src/lib/mailboxes/mailbox-api.d.ts +13 -0
  28. package/src/lib/mailboxes/mailbox-api.js.map +1 -1
  29. package/src/lib/mailboxes/mailbox-routes.d.ts +6 -0
  30. package/src/lib/mailboxes/mailbox-routes.js +28 -1
  31. package/src/lib/mailboxes/mailbox-routes.js.map +1 -1
  32. package/src/lib/mailboxes/register-mailbox-routes.js +5 -1
  33. package/src/lib/mailboxes/register-mailbox-routes.js.map +1 -1
@@ -0,0 +1,118 @@
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
+ */ 'use client';
17
+ import { jsx as _jsx } from "react/jsx-runtime";
18
+ import { Tooltip, Typography } from "@mui/material";
19
+ import { useEffect, useState } from "react";
20
+ import { isOutreachWindowOpen } from "../engine/schedule.js";
21
+ import { OUTREACH_TICK_MINUTES, outreachTickAllowance } from "../engine/sending-capacity.js";
22
+ import { formatOutreachTime } from "./outreach-ui.js";
23
+ /*
24
+ * The words for an enrollment's next send (AGL-3366): a time while it is
25
+ * still ahead, and once it has come, the queue it is waiting in — short in
26
+ * the cell, whole in the tooltip, which also keeps the exact due time.
27
+ */ const QUEUED_LABELS = {
28
+ paced: 'Queued · paced',
29
+ next_run: 'Queued · next run',
30
+ window_closed: 'Queued · window closed',
31
+ daily_cap_reached: 'Queued · cap reached',
32
+ mailbox_not_sending: 'Held · mailbox not sending',
33
+ sequence_not_active: 'Held · sequence not active'
34
+ };
35
+ /**
36
+ * The clock next sends are judged against, moved on every minute so a row
37
+ * turns from its time to "Queued" while the page stays open.
38
+ */ export function useOutreachNowMs(everyMs = 60000) {
39
+ const [nowMs, setNowMs] = useState(()=>Date.now());
40
+ useEffect(()=>{
41
+ const timer = setInterval(()=>setNowMs(Date.now()), everyMs);
42
+ return ()=>clearInterval(timer);
43
+ }, [
44
+ everyMs
45
+ ]);
46
+ return nowMs;
47
+ }
48
+ /** What the cell, the chip and the export say. */ export function outreachNextSendLabel(state, timeZone) {
49
+ if (state.kind === 'none') return '—';
50
+ if (state.kind === 'scheduled') return formatOutreachTime(state.dueAtMs, timeZone);
51
+ return QUEUED_LABELS[state.reason];
52
+ }
53
+ /** The whole explanation of a queued send; `''` for one that is not queued. */ export function outreachNextSendDetail(state, timeZone) {
54
+ if (state.kind !== 'queued') return '';
55
+ const time = (ms)=>formatOutreachTime(ms, timeZone);
56
+ const due = `Due ${time(state.dueAtMs)}.`;
57
+ const pacing = state.pacing ? ` This mailbox has sent ${state.pacing.sentToday} of ${state.pacing.dailyCap} today` + (state.reason === 'paced' ? ` and may send ${state.pacing.allowance} in this run.` : '.') : '';
58
+ switch(state.reason){
59
+ case 'paced':
60
+ return `${due} Emails go out a few at a time every ${OUTREACH_TICK_MINUTES} minutes, ` + 'follow-ups first, so the day’s cap is spread across the send window rather than ' + `sent at once. This one goes out in a later run.${pacing}`;
61
+ case 'next_run':
62
+ return `${due} The task is created on the next run, within ${OUTREACH_TICK_MINUTES} minutes.`;
63
+ case 'window_closed':
64
+ return `${due} The send window is closed; it goes out once the window opens${state.opensAtMs ? `, ${time(state.opensAtMs)}` : ''}.${pacing}`;
65
+ case 'daily_cap_reached':
66
+ return `${due}${pacing} Sending resumes${state.opensAtMs ? ` ${time(state.opensAtMs)}` : ' in the next window'}.`;
67
+ case 'mailbox_not_sending':
68
+ return `${due} Its mailbox is not sending, so nothing goes out until it is connected and resumed.`;
69
+ case 'sequence_not_active':
70
+ return `${due} The sequence is not active, so nothing goes out until it is activated.`;
71
+ }
72
+ }
73
+ /**
74
+ * The Mailboxes card's one line on pacing: what has gone today against the
75
+ * cap, and how much the next run may send — the number a due enrollment's
76
+ * "Queued" is waiting on.
77
+ */ export function outreachPacingNote(mailbox, nowMs) {
78
+ const tick = outreachTickAllowance({
79
+ mailbox,
80
+ nowMs
81
+ });
82
+ if (tick.hold === 'mailbox_not_connected' || tick.hold === 'invalid_timezone') {
83
+ return 'Sends nothing until it is connected and sending.';
84
+ }
85
+ const today = `Today: ${tick.sentToday} of ${tick.dailyCap} sent`;
86
+ if (tick.hold === 'daily_cap_reached') return `${today} · cap reached, sending resumes in the next day’s window.`;
87
+ if (!isOutreachWindowOpen(nowMs, mailbox.window, mailbox.timezone)) {
88
+ return `${today} · outside its send window.`;
89
+ }
90
+ return `${today} · up to ${tick.allowance} per ${OUTREACH_TICK_MINUTES}-minute run now, ` + 'follow-ups first, so the rest of the day’s cap is spread across the window.';
91
+ }
92
+ /** An enrollment's next send, with the reason on hover when it is queued. */ export function OutreachNextSend(props) {
93
+ const { state, timeZone } = props;
94
+ const label = outreachNextSendLabel(state, timeZone);
95
+ if (state.kind !== 'queued') {
96
+ return /*#__PURE__*/ _jsx(Typography, {
97
+ variant: "body2",
98
+ color: state.kind === 'none' ? 'text.secondary' : undefined,
99
+ children: label
100
+ });
101
+ }
102
+ return /*#__PURE__*/ _jsx(Tooltip, {
103
+ title: outreachNextSendDetail(state, timeZone),
104
+ describeChild: true,
105
+ children: /*#__PURE__*/ _jsx(Typography, {
106
+ variant: "body2",
107
+ color: state.reason === 'mailbox_not_sending' || state.reason === 'sequence_not_active' ? 'warning.main' : 'text.secondary',
108
+ sx: {
109
+ textDecoration: 'underline dotted',
110
+ textUnderlineOffset: 3,
111
+ cursor: 'help'
112
+ },
113
+ children: label
114
+ })
115
+ });
116
+ }
117
+
118
+ //# sourceMappingURL=next-send.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/next-send.tsx"],"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 { Tooltip, Typography } from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport type { OutreachNextSendState, OutreachQueuedReason } from '../engine/next-send'\nimport { isOutreachWindowOpen } from '../engine/schedule'\nimport { OUTREACH_TICK_MINUTES, outreachTickAllowance } from '../engine/sending-capacity'\nimport type { OutreachMailbox } from '../model/outreach.types'\nimport { formatOutreachTime } from './outreach-ui'\n\n/*\n * The words for an enrollment's next send (AGL-3366): a time while it is\n * still ahead, and once it has come, the queue it is waiting in — short in\n * the cell, whole in the tooltip, which also keeps the exact due time.\n */\n\nconst QUEUED_LABELS: Record<OutreachQueuedReason, string> = {\n paced: 'Queued · paced',\n next_run: 'Queued · next run',\n window_closed: 'Queued · window closed',\n daily_cap_reached: 'Queued · cap reached',\n mailbox_not_sending: 'Held · mailbox not sending',\n sequence_not_active: 'Held · sequence not active',\n}\n\n/**\n * The clock next sends are judged against, moved on every minute so a row\n * turns from its time to \"Queued\" while the page stays open.\n */\nexport function useOutreachNowMs(everyMs = 60_000): number {\n const [nowMs, setNowMs] = useState(() => Date.now())\n useEffect(() => {\n const timer = setInterval(() => setNowMs(Date.now()), everyMs)\n return () => clearInterval(timer)\n }, [everyMs])\n return nowMs\n}\n\n/** What the cell, the chip and the export say. */\nexport function outreachNextSendLabel(state: OutreachNextSendState, timeZone: string | null): string {\n if (state.kind === 'none') return '—'\n if (state.kind === 'scheduled') return formatOutreachTime(state.dueAtMs, timeZone)\n return QUEUED_LABELS[state.reason]\n}\n\n/** The whole explanation of a queued send; `''` for one that is not queued. */\nexport function outreachNextSendDetail(state: OutreachNextSendState, timeZone: string | null): string {\n if (state.kind !== 'queued') return ''\n const time = (ms: number | null) => formatOutreachTime(ms, timeZone)\n const due = `Due ${time(state.dueAtMs)}.`\n const pacing = state.pacing\n ? ` This mailbox has sent ${state.pacing.sentToday} of ${state.pacing.dailyCap} today` +\n (state.reason === 'paced' ? ` and may send ${state.pacing.allowance} in this run.` : '.')\n : ''\n switch (state.reason) {\n case 'paced':\n return (\n `${due} Emails go out a few at a time every ${OUTREACH_TICK_MINUTES} minutes, ` +\n 'follow-ups first, so the day’s cap is spread across the send window rather than ' +\n `sent at once. This one goes out in a later run.${pacing}`\n )\n case 'next_run':\n return `${due} The task is created on the next run, within ${OUTREACH_TICK_MINUTES} minutes.`\n case 'window_closed':\n return `${due} The send window is closed; it goes out once the window opens${\n state.opensAtMs ? `, ${time(state.opensAtMs)}` : ''\n }.${pacing}`\n case 'daily_cap_reached':\n return `${due}${pacing} Sending resumes${\n state.opensAtMs ? ` ${time(state.opensAtMs)}` : ' in the next window'\n }.`\n case 'mailbox_not_sending':\n return `${due} Its mailbox is not sending, so nothing goes out until it is connected and resumed.`\n case 'sequence_not_active':\n return `${due} The sequence is not active, so nothing goes out until it is activated.`\n }\n}\n\n/**\n * The Mailboxes card's one line on pacing: what has gone today against the\n * cap, and how much the next run may send — the number a due enrollment's\n * \"Queued\" is waiting on.\n */\nexport function outreachPacingNote(\n mailbox: Pick<\n OutreachMailbox,\n 'status' | 'dailyCap' | 'rampStartedAtMs' | 'timezone' | 'window' | 'health'\n >,\n nowMs: number,\n): string {\n const tick = outreachTickAllowance({ mailbox, nowMs })\n if (tick.hold === 'mailbox_not_connected' || tick.hold === 'invalid_timezone') {\n return 'Sends nothing until it is connected and sending.'\n }\n const today = `Today: ${tick.sentToday} of ${tick.dailyCap} sent`\n if (tick.hold === 'daily_cap_reached') return `${today} · cap reached, sending resumes in the next day’s window.`\n if (!isOutreachWindowOpen(nowMs, mailbox.window, mailbox.timezone)) {\n return `${today} · outside its send window.`\n }\n return (\n `${today} · up to ${tick.allowance} per ${OUTREACH_TICK_MINUTES}-minute run now, ` +\n 'follow-ups first, so the rest of the day’s cap is spread across the window.'\n )\n}\n\n/** An enrollment's next send, with the reason on hover when it is queued. */\nexport function OutreachNextSend(props: { state: OutreachNextSendState; timeZone: string | null }) {\n const { state, timeZone } = props\n const label = outreachNextSendLabel(state, timeZone)\n if (state.kind !== 'queued') {\n return (\n <Typography variant=\"body2\" color={state.kind === 'none' ? 'text.secondary' : undefined}>\n {label}\n </Typography>\n )\n }\n return (\n <Tooltip title={outreachNextSendDetail(state, timeZone)} describeChild>\n <Typography\n variant=\"body2\"\n color={\n state.reason === 'mailbox_not_sending' || state.reason === 'sequence_not_active'\n ? 'warning.main'\n : 'text.secondary'\n }\n sx={{ textDecoration: 'underline dotted', textUnderlineOffset: 3, cursor: 'help' }}\n >\n {label}\n </Typography>\n </Tooltip>\n )\n}\n"],"names":["Tooltip","Typography","useEffect","useState","isOutreachWindowOpen","OUTREACH_TICK_MINUTES","outreachTickAllowance","formatOutreachTime","QUEUED_LABELS","paced","next_run","window_closed","daily_cap_reached","mailbox_not_sending","sequence_not_active","useOutreachNowMs","everyMs","nowMs","setNowMs","Date","now","timer","setInterval","clearInterval","outreachNextSendLabel","state","timeZone","kind","dueAtMs","reason","outreachNextSendDetail","time","ms","due","pacing","sentToday","dailyCap","allowance","opensAtMs","outreachPacingNote","mailbox","tick","hold","today","window","timezone","OutreachNextSend","props","label","variant","color","undefined","title","describeChild","sx","textDecoration","textUnderlineOffset","cursor"],"mappings":"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"}
@@ -447,6 +447,8 @@ import { useOutreachSequence, useOutreachSequenceLinks } from "./use-outreach-da
447
447
  // not it counts them now.
448
448
  trackClicks: sequence.settings.trackClicks || ((_sequence_stats = sequence.stats) == null ? void 0 : _sequence_stats.clickTracked) === true,
449
449
  timeZone: (_ref2 = mailbox == null ? void 0 : mailbox.timezone) != null ? _ref2 : null,
450
+ mailbox: props.mailboxes.status === 'ready' ? mailbox : undefined,
451
+ sequence: sequence,
450
452
  api: api,
451
453
  enrollAction: sequence.status === 'active' ? enrollButton : undefined,
452
454
  onOpen: (enrollment)=>navigate(enrollmentHref(enrollment)),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-detail.tsx"],"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 { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport type { ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport {\n mdiAccountMultiplePlusOutline,\n mdiChevronLeft,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Card,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n Tab,\n Tabs,\n Typography,\n} from '@mui/material'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport { useMemo, useState } from 'react'\nimport { validateOutreachOrgSettings } from '../engine/sequence-validation'\nimport type { OutreachSequenceAction } from '../model/outreach-api'\nimport type { OutreachEnrollment } from '../model/outreach.types'\nimport { outreachSequenceLinkReport } from '../model/sequence-report'\nimport {\n outreachMailboxActivationIssue,\n outreachSequenceMailboxState,\n type OutreachSequenceIssue,\n type OutreachSequenceMailboxState,\n} from '../model/sequence-draft'\nimport { OutreachEnrollDialog } from './enroll-dialog'\nimport {\n outreachClauseLivesInUrl,\n outreachEnrollmentFilterSearch,\n outreachEnrollmentsFilterHref,\n outreachEnrollmentUrlClauses,\n} from './enrollment-filter-params'\nimport { OutreachEnrollmentsTable } from './enrollments-table'\nimport {\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { OutreachSequenceReportCard } from './sequence-report-card'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport {\n useOutreachSequence,\n useOutreachSequenceLinks,\n} from './use-outreach-data'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport type OutreachSequenceTab = 'steps' | 'enrollments'\n\nexport interface OutreachSequenceDetailProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The organization document the shell loaded: whose consent groups name a site's contacts. */\n org?: Record<string, unknown> | null\n /** Where the Sequences section lives: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n sequenceId: string\n tab: OutreachSequenceTab\n settings: OutreachSettingsLoad\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section: where a mailbox is connected, resumed or reconnected. */\n mailboxesPath: string\n /** The Compliance section: where the footer's legal name and address are set. */\n compliancePath: string\n}\n\n/** Something activation would refuse, and where it is put right. */\nexport interface OutreachActivationBlocker {\n message: string\n href: string\n linkLabel: string\n}\n\n/**\n * What stops a sequence being activated, read before anyone clicks: its\n * mailbox not sending, and a footer the organization cannot write yet —\n * each in the words the status route refuses with. Only a finished read\n * says something is missing, so nothing is claimed while one is loading.\n */\nexport function outreachActivationBlockers(input: {\n sequence: { mailboxId: string }\n mailboxes: OutreachMailboxesResult\n settings: OutreachSettingsLoad\n paths: { steps: string; mailboxes: string; compliance: string }\n}): OutreachActivationBlocker[] {\n const blockers: OutreachActivationBlocker[] = []\n if (input.mailboxes.status === 'ready') {\n const mailbox =\n input.mailboxes.mailboxes.find((entry) => entry.id === input.sequence.mailboxId) ?? null\n const issue = outreachMailboxActivationIssue(input.sequence.mailboxId, mailbox)\n if (issue) {\n blockers.push(\n issue.code === 'mailbox_not_sending'\n ? { message: issue.message, href: input.paths.mailboxes, linkLabel: 'Open Mailboxes' }\n : { message: issue.message, href: input.paths.steps, linkLabel: 'Choose a mailbox' },\n )\n }\n }\n if (input.settings.status === 'ready') {\n for (const issue of validateOutreachOrgSettings(input.settings.settings)) {\n blockers.push({ message: issue.message, href: input.paths.compliance, linkLabel: 'Open Compliance' })\n }\n }\n return blockers\n}\n\n/** What an active sequence's page says while its mailbox is not sending. */\nconst NOT_SENDING: Record<Exclude<OutreachSequenceMailboxState, 'sending'>, string> = {\n none: 'This sequence has no mailbox, so nothing is sent from it.',\n gone: 'This sequence’s mailbox is no longer connected, so nothing is sent from it.',\n paused: 'This sequence’s mailbox is paused, so nothing is sent until it is resumed.',\n reconnect_required:\n 'Google stopped accepting this sequence’s mailbox, so nothing is sent until it is reconnected.',\n}\n\n/** One person's page (AGL-3332): `…/sequences/{id}/enrollments/{enrollmentId}`. */\nexport function outreachEnrollmentPath(\n sectionPath: string,\n sequenceId: string,\n enrollmentId: string,\n): string {\n return `${sectionPath}/${sequenceId}/enrollments/${encodeURIComponent(enrollmentId)}`\n}\n\n/** The words an action's button and its snackbar use. */\nconst ACTIONS: Record<OutreachSequenceAction, { label: string; done: string }> =\n {\n activate: { label: 'Activate', done: 'Sequence activated.' },\n pause: { label: 'Pause', done: 'Sequence paused.' },\n archive: { label: 'Archive', done: 'Sequence archived.' },\n }\n\n/**\n * One sequence (AGL-2980): its steps in the editor, the people enrolled in\n * it, and what can be done with it — activate, pause, archive, delete a\n * draft, and enroll people in an active one.\n */\nexport function OutreachSequenceDetail(props: OutreachSequenceDetailProps) {\n const { orgId, sequenceId, sectionPath } = props\n const api = useOutreachApi(orgId)\n const navigate = useOutreachNavigate()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const loaded = useOutreachSequence(orgId, sequenceId)\n // One document however large the sequence, and read on both tabs, because\n // the report card sits above them (AGL-3239).\n const links = useOutreachSequenceLinks(orgId, sequenceId)\n const [busy, setBusy] = useState(false)\n const [refusal, setRefusal] = useState<{\n message: string\n issues: OutreachSequenceIssue[]\n } | null>(null)\n const [confirming, setConfirming] = useState<'archive' | 'delete' | null>(\n null,\n )\n const [enrolling, setEnrolling] = useState(false)\n\n /*\n * The enrollments table's filter (AGL-3332). \"Who clicked\" and \"who\n * followed this destination\" live in the URL, where the Results card\n * sets them and where back from a person finds them again; every other\n * clause the panel sets is the page's own.\n */\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const search = searchParams?.toString() ?? ''\n const urlClauses = useMemo(\n () => outreachEnrollmentUrlClauses(new URLSearchParams(search)),\n [search],\n )\n const [ownClauses, setOwnClauses] = useState<ListFilterClause[]>([])\n const clauses = useMemo(() => [...ownClauses, ...urlClauses], [ownClauses, urlClauses])\n const setClauses = (next: ListFilterClause[]) => {\n setOwnClauses(next.filter((clause) => !outreachClauseLivesInUrl(clause)))\n const nextSearch = outreachEnrollmentFilterSearch(next, search)\n if (nextSearch !== search) {\n router.replace(nextSearch ? `${pathname}?${nextSearch}` : pathname, { scroll: false })\n }\n }\n const linkOptions = useMemo(\n () =>\n outreachSequenceLinkReport(\n links.status === 'ready' ? (links.data ?? undefined) : undefined,\n ).rows.map((row) => ({ value: row.url, label: row.url })),\n [links],\n )\n\n const sequence = loaded.data\n const detailPath = `${sectionPath}/${sequenceId}`\n const enrollmentsPath = `${detailPath}/enrollments`\n const enrollmentHref = (enrollment: OutreachEnrollment) =>\n outreachEnrollmentPath(sectionPath, sequenceId, enrollment.id)\n const back = (\n <OutreachLink href={sectionPath}>\n <Stack\n direction=\"row\"\n spacing={0.5}\n component=\"span\"\n sx={{ alignItems: 'center', display: 'inline-flex' }}\n >\n <MdiIcon path={mdiChevronLeft.path} fontSize=\"small\" />\n <span>Sequences</span>\n </Stack>\n </OutreachLink>\n )\n\n if (loaded.status === 'loading')\n return <OutreachLoading label=\"Loading the sequence…\" />\n if (loaded.status === 'error' || loaded.status === 'refused') {\n return <OutreachLoadProblem status={loaded.status} what=\"sequence\" />\n }\n if (!sequence) {\n return (\n <Stack spacing={2}>\n {back}\n <Card variant=\"outlined\">\n <EmptyStateComponent label=\"This sequence no longer exists\" />\n </Card>\n </Stack>\n )\n }\n\n const mailbox =\n props.mailboxes.mailboxes.find((entry) => entry.id === sequence.mailboxId) ??\n null\n const mailboxState =\n props.mailboxes.status === 'ready'\n ? outreachSequenceMailboxState(sequence.mailboxId, mailbox)\n : 'sending'\n const activatable = sequence.status === 'draft' || sequence.status === 'paused'\n const blockers = activatable\n ? outreachActivationBlockers({\n sequence,\n mailboxes: props.mailboxes,\n settings: props.settings,\n paths: {\n steps: detailPath,\n mailboxes: props.mailboxesPath,\n compliance: props.compliancePath,\n },\n })\n : []\n // Enrolling onto a mailbox that is gone is refused by the route; a paused\n // one only makes the new people wait with everyone else.\n const canEnroll =\n sequence.status === 'active' &&\n mailboxState !== 'gone' &&\n mailboxState !== 'none'\n const act = async (action: OutreachSequenceAction) => {\n setBusy(true)\n setRefusal(null)\n try {\n const answer = await api.setSequenceStatus(sequence.id, action)\n enqueueSnackbar(\n action === 'archive' && answer.stoppedEnrollments\n ? `${ACTIONS.archive.done} ${answer.stoppedEnrollments} ${answer.stoppedEnrollments === 1 ? 'person was' : 'people were'} stopped.`\n : ACTIONS[action].done,\n { variant: 'success' },\n )\n } catch (error) {\n setRefusal({\n message: (error as Error).message,\n issues:\n error instanceof OutreachRouteError\n ? error.issues.filter(\n (issue): issue is OutreachSequenceIssue => 'path' in issue,\n )\n : [],\n })\n } finally {\n setBusy(false)\n }\n }\n const remove = async () => {\n setBusy(true)\n setRefusal(null)\n try {\n await api.deleteSequence(sequence.id)\n enqueueSnackbar('Sequence deleted.', { variant: 'success' })\n navigate(sectionPath)\n } catch (error) {\n setRefusal({ message: (error as Error).message, issues: [] })\n setBusy(false)\n }\n }\n\n const enrollButton = (\n <Button\n variant=\"contained\"\n startIcon={\n <MdiIcon path={mdiAccountMultiplePlusOutline.path} fontSize=\"small\" />\n }\n disabled={!canEnroll}\n onClick={() => setEnrolling(true)}\n >\n Enroll people\n </Button>\n )\n\n return (\n <Stack spacing={2}>\n {back}\n <Stack\n direction={{ xs: 'column', md: 'row' }}\n spacing={1.5}\n sx={{ alignItems: { md: 'center' }, justifyContent: 'space-between' }}\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', minWidth: 0 }}\n >\n <Typography\n variant=\"h5\"\n component=\"h2\"\n sx={{ wordBreak: 'break-word' }}\n >\n {sequence.name}\n </Typography>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n {activatable ? (\n <Button\n variant=\"outlined\"\n disabled={busy || blockers.length > 0}\n onClick={() => void act('activate')}\n >\n {ACTIONS.activate.label}\n </Button>\n ) : null}\n {sequence.status === 'active' ? (\n <Button\n variant=\"outlined\"\n disabled={busy}\n onClick={() => void act('pause')}\n >\n {ACTIONS.pause.label}\n </Button>\n ) : null}\n {sequence.status !== 'archived' ? (\n <Button\n variant=\"outlined\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('archive')}\n >\n {ACTIONS.archive.label}\n </Button>\n ) : null}\n {sequence.status === 'draft' ? (\n <Button\n variant=\"text\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('delete')}\n >\n Delete\n </Button>\n ) : null}\n {sequence.status === 'active' ? enrollButton : null}\n </Stack>\n </Stack>\n\n {refusal ? (\n <Alert severity=\"error\" onClose={() => setRefusal(null)}>\n <Stack spacing={0.5}>\n <span>{refusal.message}</span>\n {refusal.issues.length > 1 ? (\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {refusal.issues.slice(1).map((issue) => (\n <li key={`${issue.path}:${issue.code}`}>{issue.message}</li>\n ))}\n </Stack>\n ) : null}\n </Stack>\n </Alert>\n ) : null}\n {blockers.length ? (\n <Alert severity=\"warning\" data-testid=\"outreach-activation-blockers\">\n <Stack spacing={0.5}>\n <span>\n {sequence.status === 'draft'\n ? 'A draft sends nothing, and this one can’t be activated yet:'\n : 'This sequence can’t be activated again yet:'}\n </span>\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {blockers.map((blocker) => (\n <li key={blocker.message}>\n {blocker.message}{' '}\n <OutreachLink href={blocker.href}>{blocker.linkLabel}</OutreachLink>\n </li>\n ))}\n </Stack>\n </Stack>\n </Alert>\n ) : sequence.status === 'draft' ? (\n <Alert severity=\"info\">\n A draft sends nothing. Activate it to enroll people.\n </Alert>\n ) : null}\n {sequence.status === 'active' && mailboxState !== 'sending' ? (\n <Alert severity=\"warning\">\n {NOT_SENDING[mailboxState]}{' '}\n <OutreachLink href={props.mailboxesPath}>Open Mailboxes</OutreachLink>\n </Alert>\n ) : null}\n\n {/*\n * Above the tabs, not inside one (AGL-3239): what a sequence is doing\n * is the question a rep opens it with, and putting it behind a tab\n * makes the steps the first answer to \"how is this going\".\n *\n * A draft has sent nothing and measured nothing, so it gets no card\n * rather than a row of zeroes.\n */}\n {sequence.status === 'draft' ? null : (\n <OutreachSequenceReportCard\n sequence={sequence}\n links={links}\n timeZone={mailbox?.timezone ?? null}\n // Each leads to the people behind it, on the Enrollments tab's own\n // filter (AGL-3332).\n onShowClickers={() =>\n navigate(outreachEnrollmentsFilterHref(enrollmentsPath, { clicked: true }))\n }\n onShowLink={(url) =>\n navigate(outreachEnrollmentsFilterHref(enrollmentsPath, { link: url }))\n }\n />\n )}\n\n <Tabs\n value={props.tab}\n onChange={(_event, next: OutreachSequenceTab) =>\n navigate(next === 'steps' ? detailPath : enrollmentsPath)\n }\n aria-label=\"Sequence\"\n >\n <Tab value=\"steps\" label=\"Steps\" />\n <Tab value=\"enrollments\" label=\"Enrollments\" />\n </Tabs>\n\n {props.tab === 'steps' ? (\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n org={props.org}\n sequence={sequence}\n settings={props.settings}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={() => undefined}\n />\n ) : (\n <OutreachEnrollmentsTable\n orgId={orgId}\n sequenceId={sequence.id}\n steps={sequence.steps}\n // A sequence that counted clicks once has them to show, whether or\n // not it counts them now.\n trackClicks={sequence.settings.trackClicks || sequence.stats?.clickTracked === true}\n timeZone={mailbox?.timezone ?? null}\n api={api}\n enrollAction={sequence.status === 'active' ? enrollButton : undefined}\n onOpen={(enrollment) => navigate(enrollmentHref(enrollment))}\n hrefFor={enrollmentHref}\n clauses={clauses}\n onClausesChange={setClauses}\n linkOptions={linkOptions}\n />\n )}\n\n <OutreachEnrollDialog\n open={enrolling}\n onClose={() => setEnrolling(false)}\n orgId={orgId}\n sequence={sequence}\n contactGroupId={\n sequence.hostId\n ? consentGroupForHost(props.org ?? null, sequence.hostId).groupId\n : ''\n }\n uid={user?.uid ?? null}\n api={api}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n <DialogTitle>\n {confirming === 'delete'\n ? 'Delete this draft?'\n : 'Archive this sequence?'}\n </DialogTitle>\n <DialogContent>\n <DialogContentText>\n {confirming === 'delete'\n ? 'It is removed for good. Nobody was ever enrolled in it.'\n : 'Archiving is final. Everyone still in it is stopped, and it takes no one new.'}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const chosen = confirming\n setConfirming(null)\n if (chosen === 'delete') void remove()\n else void act('archive')\n }}\n >\n {confirming === 'delete' ? 'Delete' : 'Archive'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nOutreachSequenceDetail.displayName = 'OutreachSequenceDetail'\n\nexport default OutreachSequenceDetail\n"],"names":["consentGroupForHost","mdiAccountMultiplePlusOutline","mdiChevronLeft","MdiIcon","useSnackbar","useUser","Alert","Button","Card","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","Tab","Tabs","Typography","EmptyStateComponent","usePathname","useRouter","useSearchParams","useMemo","useState","validateOutreachOrgSettings","outreachSequenceLinkReport","outreachMailboxActivationIssue","outreachSequenceMailboxState","OutreachEnrollDialog","outreachClauseLivesInUrl","outreachEnrollmentFilterSearch","outreachEnrollmentsFilterHref","outreachEnrollmentUrlClauses","OutreachEnrollmentsTable","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachSequenceEditor","OutreachSequenceReportCard","OutreachRouteError","useOutreachApi","useOutreachSequence","useOutreachSequenceLinks","outreachActivationBlockers","input","blockers","mailboxes","status","mailbox","find","entry","id","sequence","mailboxId","issue","push","code","message","href","paths","linkLabel","steps","settings","compliance","NOT_SENDING","none","gone","paused","reconnect_required","outreachEnrollmentPath","sectionPath","sequenceId","enrollmentId","encodeURIComponent","ACTIONS","activate","label","done","pause","archive","OutreachSequenceDetail","props","orgId","api","navigate","enqueueSnackbar","data","user","loaded","links","busy","setBusy","refusal","setRefusal","confirming","setConfirming","enrolling","setEnrolling","router","pathname","searchParams","search","toString","urlClauses","URLSearchParams","ownClauses","setOwnClauses","clauses","setClauses","next","filter","clause","nextSearch","replace","scroll","linkOptions","undefined","rows","map","row","value","url","detailPath","enrollmentsPath","enrollmentHref","enrollment","back","direction","spacing","component","sx","alignItems","display","path","fontSize","span","what","variant","mailboxState","activatable","mailboxesPath","compliancePath","canEnroll","act","action","answer","setSequenceStatus","stoppedEnrollments","error","issues","remove","deleteSequence","enrollButton","startIcon","disabled","onClick","xs","md","justifyContent","minWidth","wordBreak","name","flexWrap","rowGap","length","color","severity","onClose","m","pl","slice","li","data-testid","blocker","timeZone","timezone","onShowClickers","clicked","onShowLink","link","tab","onChange","_event","aria-label","orgMount","org","onSaved","trackClicks","stats","clickTracked","enrollAction","onOpen","hrefFor","onClausesChange","open","contactGroupId","hostId","groupId","uid","Boolean","fullWidth","maxWidth","chosen","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-detail.tsx"],"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 { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport type { ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport {\n mdiAccountMultiplePlusOutline,\n mdiChevronLeft,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Card,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n Tab,\n Tabs,\n Typography,\n} from '@mui/material'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport { useMemo, useState } from 'react'\nimport { validateOutreachOrgSettings } from '../engine/sequence-validation'\nimport type { OutreachSequenceAction } from '../model/outreach-api'\nimport type { OutreachEnrollment } from '../model/outreach.types'\nimport { outreachSequenceLinkReport } from '../model/sequence-report'\nimport {\n outreachMailboxActivationIssue,\n outreachSequenceMailboxState,\n type OutreachSequenceIssue,\n type OutreachSequenceMailboxState,\n} from '../model/sequence-draft'\nimport { OutreachEnrollDialog } from './enroll-dialog'\nimport {\n outreachClauseLivesInUrl,\n outreachEnrollmentFilterSearch,\n outreachEnrollmentsFilterHref,\n outreachEnrollmentUrlClauses,\n} from './enrollment-filter-params'\nimport { OutreachEnrollmentsTable } from './enrollments-table'\nimport {\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { OutreachSequenceReportCard } from './sequence-report-card'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport {\n useOutreachSequence,\n useOutreachSequenceLinks,\n} from './use-outreach-data'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport type OutreachSequenceTab = 'steps' | 'enrollments'\n\nexport interface OutreachSequenceDetailProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The organization document the shell loaded: whose consent groups name a site's contacts. */\n org?: Record<string, unknown> | null\n /** Where the Sequences section lives: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n sequenceId: string\n tab: OutreachSequenceTab\n settings: OutreachSettingsLoad\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section: where a mailbox is connected, resumed or reconnected. */\n mailboxesPath: string\n /** The Compliance section: where the footer's legal name and address are set. */\n compliancePath: string\n}\n\n/** Something activation would refuse, and where it is put right. */\nexport interface OutreachActivationBlocker {\n message: string\n href: string\n linkLabel: string\n}\n\n/**\n * What stops a sequence being activated, read before anyone clicks: its\n * mailbox not sending, and a footer the organization cannot write yet —\n * each in the words the status route refuses with. Only a finished read\n * says something is missing, so nothing is claimed while one is loading.\n */\nexport function outreachActivationBlockers(input: {\n sequence: { mailboxId: string }\n mailboxes: OutreachMailboxesResult\n settings: OutreachSettingsLoad\n paths: { steps: string; mailboxes: string; compliance: string }\n}): OutreachActivationBlocker[] {\n const blockers: OutreachActivationBlocker[] = []\n if (input.mailboxes.status === 'ready') {\n const mailbox =\n input.mailboxes.mailboxes.find((entry) => entry.id === input.sequence.mailboxId) ?? null\n const issue = outreachMailboxActivationIssue(input.sequence.mailboxId, mailbox)\n if (issue) {\n blockers.push(\n issue.code === 'mailbox_not_sending'\n ? { message: issue.message, href: input.paths.mailboxes, linkLabel: 'Open Mailboxes' }\n : { message: issue.message, href: input.paths.steps, linkLabel: 'Choose a mailbox' },\n )\n }\n }\n if (input.settings.status === 'ready') {\n for (const issue of validateOutreachOrgSettings(input.settings.settings)) {\n blockers.push({ message: issue.message, href: input.paths.compliance, linkLabel: 'Open Compliance' })\n }\n }\n return blockers\n}\n\n/** What an active sequence's page says while its mailbox is not sending. */\nconst NOT_SENDING: Record<Exclude<OutreachSequenceMailboxState, 'sending'>, string> = {\n none: 'This sequence has no mailbox, so nothing is sent from it.',\n gone: 'This sequence’s mailbox is no longer connected, so nothing is sent from it.',\n paused: 'This sequence’s mailbox is paused, so nothing is sent until it is resumed.',\n reconnect_required:\n 'Google stopped accepting this sequence’s mailbox, so nothing is sent until it is reconnected.',\n}\n\n/** One person's page (AGL-3332): `…/sequences/{id}/enrollments/{enrollmentId}`. */\nexport function outreachEnrollmentPath(\n sectionPath: string,\n sequenceId: string,\n enrollmentId: string,\n): string {\n return `${sectionPath}/${sequenceId}/enrollments/${encodeURIComponent(enrollmentId)}`\n}\n\n/** The words an action's button and its snackbar use. */\nconst ACTIONS: Record<OutreachSequenceAction, { label: string; done: string }> =\n {\n activate: { label: 'Activate', done: 'Sequence activated.' },\n pause: { label: 'Pause', done: 'Sequence paused.' },\n archive: { label: 'Archive', done: 'Sequence archived.' },\n }\n\n/**\n * One sequence (AGL-2980): its steps in the editor, the people enrolled in\n * it, and what can be done with it — activate, pause, archive, delete a\n * draft, and enroll people in an active one.\n */\nexport function OutreachSequenceDetail(props: OutreachSequenceDetailProps) {\n const { orgId, sequenceId, sectionPath } = props\n const api = useOutreachApi(orgId)\n const navigate = useOutreachNavigate()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const loaded = useOutreachSequence(orgId, sequenceId)\n // One document however large the sequence, and read on both tabs, because\n // the report card sits above them (AGL-3239).\n const links = useOutreachSequenceLinks(orgId, sequenceId)\n const [busy, setBusy] = useState(false)\n const [refusal, setRefusal] = useState<{\n message: string\n issues: OutreachSequenceIssue[]\n } | null>(null)\n const [confirming, setConfirming] = useState<'archive' | 'delete' | null>(\n null,\n )\n const [enrolling, setEnrolling] = useState(false)\n\n /*\n * The enrollments table's filter (AGL-3332). \"Who clicked\" and \"who\n * followed this destination\" live in the URL, where the Results card\n * sets them and where back from a person finds them again; every other\n * clause the panel sets is the page's own.\n */\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const search = searchParams?.toString() ?? ''\n const urlClauses = useMemo(\n () => outreachEnrollmentUrlClauses(new URLSearchParams(search)),\n [search],\n )\n const [ownClauses, setOwnClauses] = useState<ListFilterClause[]>([])\n const clauses = useMemo(() => [...ownClauses, ...urlClauses], [ownClauses, urlClauses])\n const setClauses = (next: ListFilterClause[]) => {\n setOwnClauses(next.filter((clause) => !outreachClauseLivesInUrl(clause)))\n const nextSearch = outreachEnrollmentFilterSearch(next, search)\n if (nextSearch !== search) {\n router.replace(nextSearch ? `${pathname}?${nextSearch}` : pathname, { scroll: false })\n }\n }\n const linkOptions = useMemo(\n () =>\n outreachSequenceLinkReport(\n links.status === 'ready' ? (links.data ?? undefined) : undefined,\n ).rows.map((row) => ({ value: row.url, label: row.url })),\n [links],\n )\n\n const sequence = loaded.data\n const detailPath = `${sectionPath}/${sequenceId}`\n const enrollmentsPath = `${detailPath}/enrollments`\n const enrollmentHref = (enrollment: OutreachEnrollment) =>\n outreachEnrollmentPath(sectionPath, sequenceId, enrollment.id)\n const back = (\n <OutreachLink href={sectionPath}>\n <Stack\n direction=\"row\"\n spacing={0.5}\n component=\"span\"\n sx={{ alignItems: 'center', display: 'inline-flex' }}\n >\n <MdiIcon path={mdiChevronLeft.path} fontSize=\"small\" />\n <span>Sequences</span>\n </Stack>\n </OutreachLink>\n )\n\n if (loaded.status === 'loading')\n return <OutreachLoading label=\"Loading the sequence…\" />\n if (loaded.status === 'error' || loaded.status === 'refused') {\n return <OutreachLoadProblem status={loaded.status} what=\"sequence\" />\n }\n if (!sequence) {\n return (\n <Stack spacing={2}>\n {back}\n <Card variant=\"outlined\">\n <EmptyStateComponent label=\"This sequence no longer exists\" />\n </Card>\n </Stack>\n )\n }\n\n const mailbox =\n props.mailboxes.mailboxes.find((entry) => entry.id === sequence.mailboxId) ??\n null\n const mailboxState =\n props.mailboxes.status === 'ready'\n ? outreachSequenceMailboxState(sequence.mailboxId, mailbox)\n : 'sending'\n const activatable = sequence.status === 'draft' || sequence.status === 'paused'\n const blockers = activatable\n ? outreachActivationBlockers({\n sequence,\n mailboxes: props.mailboxes,\n settings: props.settings,\n paths: {\n steps: detailPath,\n mailboxes: props.mailboxesPath,\n compliance: props.compliancePath,\n },\n })\n : []\n // Enrolling onto a mailbox that is gone is refused by the route; a paused\n // one only makes the new people wait with everyone else.\n const canEnroll =\n sequence.status === 'active' &&\n mailboxState !== 'gone' &&\n mailboxState !== 'none'\n const act = async (action: OutreachSequenceAction) => {\n setBusy(true)\n setRefusal(null)\n try {\n const answer = await api.setSequenceStatus(sequence.id, action)\n enqueueSnackbar(\n action === 'archive' && answer.stoppedEnrollments\n ? `${ACTIONS.archive.done} ${answer.stoppedEnrollments} ${answer.stoppedEnrollments === 1 ? 'person was' : 'people were'} stopped.`\n : ACTIONS[action].done,\n { variant: 'success' },\n )\n } catch (error) {\n setRefusal({\n message: (error as Error).message,\n issues:\n error instanceof OutreachRouteError\n ? error.issues.filter(\n (issue): issue is OutreachSequenceIssue => 'path' in issue,\n )\n : [],\n })\n } finally {\n setBusy(false)\n }\n }\n const remove = async () => {\n setBusy(true)\n setRefusal(null)\n try {\n await api.deleteSequence(sequence.id)\n enqueueSnackbar('Sequence deleted.', { variant: 'success' })\n navigate(sectionPath)\n } catch (error) {\n setRefusal({ message: (error as Error).message, issues: [] })\n setBusy(false)\n }\n }\n\n const enrollButton = (\n <Button\n variant=\"contained\"\n startIcon={\n <MdiIcon path={mdiAccountMultiplePlusOutline.path} fontSize=\"small\" />\n }\n disabled={!canEnroll}\n onClick={() => setEnrolling(true)}\n >\n Enroll people\n </Button>\n )\n\n return (\n <Stack spacing={2}>\n {back}\n <Stack\n direction={{ xs: 'column', md: 'row' }}\n spacing={1.5}\n sx={{ alignItems: { md: 'center' }, justifyContent: 'space-between' }}\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', minWidth: 0 }}\n >\n <Typography\n variant=\"h5\"\n component=\"h2\"\n sx={{ wordBreak: 'break-word' }}\n >\n {sequence.name}\n </Typography>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n {activatable ? (\n <Button\n variant=\"outlined\"\n disabled={busy || blockers.length > 0}\n onClick={() => void act('activate')}\n >\n {ACTIONS.activate.label}\n </Button>\n ) : null}\n {sequence.status === 'active' ? (\n <Button\n variant=\"outlined\"\n disabled={busy}\n onClick={() => void act('pause')}\n >\n {ACTIONS.pause.label}\n </Button>\n ) : null}\n {sequence.status !== 'archived' ? (\n <Button\n variant=\"outlined\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('archive')}\n >\n {ACTIONS.archive.label}\n </Button>\n ) : null}\n {sequence.status === 'draft' ? (\n <Button\n variant=\"text\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('delete')}\n >\n Delete\n </Button>\n ) : null}\n {sequence.status === 'active' ? enrollButton : null}\n </Stack>\n </Stack>\n\n {refusal ? (\n <Alert severity=\"error\" onClose={() => setRefusal(null)}>\n <Stack spacing={0.5}>\n <span>{refusal.message}</span>\n {refusal.issues.length > 1 ? (\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {refusal.issues.slice(1).map((issue) => (\n <li key={`${issue.path}:${issue.code}`}>{issue.message}</li>\n ))}\n </Stack>\n ) : null}\n </Stack>\n </Alert>\n ) : null}\n {blockers.length ? (\n <Alert severity=\"warning\" data-testid=\"outreach-activation-blockers\">\n <Stack spacing={0.5}>\n <span>\n {sequence.status === 'draft'\n ? 'A draft sends nothing, and this one can’t be activated yet:'\n : 'This sequence can’t be activated again yet:'}\n </span>\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {blockers.map((blocker) => (\n <li key={blocker.message}>\n {blocker.message}{' '}\n <OutreachLink href={blocker.href}>{blocker.linkLabel}</OutreachLink>\n </li>\n ))}\n </Stack>\n </Stack>\n </Alert>\n ) : sequence.status === 'draft' ? (\n <Alert severity=\"info\">\n A draft sends nothing. Activate it to enroll people.\n </Alert>\n ) : null}\n {sequence.status === 'active' && mailboxState !== 'sending' ? (\n <Alert severity=\"warning\">\n {NOT_SENDING[mailboxState]}{' '}\n <OutreachLink href={props.mailboxesPath}>Open Mailboxes</OutreachLink>\n </Alert>\n ) : null}\n\n {/*\n * Above the tabs, not inside one (AGL-3239): what a sequence is doing\n * is the question a rep opens it with, and putting it behind a tab\n * makes the steps the first answer to \"how is this going\".\n *\n * A draft has sent nothing and measured nothing, so it gets no card\n * rather than a row of zeroes.\n */}\n {sequence.status === 'draft' ? null : (\n <OutreachSequenceReportCard\n sequence={sequence}\n links={links}\n timeZone={mailbox?.timezone ?? null}\n // Each leads to the people behind it, on the Enrollments tab's own\n // filter (AGL-3332).\n onShowClickers={() =>\n navigate(outreachEnrollmentsFilterHref(enrollmentsPath, { clicked: true }))\n }\n onShowLink={(url) =>\n navigate(outreachEnrollmentsFilterHref(enrollmentsPath, { link: url }))\n }\n />\n )}\n\n <Tabs\n value={props.tab}\n onChange={(_event, next: OutreachSequenceTab) =>\n navigate(next === 'steps' ? detailPath : enrollmentsPath)\n }\n aria-label=\"Sequence\"\n >\n <Tab value=\"steps\" label=\"Steps\" />\n <Tab value=\"enrollments\" label=\"Enrollments\" />\n </Tabs>\n\n {props.tab === 'steps' ? (\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n org={props.org}\n sequence={sequence}\n settings={props.settings}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={() => undefined}\n />\n ) : (\n <OutreachEnrollmentsTable\n orgId={orgId}\n sequenceId={sequence.id}\n steps={sequence.steps}\n // A sequence that counted clicks once has them to show, whether or\n // not it counts them now.\n trackClicks={sequence.settings.trackClicks || sequence.stats?.clickTracked === true}\n timeZone={mailbox?.timezone ?? null}\n mailbox={props.mailboxes.status === 'ready' ? mailbox : undefined}\n sequence={sequence}\n api={api}\n enrollAction={sequence.status === 'active' ? enrollButton : undefined}\n onOpen={(enrollment) => navigate(enrollmentHref(enrollment))}\n hrefFor={enrollmentHref}\n clauses={clauses}\n onClausesChange={setClauses}\n linkOptions={linkOptions}\n />\n )}\n\n <OutreachEnrollDialog\n open={enrolling}\n onClose={() => setEnrolling(false)}\n orgId={orgId}\n sequence={sequence}\n contactGroupId={\n sequence.hostId\n ? consentGroupForHost(props.org ?? null, sequence.hostId).groupId\n : ''\n }\n uid={user?.uid ?? null}\n api={api}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n <DialogTitle>\n {confirming === 'delete'\n ? 'Delete this draft?'\n : 'Archive this sequence?'}\n </DialogTitle>\n <DialogContent>\n <DialogContentText>\n {confirming === 'delete'\n ? 'It is removed for good. Nobody was ever enrolled in it.'\n : 'Archiving is final. Everyone still in it is stopped, and it takes no one new.'}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const chosen = confirming\n setConfirming(null)\n if (chosen === 'delete') void remove()\n else void act('archive')\n }}\n >\n {confirming === 'delete' ? 'Delete' : 'Archive'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nOutreachSequenceDetail.displayName = 'OutreachSequenceDetail'\n\nexport default OutreachSequenceDetail\n"],"names":["consentGroupForHost","mdiAccountMultiplePlusOutline","mdiChevronLeft","MdiIcon","useSnackbar","useUser","Alert","Button","Card","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","Tab","Tabs","Typography","EmptyStateComponent","usePathname","useRouter","useSearchParams","useMemo","useState","validateOutreachOrgSettings","outreachSequenceLinkReport","outreachMailboxActivationIssue","outreachSequenceMailboxState","OutreachEnrollDialog","outreachClauseLivesInUrl","outreachEnrollmentFilterSearch","outreachEnrollmentsFilterHref","outreachEnrollmentUrlClauses","OutreachEnrollmentsTable","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachSequenceEditor","OutreachSequenceReportCard","OutreachRouteError","useOutreachApi","useOutreachSequence","useOutreachSequenceLinks","outreachActivationBlockers","input","blockers","mailboxes","status","mailbox","find","entry","id","sequence","mailboxId","issue","push","code","message","href","paths","linkLabel","steps","settings","compliance","NOT_SENDING","none","gone","paused","reconnect_required","outreachEnrollmentPath","sectionPath","sequenceId","enrollmentId","encodeURIComponent","ACTIONS","activate","label","done","pause","archive","OutreachSequenceDetail","props","orgId","api","navigate","enqueueSnackbar","data","user","loaded","links","busy","setBusy","refusal","setRefusal","confirming","setConfirming","enrolling","setEnrolling","router","pathname","searchParams","search","toString","urlClauses","URLSearchParams","ownClauses","setOwnClauses","clauses","setClauses","next","filter","clause","nextSearch","replace","scroll","linkOptions","undefined","rows","map","row","value","url","detailPath","enrollmentsPath","enrollmentHref","enrollment","back","direction","spacing","component","sx","alignItems","display","path","fontSize","span","what","variant","mailboxState","activatable","mailboxesPath","compliancePath","canEnroll","act","action","answer","setSequenceStatus","stoppedEnrollments","error","issues","remove","deleteSequence","enrollButton","startIcon","disabled","onClick","xs","md","justifyContent","minWidth","wordBreak","name","flexWrap","rowGap","length","color","severity","onClose","m","pl","slice","li","data-testid","blocker","timeZone","timezone","onShowClickers","clicked","onShowLink","link","tab","onChange","_event","aria-label","orgMount","org","onSaved","trackClicks","stats","clickTracked","enrollAction","onOpen","hrefFor","onClausesChange","open","contactGroupId","hostId","groupId","uid","Boolean","fullWidth","maxWidth","chosen","displayName"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { OutreachApiRefusalReason, OutreachConnectCompleteResponse, OutreachMailboxAvailability, OutreachMailboxDisconnectResponse, OutreachMailboxResponse, OutreachMailboxSettingsRequest, OutreachMailboxTestResponse } from '../mailboxes/mailbox-api';
1
+ import type { OutreachApiRefusalReason, OutreachConnectCompleteResponse, OutreachMailboxAvailability, OutreachMailboxDisconnectResponse, OutreachMailboxReadinessResponse, OutreachMailboxResponse, OutreachMailboxSettingsRequest, OutreachMailboxTestResponse } from '../mailboxes/mailbox-api';
2
2
  /** A mailbox route's refusal, carrying the sentence and the stable reason. */
3
3
  export declare class OutreachApiError extends Error {
4
4
  readonly reason: OutreachApiRefusalReason | 'unreachable';
@@ -20,6 +20,8 @@ export interface OutreachMailboxApi {
20
20
  /** A test from the mailbox — to `to`, or to the account's own address. */
21
21
  sendTest(mailboxId: string, to?: string): Promise<OutreachMailboxTestResponse>;
22
22
  disconnect(mailboxId: string): Promise<OutreachMailboxDisconnectResponse>;
23
+ /** SPF, DKIM and DMARC for the address it sends as; `fresh` skips the server's brief memory. */
24
+ readiness(mailboxId: string, fresh?: boolean): Promise<OutreachMailboxReadinessResponse>;
23
25
  }
24
26
  /**
25
27
  * The Mailboxes panel's one door to the mailbox routes (AGL-2978), with the
@@ -94,6 +94,14 @@ import { OUTREACH_API_ROUTES } from "../constants/api-routes.js";
94
94
  body: {
95
95
  mailboxId
96
96
  }
97
+ }),
98
+ readiness: (mailboxId, fresh)=>call(OUTREACH_API_ROUTES.mailboxesReadiness, {
99
+ method: 'POST',
100
+ body: _extends({
101
+ mailboxId
102
+ }, fresh ? {
103
+ fresh: true
104
+ } : {})
97
105
  })
98
106
  }), [
99
107
  call
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/use-outreach-mailbox-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 {\n OutreachApiRefusalReason,\n OutreachConnectCompleteResponse,\n OutreachConnectResponse,\n OutreachMailboxAvailability,\n OutreachMailboxDisconnectResponse,\n OutreachMailboxResponse,\n OutreachMailboxSettingsRequest,\n OutreachMailboxTestResponse,\n} from '../mailboxes/mailbox-api'\n\n/** A mailbox route's refusal, carrying the sentence and the stable reason. */\nexport class OutreachApiError extends Error {\n readonly reason: OutreachApiRefusalReason | 'unreachable'\n readonly status: number\n\n constructor(message: string, reason: OutreachApiRefusalReason | 'unreachable', status: number) {\n super(message)\n this.name = 'OutreachApiError'\n this.reason = reason\n this.status = status\n }\n}\n\n/** The mailbox routes, as the Mailboxes panel calls them. */\nexport interface OutreachMailboxApi {\n availability(): Promise<OutreachMailboxAvailability>\n /** Google's consent address for a new connect, or a reconnect. */\n connect(): Promise<string>\n complete(input: { code: string; state: string; timezone?: string }): Promise<OutreachConnectCompleteResponse>\n saveSettings(\n input: Omit<OutreachMailboxSettingsRequest, 'orgId'>,\n ): Promise<OutreachMailboxResponse>\n setPaused(mailboxId: string, paused: boolean): Promise<OutreachMailboxResponse>\n /** A test from the mailbox — to `to`, or to the account's own address. */\n sendTest(mailboxId: string, to?: string): Promise<OutreachMailboxTestResponse>\n disconnect(mailboxId: string): Promise<OutreachMailboxDisconnectResponse>\n}\n\n/**\n * The Mailboxes panel's one door to the mailbox routes (AGL-2978), with the\n * member's session attached and the organization named on every call — the\n * dispatcher's release gate reads it. A refusal is thrown as an\n * {@link OutreachApiError} carrying the route's own sentence, which is\n * written for the person reading it.\n */\nexport function useOutreachMailboxApi(orgId: string | null): OutreachMailboxApi {\n const { data: user } = useUser()\n\n const call = useCallback(\n async <T>(route: string, init: { method: 'GET' | 'POST'; body?: Record<string, unknown> }): Promise<T> => {\n if (!orgId) throw new OutreachApiError('Open an organization first.', 'org-required', 400)\n const path =\n init.method === 'GET'\n ? `/api/${route}?orgId=${encodeURIComponent(orgId)}`\n : `/api/${route}`\n const 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 const payload = (await response.json().catch(() => null)) as\n | (T & { error?: undefined })\n | { error?: string; reason?: OutreachApiRefusalReason }\n | null\n if (!response.ok || !payload) {\n const refusal = (payload ?? {}) as { error?: string; reason?: OutreachApiRefusalReason }\n throw new OutreachApiError(\n refusal.error || 'Sequences could not be reached. Try again.',\n refusal.reason ?? 'unreachable',\n response.status,\n )\n }\n return payload as T\n },\n [user, orgId],\n )\n\n return useMemo<OutreachMailboxApi>(\n () => ({\n availability: () =>\n call<OutreachMailboxAvailability>(OUTREACH_API_ROUTES.mailboxesAvailability, { method: 'GET' }),\n connect: async () =>\n (await call<OutreachConnectResponse>(OUTREACH_API_ROUTES.mailboxesConnect, { method: 'POST' })).url,\n complete: (input) =>\n call<OutreachConnectCompleteResponse>(OUTREACH_API_ROUTES.mailboxesConnectComplete, {\n method: 'POST',\n body: input,\n }),\n saveSettings: (input) =>\n call<OutreachMailboxResponse>(OUTREACH_API_ROUTES.mailboxesSettings, {\n method: 'POST',\n body: input as unknown as Record<string, unknown>,\n }),\n setPaused: (mailboxId, paused) =>\n call<OutreachMailboxResponse>(OUTREACH_API_ROUTES.mailboxesStatus, {\n method: 'POST',\n body: { mailboxId, paused },\n }),\n sendTest: (mailboxId, to) =>\n call<OutreachMailboxTestResponse>(OUTREACH_API_ROUTES.mailboxesTest, {\n method: 'POST',\n body: { mailboxId, ...(to ? { to } : {}) },\n }),\n disconnect: (mailboxId) =>\n call<OutreachMailboxDisconnectResponse>(OUTREACH_API_ROUTES.mailboxesDisconnect, {\n method: 'POST',\n body: { mailboxId },\n }),\n }),\n [call],\n )\n}\n"],"names":["authorizedFetch","useUser","useCallback","useMemo","OUTREACH_API_ROUTES","OutreachApiError","Error","message","reason","status","name","useOutreachMailboxApi","orgId","data","user","call","route","init","path","method","encodeURIComponent","response","headers","body","JSON","stringify","payload","json","catch","ok","refusal","error","availability","mailboxesAvailability","connect","mailboxesConnect","url","complete","input","mailboxesConnectComplete","saveSettings","mailboxesSettings","setPaused","mailboxId","paused","mailboxesStatus","sendTest","to","mailboxesTest","disconnect","mailboxesDisconnect"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,WAAW,EAAEC,OAAO,QAAQ,QAAO;AAC5C,SAASC,mBAAmB,QAAQ,6BAAyB;AAY7D,4EAA4E,GAC5E,OAAO,MAAMC,yBAAyBC;IAIpC,YAAYC,OAAe,EAAEC,MAAgD,EAAEC,MAAc,CAAE;QAC7F,KAAK,CAACF;QACN,IAAI,CAACG,IAAI,GAAG;QACZ,IAAI,CAACF,MAAM,GAAGA;QACd,IAAI,CAACC,MAAM,GAAGA;IAChB;AACF;AAiBA;;;;;;CAMC,GACD,OAAO,SAASE,sBAAsBC,KAAoB;IACxD,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGb;IAEvB,MAAMc,OAAOb,YACX,OAAUc,OAAeC;QACvB,IAAI,CAACL,OAAO,MAAM,IAAIP,iBAAiB,+BAA+B,gBAAgB;QACtF,MAAMa,OACJD,KAAKE,MAAM,KAAK,QACZ,CAAC,KAAK,EAAEH,MAAM,OAAO,EAAEI,mBAAmBR,QAAQ,GAClD,CAAC,KAAK,EAAEI,OAAO;QACrB,MAAMK,WAAW,MAAMrB,gBAAgBc,MAAMI,MAAM;YACjDC,QAAQF,KAAKE,MAAM;WACfF,KAAKE,MAAM,KAAK,SAChB;YACEG,SAAS;gBAAE,gBAAgB;YAAmB;YAC9CC,MAAMC,KAAKC,SAAS,CAAC;gBAAEb;eAAUK,KAAKM,IAAI;QAC5C,IACA,CAAC;QAEP,MAAMG,UAAW,MAAML,SAASM,IAAI,GAAGC,KAAK,CAAC,IAAM;QAInD,IAAI,CAACP,SAASQ,EAAE,IAAI,CAACH,SAAS;gBAI1BI;YAHF,MAAMA,UAAWJ,kBAAAA,UAAW,CAAC;YAC7B,MAAM,IAAIrB,iBACRyB,QAAQC,KAAK,IAAI,+CACjBD,kBAAAA,QAAQtB,MAAM,YAAdsB,kBAAkB,eAClBT,SAASZ,MAAM;QAEnB;QACA,OAAOiB;IACT,GACA;QAACZ;QAAMF;KAAM;IAGf,OAAOT,QACL,IAAO,CAAA;YACL6B,cAAc,IACZjB,KAAkCX,oBAAoB6B,qBAAqB,EAAE;oBAAEd,QAAQ;gBAAM;YAC/Fe,SAAS,UACP,AAAC,CAAA,MAAMnB,KAA8BX,oBAAoB+B,gBAAgB,EAAE;oBAAEhB,QAAQ;gBAAO,EAAC,EAAGiB,GAAG;YACrGC,UAAU,CAACC,QACTvB,KAAsCX,oBAAoBmC,wBAAwB,EAAE;oBAClFpB,QAAQ;oBACRI,MAAMe;gBACR;YACFE,cAAc,CAACF,QACbvB,KAA8BX,oBAAoBqC,iBAAiB,EAAE;oBACnEtB,QAAQ;oBACRI,MAAMe;gBACR;YACFI,WAAW,CAACC,WAAWC,SACrB7B,KAA8BX,oBAAoByC,eAAe,EAAE;oBACjE1B,QAAQ;oBACRI,MAAM;wBAAEoB;wBAAWC;oBAAO;gBAC5B;YACFE,UAAU,CAACH,WAAWI,KACpBhC,KAAkCX,oBAAoB4C,aAAa,EAAE;oBACnE7B,QAAQ;oBACRI,MAAM;wBAAEoB;uBAAeI,KAAK;wBAAEA;oBAAG,IAAI,CAAC;gBACxC;YACFE,YAAY,CAACN,YACX5B,KAAwCX,oBAAoB8C,mBAAmB,EAAE;oBAC/E/B,QAAQ;oBACRI,MAAM;wBAAEoB;oBAAU;gBACpB;QACJ,CAAA,GACA;QAAC5B;KAAK;AAEV"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/use-outreach-mailbox-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 {\n OutreachApiRefusalReason,\n OutreachConnectCompleteResponse,\n OutreachConnectResponse,\n OutreachMailboxAvailability,\n OutreachMailboxDisconnectResponse,\n OutreachMailboxReadinessResponse,\n OutreachMailboxResponse,\n OutreachMailboxSettingsRequest,\n OutreachMailboxTestResponse,\n} from '../mailboxes/mailbox-api'\n\n/** A mailbox route's refusal, carrying the sentence and the stable reason. */\nexport class OutreachApiError extends Error {\n readonly reason: OutreachApiRefusalReason | 'unreachable'\n readonly status: number\n\n constructor(message: string, reason: OutreachApiRefusalReason | 'unreachable', status: number) {\n super(message)\n this.name = 'OutreachApiError'\n this.reason = reason\n this.status = status\n }\n}\n\n/** The mailbox routes, as the Mailboxes panel calls them. */\nexport interface OutreachMailboxApi {\n availability(): Promise<OutreachMailboxAvailability>\n /** Google's consent address for a new connect, or a reconnect. */\n connect(): Promise<string>\n complete(input: { code: string; state: string; timezone?: string }): Promise<OutreachConnectCompleteResponse>\n saveSettings(\n input: Omit<OutreachMailboxSettingsRequest, 'orgId'>,\n ): Promise<OutreachMailboxResponse>\n setPaused(mailboxId: string, paused: boolean): Promise<OutreachMailboxResponse>\n /** A test from the mailbox — to `to`, or to the account's own address. */\n sendTest(mailboxId: string, to?: string): Promise<OutreachMailboxTestResponse>\n disconnect(mailboxId: string): Promise<OutreachMailboxDisconnectResponse>\n /** SPF, DKIM and DMARC for the address it sends as; `fresh` skips the server's brief memory. */\n readiness(mailboxId: string, fresh?: boolean): Promise<OutreachMailboxReadinessResponse>\n}\n\n/**\n * The Mailboxes panel's one door to the mailbox routes (AGL-2978), with the\n * member's session attached and the organization named on every call — the\n * dispatcher's release gate reads it. A refusal is thrown as an\n * {@link OutreachApiError} carrying the route's own sentence, which is\n * written for the person reading it.\n */\nexport function useOutreachMailboxApi(orgId: string | null): OutreachMailboxApi {\n const { data: user } = useUser()\n\n const call = useCallback(\n async <T>(route: string, init: { method: 'GET' | 'POST'; body?: Record<string, unknown> }): Promise<T> => {\n if (!orgId) throw new OutreachApiError('Open an organization first.', 'org-required', 400)\n const path =\n init.method === 'GET'\n ? `/api/${route}?orgId=${encodeURIComponent(orgId)}`\n : `/api/${route}`\n const 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 const payload = (await response.json().catch(() => null)) as\n | (T & { error?: undefined })\n | { error?: string; reason?: OutreachApiRefusalReason }\n | null\n if (!response.ok || !payload) {\n const refusal = (payload ?? {}) as { error?: string; reason?: OutreachApiRefusalReason }\n throw new OutreachApiError(\n refusal.error || 'Sequences could not be reached. Try again.',\n refusal.reason ?? 'unreachable',\n response.status,\n )\n }\n return payload as T\n },\n [user, orgId],\n )\n\n return useMemo<OutreachMailboxApi>(\n () => ({\n availability: () =>\n call<OutreachMailboxAvailability>(OUTREACH_API_ROUTES.mailboxesAvailability, { method: 'GET' }),\n connect: async () =>\n (await call<OutreachConnectResponse>(OUTREACH_API_ROUTES.mailboxesConnect, { method: 'POST' })).url,\n complete: (input) =>\n call<OutreachConnectCompleteResponse>(OUTREACH_API_ROUTES.mailboxesConnectComplete, {\n method: 'POST',\n body: input,\n }),\n saveSettings: (input) =>\n call<OutreachMailboxResponse>(OUTREACH_API_ROUTES.mailboxesSettings, {\n method: 'POST',\n body: input as unknown as Record<string, unknown>,\n }),\n setPaused: (mailboxId, paused) =>\n call<OutreachMailboxResponse>(OUTREACH_API_ROUTES.mailboxesStatus, {\n method: 'POST',\n body: { mailboxId, paused },\n }),\n sendTest: (mailboxId, to) =>\n call<OutreachMailboxTestResponse>(OUTREACH_API_ROUTES.mailboxesTest, {\n method: 'POST',\n body: { mailboxId, ...(to ? { to } : {}) },\n }),\n disconnect: (mailboxId) =>\n call<OutreachMailboxDisconnectResponse>(OUTREACH_API_ROUTES.mailboxesDisconnect, {\n method: 'POST',\n body: { mailboxId },\n }),\n readiness: (mailboxId, fresh) =>\n call<OutreachMailboxReadinessResponse>(OUTREACH_API_ROUTES.mailboxesReadiness, {\n method: 'POST',\n body: { mailboxId, ...(fresh ? { fresh: true } : {}) },\n }),\n }),\n [call],\n )\n}\n"],"names":["authorizedFetch","useUser","useCallback","useMemo","OUTREACH_API_ROUTES","OutreachApiError","Error","message","reason","status","name","useOutreachMailboxApi","orgId","data","user","call","route","init","path","method","encodeURIComponent","response","headers","body","JSON","stringify","payload","json","catch","ok","refusal","error","availability","mailboxesAvailability","connect","mailboxesConnect","url","complete","input","mailboxesConnectComplete","saveSettings","mailboxesSettings","setPaused","mailboxId","paused","mailboxesStatus","sendTest","to","mailboxesTest","disconnect","mailboxesDisconnect","readiness","fresh","mailboxesReadiness"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,WAAW,EAAEC,OAAO,QAAQ,QAAO;AAC5C,SAASC,mBAAmB,QAAQ,6BAAyB;AAa7D,4EAA4E,GAC5E,OAAO,MAAMC,yBAAyBC;IAIpC,YAAYC,OAAe,EAAEC,MAAgD,EAAEC,MAAc,CAAE;QAC7F,KAAK,CAACF;QACN,IAAI,CAACG,IAAI,GAAG;QACZ,IAAI,CAACF,MAAM,GAAGA;QACd,IAAI,CAACC,MAAM,GAAGA;IAChB;AACF;AAmBA;;;;;;CAMC,GACD,OAAO,SAASE,sBAAsBC,KAAoB;IACxD,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGb;IAEvB,MAAMc,OAAOb,YACX,OAAUc,OAAeC;QACvB,IAAI,CAACL,OAAO,MAAM,IAAIP,iBAAiB,+BAA+B,gBAAgB;QACtF,MAAMa,OACJD,KAAKE,MAAM,KAAK,QACZ,CAAC,KAAK,EAAEH,MAAM,OAAO,EAAEI,mBAAmBR,QAAQ,GAClD,CAAC,KAAK,EAAEI,OAAO;QACrB,MAAMK,WAAW,MAAMrB,gBAAgBc,MAAMI,MAAM;YACjDC,QAAQF,KAAKE,MAAM;WACfF,KAAKE,MAAM,KAAK,SAChB;YACEG,SAAS;gBAAE,gBAAgB;YAAmB;YAC9CC,MAAMC,KAAKC,SAAS,CAAC;gBAAEb;eAAUK,KAAKM,IAAI;QAC5C,IACA,CAAC;QAEP,MAAMG,UAAW,MAAML,SAASM,IAAI,GAAGC,KAAK,CAAC,IAAM;QAInD,IAAI,CAACP,SAASQ,EAAE,IAAI,CAACH,SAAS;gBAI1BI;YAHF,MAAMA,UAAWJ,kBAAAA,UAAW,CAAC;YAC7B,MAAM,IAAIrB,iBACRyB,QAAQC,KAAK,IAAI,+CACjBD,kBAAAA,QAAQtB,MAAM,YAAdsB,kBAAkB,eAClBT,SAASZ,MAAM;QAEnB;QACA,OAAOiB;IACT,GACA;QAACZ;QAAMF;KAAM;IAGf,OAAOT,QACL,IAAO,CAAA;YACL6B,cAAc,IACZjB,KAAkCX,oBAAoB6B,qBAAqB,EAAE;oBAAEd,QAAQ;gBAAM;YAC/Fe,SAAS,UACP,AAAC,CAAA,MAAMnB,KAA8BX,oBAAoB+B,gBAAgB,EAAE;oBAAEhB,QAAQ;gBAAO,EAAC,EAAGiB,GAAG;YACrGC,UAAU,CAACC,QACTvB,KAAsCX,oBAAoBmC,wBAAwB,EAAE;oBAClFpB,QAAQ;oBACRI,MAAMe;gBACR;YACFE,cAAc,CAACF,QACbvB,KAA8BX,oBAAoBqC,iBAAiB,EAAE;oBACnEtB,QAAQ;oBACRI,MAAMe;gBACR;YACFI,WAAW,CAACC,WAAWC,SACrB7B,KAA8BX,oBAAoByC,eAAe,EAAE;oBACjE1B,QAAQ;oBACRI,MAAM;wBAAEoB;wBAAWC;oBAAO;gBAC5B;YACFE,UAAU,CAACH,WAAWI,KACpBhC,KAAkCX,oBAAoB4C,aAAa,EAAE;oBACnE7B,QAAQ;oBACRI,MAAM;wBAAEoB;uBAAeI,KAAK;wBAAEA;oBAAG,IAAI,CAAC;gBACxC;YACFE,YAAY,CAACN,YACX5B,KAAwCX,oBAAoB8C,mBAAmB,EAAE;oBAC/E/B,QAAQ;oBACRI,MAAM;wBAAEoB;oBAAU;gBACpB;YACFQ,WAAW,CAACR,WAAWS,QACrBrC,KAAuCX,oBAAoBiD,kBAAkB,EAAE;oBAC7ElC,QAAQ;oBACRI,MAAM;wBAAEoB;uBAAeS,QAAQ;wBAAEA,OAAO;oBAAK,IAAI,CAAC;gBACpD;QACJ,CAAA,GACA;QAACrC;KAAK;AAEV"}
@@ -47,6 +47,11 @@ export declare const OUTREACH_API_ROUTES: {
47
47
  readonly mailboxesTest: "outreach/mailboxes/test";
48
48
  /** `POST` — revoke the grant at Google and delete the mailbox. */
49
49
  readonly mailboxesDisconnect: "outreach/mailboxes/disconnect";
50
+ /**
51
+ * `POST` — SPF, DKIM and DMARC for the domain a mailbox sends as, and
52
+ * whether they align (AGL-3328).
53
+ */
54
+ readonly mailboxesReadiness: "outreach/mailboxes/readiness";
50
55
  /** `GET ?orgId` — the compliance settings; `POST` — save them. */
51
56
  readonly settings: "outreach/settings";
52
57
  /** `POST` — create a sequence, or save an edit to one. */
@@ -38,6 +38,10 @@
38
38
  /** `POST` — pause or resume. */ mailboxesStatus: 'outreach/mailboxes/status',
39
39
  /** `POST` — a plain-text test to the account's own address. */ mailboxesTest: 'outreach/mailboxes/test',
40
40
  /** `POST` — revoke the grant at Google and delete the mailbox. */ mailboxesDisconnect: 'outreach/mailboxes/disconnect',
41
+ /**
42
+ * `POST` — SPF, DKIM and DMARC for the domain a mailbox sends as, and
43
+ * whether they align (AGL-3328).
44
+ */ mailboxesReadiness: 'outreach/mailboxes/readiness',
41
45
  // Settings, sequences and enrollments (AGL-2980). Every one names its org
42
46
  // (`orgId`, query or body); the contract is `model/outreach-api.ts`.
43
47
  /** `GET ?orgId` — the compliance settings; `POST` — save them. */ settings: 'outreach/settings',
@@ -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 // 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","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,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 * `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"}
@@ -19,6 +19,7 @@ export * from './recipient-country';
19
19
  export * from './gates';
20
20
  export * from './schedule';
21
21
  export * from './sending-capacity';
22
+ export * from './next-send';
22
23
  export * from './enrollment-state';
23
24
  export * from './compose';
24
25
  export * from './thread-message';
@@ -22,6 +22,7 @@ export * from "./recipient-country.js";
22
22
  export * from "./gates.js";
23
23
  export * from "./schedule.js";
24
24
  export * from "./sending-capacity.js";
25
+ export * from "./next-send.js";
25
26
  export * from "./enrollment-state.js";
26
27
  export * from "./compose.js";
27
28
  export * from "./thread-message.js";
@@ -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 './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'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;CAIC,GACD,cAAc,2BAAuB;AACrC,cAAc,yBAAqB;AACnC,cAAc,aAAS;AACvB,cAAc,gBAAY;AAC1B,cAAc,wBAAoB;AAClC,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"}
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'\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"}
@@ -0,0 +1,59 @@
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, OutreachMailbox, OutreachSequence } from '../model/outreach.types';
18
+ import { type OutreachTickAllowance } from './sending-capacity';
19
+ /** Why a due enrollment has not gone yet. */
20
+ export type OutreachQueuedReason =
21
+ /** Its turn comes in a later run: the day's cap is spread over the window. */
22
+ 'paced'
23
+ /** A task step, which the next run turns into a CRM task. */
24
+ | 'next_run'
25
+ /** The send window is closed; it goes when the window next opens. */
26
+ | 'window_closed'
27
+ /** The mailbox has sent its cap for today. */
28
+ | 'daily_cap_reached'
29
+ /** The mailbox is missing, paused or disconnected: nothing sends. */
30
+ | 'mailbox_not_sending'
31
+ /** The sequence is not active: nothing sends. */
32
+ | 'sequence_not_active';
33
+ export type OutreachNextSendState = {
34
+ kind: 'none';
35
+ } | {
36
+ kind: 'scheduled';
37
+ dueAtMs: number;
38
+ } | {
39
+ kind: 'queued';
40
+ dueAtMs: number;
41
+ reason: OutreachQueuedReason;
42
+ /** When the window it waits for next opens, for the two reasons that wait on one. */
43
+ opensAtMs: number | null;
44
+ /** The mailbox's allowance this run, when the mailbox is known and sending. */
45
+ pacing: Pick<OutreachTickAllowance, 'allowance' | 'dailyCap' | 'sentToday'> | null;
46
+ };
47
+ export interface OutreachNextSendInput {
48
+ enrollment: Pick<OutreachEnrollment, 'status' | 'nextDueAtMs' | 'stopReason' | 'stepIndex'>;
49
+ /** The enrollment's sequence; omitted when the reader does not have it. */
50
+ sequence?: Pick<OutreachSequence, 'status' | 'steps' | 'settings'> | null;
51
+ /**
52
+ * The enrollment's mailbox: `undefined` while it is still loading, which
53
+ * reads as "paced" without figures, and `null` when there is none.
54
+ */
55
+ mailbox: Pick<OutreachMailbox, 'status' | 'dailyCap' | 'rampStartedAtMs' | 'timezone' | 'window' | 'health'> | null | undefined;
56
+ nowMs: number;
57
+ }
58
+ /** What an enrollment's "Next send" says — see the module note. */
59
+ export declare function outreachNextSendState(input: OutreachNextSendInput): OutreachNextSendState;