@aglyn/plugins-outreach 1.0.0-beta.202 → 1.0.0-beta.204

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-outreach",
3
- "version": "1.0.0-beta.202",
3
+ "version": "1.0.0-beta.204",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,19 +25,19 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.202",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.202",
30
- "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.202",
31
- "@aglyn/shared-ui-jsx": "1.0.0-beta.202",
32
- "@aglyn/shared-ui-next": "1.0.0-beta.202",
33
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.202",
34
- "@aglyn/shared-util-email": "1.0.0-beta.202",
35
- "@aglyn/shared-util-http": "1.0.0-beta.202",
36
- "@aglyn/shared-util-timestamp": "1.0.0-beta.202",
37
- "@aglyn/shared-util-tools": "1.0.0-beta.202",
38
- "@aglyn/tenant-data-admin": "1.0.0-beta.202",
39
- "@aglyn/tenant-feature-instance": "1.0.0-beta.202",
40
- "@aglyn/tenant-runtime": "1.0.0-beta.202",
28
+ "@aglyn/aglyn": "1.0.0-beta.204",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.204",
30
+ "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.204",
31
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.204",
32
+ "@aglyn/shared-ui-next": "1.0.0-beta.204",
33
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.204",
34
+ "@aglyn/shared-util-email": "1.0.0-beta.204",
35
+ "@aglyn/shared-util-http": "1.0.0-beta.204",
36
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.204",
37
+ "@aglyn/shared-util-tools": "1.0.0-beta.204",
38
+ "@aglyn/tenant-data-admin": "1.0.0-beta.204",
39
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.204",
40
+ "@aglyn/tenant-runtime": "1.0.0-beta.204",
41
41
  "@swc/helpers": "0.5.23"
42
42
  },
43
43
  "peerDependencies": {
@@ -32,6 +32,7 @@ export declare const MAILBOX_ACTION_LABELS: {
32
32
  readonly warmUp: "Warm up gradually";
33
33
  readonly disconnect: "Disconnect";
34
34
  readonly reconnect: "Reconnect";
35
+ readonly recheckReadiness: "Check DNS again";
35
36
  };
36
37
  /**
37
38
  * One connected mailbox (AGL-2978): its account and status, its last seven
@@ -19,6 +19,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  import { pluginDocsHelp } from "@aglyn/aglyn";
20
20
  import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
21
21
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
22
+ import SenderReadinessPanel from "@aglyn/tenant-feature-instance/components/sender-readiness-panel";
22
23
  import { Alert, Autocomplete, Box, Button, Chip, FormControlLabel, FormHelperText, MenuItem, Stack, Switch, TextField, ToggleButton, ToggleButtonGroup, Typography } from "@mui/material";
23
24
  import { useEffect, useMemo, useRef, useState } from "react";
24
25
  import { formatMinuteOfDay, isValidTimezone, OUTREACH_DEFAULT_WINDOW, OUTREACH_MAILBOX_STATUS_LABELS, OUTREACH_MAX_DAILY_CAP, OUTREACH_RAMP_STEPS, OUTREACH_WEEKDAY_LABELS, outreachEffectiveDailyCap, outreachLocalDay, summarizeMailboxHealth, validateDailyCap, validateDisplayName, validateSendWindow } from "../mailboxes/mailbox-settings.js";
@@ -55,7 +56,8 @@ import { formatMinuteOfDay, isValidTimezone, OUTREACH_DEFAULT_WINDOW, OUTREACH_M
55
56
  testAddress: 'Test address',
56
57
  warmUp: 'Warm up gradually',
57
58
  disconnect: 'Disconnect',
58
- reconnect: 'Reconnect'
59
+ reconnect: 'Reconnect',
60
+ recheckReadiness: 'Check DNS again'
59
61
  };
60
62
  const WEEKDAY_NAMES = [
61
63
  'Sunday',
@@ -119,6 +121,42 @@ const draftOf = (mailbox)=>{
119
121
  // Where a test goes when not to the member themself (AGL-3228): a test to
120
122
  // one's own address never leaves Google and shows no authentication result.
121
123
  const [testAddress, setTestAddress] = useState('');
124
+ /*
125
+ * SPF, DKIM and DMARC for the address this mailbox sends as (AGL-3328),
126
+ * read by the server and remembered there briefly. Re-read when the
127
+ * send-as address changes; a failed read shows nothing rather than a
128
+ * verdict nobody made.
129
+ */ const sendAsAddress = mailbox.sendAs || mailbox.email;
130
+ const [readiness, setReadiness] = useState(null);
131
+ const [readinessBusy, setReadinessBusy] = useState(false);
132
+ useEffect(()=>{
133
+ let active = true;
134
+ api.readiness(mailbox.id).then((answer)=>{
135
+ if (active) setReadiness(answer);
136
+ }).catch(()=>{
137
+ if (active) setReadiness(null);
138
+ });
139
+ return ()=>{
140
+ active = false;
141
+ };
142
+ }, [
143
+ api,
144
+ mailbox.id,
145
+ sendAsAddress
146
+ ]);
147
+ const recheckReadiness = async ()=>{
148
+ setReadinessBusy(true);
149
+ try {
150
+ setReadiness(await api.readiness(mailbox.id, true));
151
+ } catch (error) {
152
+ enqueueSnackbar(error.message, {
153
+ variant: 'error',
154
+ allowDuplicate: true
155
+ });
156
+ } finally{
157
+ setReadinessBusy(false);
158
+ }
159
+ };
122
160
  // A save here, a change made from another tab, or a reconnect rewrites the
123
161
  // stored settings; the form follows them, unless the member has unsaved
124
162
  // edits of their own, which are never thrown away.
@@ -343,6 +381,26 @@ const draftOf = (mailbox)=>{
343
381
  })
344
382
  ]
345
383
  }),
384
+ (readiness == null ? void 0 : readiness.readiness) ? /*#__PURE__*/ _jsxs(Stack, {
385
+ spacing: 1,
386
+ children: [
387
+ /*#__PURE__*/ _jsx(SenderReadinessPanel, {
388
+ readiness: readiness.readiness
389
+ }),
390
+ /*#__PURE__*/ _jsx(Box, {
391
+ children: /*#__PURE__*/ _jsx(Button, {
392
+ size: "small",
393
+ disabled: readinessBusy,
394
+ onClick: ()=>void recheckReadiness(),
395
+ children: MAILBOX_ACTION_LABELS.recheckReadiness
396
+ })
397
+ })
398
+ ]
399
+ }) : readiness ? /*#__PURE__*/ _jsx(Typography, {
400
+ variant: "caption",
401
+ color: "text.secondary",
402
+ children: `Google authenticates mail from ${readiness.sendAs.slice(readiness.sendAs.lastIndexOf('@') + 1)} ` + 'itself, so there are no records to publish for this address.'
403
+ }) : null,
346
404
  canManage ? /*#__PURE__*/ _jsxs(Stack, {
347
405
  spacing: 2,
348
406
  component: "form",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/mailbox-card.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 { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Autocomplete,\n Box,\n Button,\n Chip,\n FormControlLabel,\n FormHelperText,\n MenuItem,\n Stack,\n Switch,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport {\n formatMinuteOfDay,\n isValidTimezone,\n OUTREACH_DEFAULT_WINDOW,\n OUTREACH_MAILBOX_STATUS_LABELS,\n OUTREACH_MAX_DAILY_CAP,\n OUTREACH_RAMP_STEPS,\n OUTREACH_WEEKDAY_LABELS,\n outreachEffectiveDailyCap,\n outreachLocalDay,\n summarizeMailboxHealth,\n validateDailyCap,\n validateDisplayName,\n validateSendWindow,\n} from '../mailboxes/mailbox-settings'\nimport type {\n OutreachMailbox,\n OutreachMailboxStatus,\n OutreachSendWindow,\n} from '../model/outreach.types'\nimport type { OutreachMailboxApi } from './use-outreach-mailbox-api'\n\nexport interface MailboxCardProps {\n mailbox: OutreachMailbox\n /** The viewer is the member who connected it. */\n isMine: boolean\n /** The viewer may change, pause and disconnect it: its member, or an org owner or admin. */\n canManage: boolean\n api: OutreachMailboxApi\n /** Starts a new Google connect, for a mailbox that needs reconnecting. */\n onReconnect(): void\n /** True while that connect is on its way to Google. */\n reconnecting?: boolean\n /** The clock the health window and today's limit are read on. */\n nowMs?: number\n}\n\n/**\n * What the card says about a mailbox that paused ITSELF (AGL-2981): the\n * engine's own sentence, when it paused, and — after a complaint — the day\n * resuming stops being premature. Nothing sends from it until a member\n * resumes it.\n */\nexport function autoPauseSentence(mailbox: Pick<OutreachMailbox, 'autoPause' | 'timezone'>): string {\n const pause = mailbox.autoPause\n if (!pause) return ''\n const day = (atMs: number) => {\n try {\n return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeZone: mailbox.timezone }).format(atMs)\n } catch {\n return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeZone: 'UTC' }).format(atMs)\n }\n }\n const until = typeof pause.untilMs === 'number' ? ` Wait until ${day(pause.untilMs)} before resuming it.` : ''\n return `Sequences paused this mailbox on ${day(pause.atMs)}. ${pause.message}${until} Nothing sends from it until it is resumed.`\n}\n\n/** Accessible names of the card's actions, spelled once for the specs. */\nexport const MAILBOX_ACTION_LABELS = {\n save: 'Save settings',\n pause: 'Pause',\n resume: 'Resume',\n test: 'Send a test to myself',\n testElsewhere: 'Send a test',\n testAddress: 'Test address',\n warmUp: 'Warm up gradually',\n disconnect: 'Disconnect',\n reconnect: 'Reconnect',\n} as const\n\nconst WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']\n\nconst STATUS_COLOR: Record<OutreachMailboxStatus, 'success' | 'default' | 'warning' | 'error'> = {\n connected: 'success',\n paused: 'default',\n reconnect_required: 'warning',\n disconnected: 'error',\n}\n\n/** Every half hour of the day, and midnight at its end. */\nconst MINUTE_OPTIONS = Array.from({ length: 49 }, (_, index) => index * 30)\n\n/** The zones this runtime knows, the mailbox's own included whatever it is. */\nfunction timezoneOptions(current: string): string[] {\n const supported = (Intl as { supportedValuesOf?: (key: string) => string[] }).supportedValuesOf?.('timeZone') ?? []\n const zones = supported.length ? supported : ['UTC']\n return zones.includes(current) ? zones : [current, ...zones]\n}\n\n/** The ramp, as the cap's helper text reads it. */\nconst RAMP_SENTENCE = `Warm-up: ${OUTREACH_RAMP_STEPS.map((step, index) =>\n `week ${step.week}${index === OUTREACH_RAMP_STEPS.length - 1 ? '+' : ''}: ${step.limit}`,\n).join(' · ')} a day, never above the cap.`\n\ninterface Draft {\n sendAs: string\n displayName: string\n dailyCap: string\n window: OutreachSendWindow\n timezone: string\n /** Whether the warm-up ramp applies — `rampStartedAtMs` as a switch. */\n warmUp: boolean\n}\n\nconst draftOf = (mailbox: OutreachMailbox): Draft => ({\n sendAs: mailbox.sendAs ?? mailbox.email,\n displayName: mailbox.displayName ?? '',\n dailyCap: String(mailbox.dailyCap ?? ''),\n window: mailbox.window ?? OUTREACH_DEFAULT_WINDOW,\n timezone: mailbox.timezone ?? 'UTC',\n warmUp: typeof mailbox.rampStartedAtMs === 'number',\n})\n\n/**\n * One connected mailbox (AGL-2978): its account and status, its last seven\n * days, its settings, and pause, test and disconnect.\n *\n * Every control is offered only where the route will allow it: the settings\n * form and pause and disconnect to the member who connected the mailbox or an\n * org owner or admin, and the test and the reconnect to its member alone,\n * because both act in that member's own Google account.\n */\nexport function MailboxCard(props: MailboxCardProps) {\n const { mailbox, isMine, canManage, api, onReconnect, reconnecting = false, nowMs = Date.now() } = props\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [draft, setDraft] = useState<Draft>(() => draftOf(mailbox))\n const [saving, setSaving] = useState(false)\n const [saveError, setSaveError] = useState<string | null>(null)\n const [busy, setBusy] = useState<'status' | 'test' | 'disconnect' | null>(null)\n // Where a test goes when not to the member themself (AGL-3228): a test to\n // one's own address never leaves Google and shows no authentication result.\n const [testAddress, setTestAddress] = useState('')\n\n // A save here, a change made from another tab, or a reconnect rewrites the\n // stored settings; the form follows them, unless the member has unsaved\n // edits of their own, which are never thrown away.\n const stored = useMemo(() => draftOf(mailbox), [mailbox])\n const storedKey = JSON.stringify(stored)\n const seededFrom = useRef(storedKey)\n const dirty = JSON.stringify(draft) !== storedKey\n useEffect(() => {\n if (seededFrom.current === storedKey) return\n const previous = seededFrom.current\n seededFrom.current = storedKey\n setDraft((current) => (JSON.stringify(current) === previous ? stored : current))\n }, [storedKey, stored])\n\n const health = summarizeMailboxHealth(mailbox.health, mailbox.timezone, nowMs)\n // Tests of a sequence step sent today (AGL-3325): counted beside the\n // sends, never among them, so the cap and the seven-day sum stay honest.\n const testsToday = Math.max(\n 0,\n Number(mailbox.health?.daily?.[outreachLocalDay(nowMs, mailbox.timezone)]?.tests) || 0,\n )\n const needsReconnect = mailbox.status === 'reconnect_required' || mailbox.status === 'disconnected'\n const capCheck = validateDailyCap(draft.dailyCap === '' ? Number.NaN : Number(draft.dailyCap))\n const windowCheck = validateSendWindow(draft.window)\n const nameCheck = validateDisplayName(draft.displayName)\n const timezoneValid = isValidTimezone(draft.timezone)\n const invalid =\n typeof capCheck !== 'number' || !('days' in windowCheck) || typeof nameCheck !== 'string' || !timezoneValid\n const todayLimit = outreachEffectiveDailyCap(\n {\n dailyCap: typeof capCheck === 'number' ? capCheck : mailbox.dailyCap,\n // A ramp switched on in the draft reads as starting today.\n rampStartedAtMs: draft.warmUp ? (mailbox.rampStartedAtMs ?? nowMs) : null,\n timezone: timezoneValid ? draft.timezone : mailbox.timezone,\n },\n nowMs,\n )\n\n const save = async () => {\n setSaving(true)\n setSaveError(null)\n try {\n await api.saveSettings({\n mailboxId: mailbox.id,\n ...(draft.sendAs !== stored.sendAs ? { sendAs: draft.sendAs } : {}),\n ...(draft.displayName !== stored.displayName ? { displayName: draft.displayName } : {}),\n ...(draft.dailyCap !== stored.dailyCap ? { dailyCap: Number(draft.dailyCap) } : {}),\n ...(JSON.stringify(draft.window) !== JSON.stringify(stored.window) ? { window: draft.window } : {}),\n ...(draft.timezone !== stored.timezone ? { timezone: draft.timezone } : {}),\n ...(draft.warmUp !== stored.warmUp ? { warmUp: draft.warmUp } : {}),\n })\n enqueueSnackbar('Mailbox settings saved', { variant: 'success' })\n } catch (error) {\n setSaveError((error as Error).message)\n } finally {\n setSaving(false)\n }\n }\n\n const togglePaused = async () => {\n const paused = mailbox.status !== 'paused'\n setBusy('status')\n try {\n await api.setPaused(mailbox.id, paused)\n enqueueSnackbar(paused ? 'Mailbox paused' : 'Mailbox resumed', { variant: 'success' })\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(null)\n }\n }\n\n const sendTest = async () => {\n setBusy('test')\n try {\n const to = testAddress.trim()\n const sent = await api.sendTest(mailbox.id, to || undefined)\n enqueueSnackbar(\n to\n ? `Test sent to ${sent.sentTo}. Its original source shows whether the sender authenticated.`\n : `Test sent to ${sent.sentTo}. Check your inbox.`,\n { variant: 'success' },\n )\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(null)\n }\n }\n\n const disconnect = async () => {\n const confirmed = await confirm({\n title: 'Disconnect this mailbox?',\n description:\n 'Disconnecting deletes the access stored for this mailbox and asks Google to revoke it. ' +\n 'Nothing is sent from it again unless it is connected again.',\n confirmationText: MAILBOX_ACTION_LABELS.disconnect,\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n setBusy('disconnect')\n try {\n const { revocation } = await api.disconnect(mailbox.id)\n if (revocation === 'failed') {\n enqueueSnackbar(\n 'Mailbox disconnected, but Google could not confirm the access was revoked. ' +\n 'Remove it from the Google Account’s third-party connections to be sure.',\n { variant: 'warning', persist: true },\n )\n } else if (revocation === 'kept-for-other-mailbox') {\n enqueueSnackbar(\n 'Mailbox disconnected. Google access stays in place for another connected mailbox on the same account.',\n { variant: 'success' },\n )\n } else {\n enqueueSnackbar('Mailbox disconnected', { variant: 'success' })\n }\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n setBusy(null)\n }\n }\n\n const sendAsLabel = (email: string) => {\n const option = mailbox.sendAsOptions?.find((entry) => entry.email === email)\n return option?.displayName ? `${option.displayName} <${option.email}>` : email\n }\n\n return (\n <CardDisplay\n header={mailbox.email}\n subheader={isMine ? 'Your mailbox' : 'A teammate’s mailbox'}\n help={pluginDocsHelp('sequences', { anchor: '#connect-a-mailbox' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Chip\n size=\"small\"\n label={OUTREACH_MAILBOX_STATUS_LABELS[mailbox.status] ?? mailbox.status}\n color={STATUS_COLOR[mailbox.status] ?? 'default'}\n />\n ),\n }}\n >\n <Stack spacing={2}>\n {needsReconnect ? (\n <Alert\n severity=\"warning\"\n action={\n isMine ? (\n <Button color=\"inherit\" size=\"small\" disabled={reconnecting} onClick={onReconnect}>\n {MAILBOX_ACTION_LABELS.reconnect}\n </Button>\n ) : undefined\n }\n >\n {`${\n mailbox.status === 'disconnected'\n ? 'This mailbox was disconnected.'\n : 'Google stopped accepting this mailbox’s connection.'\n } ${\n isMine\n ? 'Nothing sends from it until you connect it again.'\n : 'Only the member who connected it can reconnect it.'\n }`}\n </Alert>\n ) : null}\n {mailbox.status === 'paused' && mailbox.autoPause ? (\n <Alert severity=\"error\" role=\"alert\">\n {autoPauseSentence(mailbox)}\n </Alert>\n ) : null}\n\n <Box>\n <Typography variant=\"subtitle2\">Last 7 days</Typography>\n {health.hasSent ? null : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n No sends yet\n </Typography>\n )}\n <Stack direction=\"row\" spacing={3} sx={{ mt: 0.5 }}>\n {[\n ['Sent', health.sent],\n ['Bounces', health.bounces],\n ['Replies', health.replies],\n ].map(([label, value]) => (\n <Box key={label as string} aria-label={`${label} in the last 7 days`}>\n <Typography variant=\"h6\" component=\"p\">\n {value}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {label}\n </Typography>\n </Box>\n ))}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\" aria-label=\"Tests today\">\n {`Tests today: ${testsToday}`}\n </Typography>\n </Box>\n\n {canManage ? (\n <Stack spacing={2} component=\"form\" onSubmit={(event) => event.preventDefault()}>\n <TextField\n select\n size=\"small\"\n label=\"Send as\"\n value={draft.sendAs}\n onChange={(event) => setDraft({ ...draft, sendAs: event.target.value })}\n helperText=\"Addresses Gmail has verified for this account.\"\n >\n {(mailbox.sendAsOptions?.length ? mailbox.sendAsOptions : [{ email: mailbox.sendAs }]).map((option) => (\n <MenuItem key={option.email} value={option.email}>\n {sendAsLabel(option.email)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Display name\"\n value={draft.displayName}\n onChange={(event) => setDraft({ ...draft, displayName: event.target.value })}\n error={typeof nameCheck !== 'string'}\n helperText={typeof nameCheck !== 'string' ? nameCheck.message : 'The name recipients see beside the address.'}\n />\n <TextField\n size=\"small\"\n type=\"number\"\n label=\"Daily cap\"\n value={draft.dailyCap}\n onChange={(event) => setDraft({ ...draft, dailyCap: event.target.value })}\n error={typeof capCheck !== 'number'}\n slotProps={{ htmlInput: { min: 1, max: OUTREACH_MAX_DAILY_CAP, step: 1 } }}\n helperText={\n typeof capCheck !== 'number'\n ? capCheck.message\n : draft.warmUp\n ? `${RAMP_SENTENCE} Today’s limit: ${todayLimit}.`\n : `No warm-up: today’s limit is the cap, ${todayLimit}.`\n }\n />\n <FormControlLabel\n control={\n <Switch\n checked={draft.warmUp}\n onChange={(event) => setDraft({ ...draft, warmUp: event.target.checked })}\n />\n }\n label={MAILBOX_ACTION_LABELS.warmUp}\n />\n <FormHelperText sx={{ mt: -1 }}>\n {'For a mailbox that is new or has not sent much. One that has sent mail for years already has ' +\n 'its reputation and can start at its cap.'}\n </FormHelperText>\n <Box>\n <Typography variant=\"subtitle2\" gutterBottom>\n Sending window\n </Typography>\n <ToggleButtonGroup\n size=\"small\"\n aria-label=\"Sending days\"\n value={draft.window.days}\n onChange={(_event, days: number[]) =>\n setDraft({ ...draft, window: { ...draft.window, days: [...days].sort((a, b) => a - b) } })\n }\n >\n {OUTREACH_WEEKDAY_LABELS.map((label, day) => (\n <ToggleButton key={label} value={day} aria-label={WEEKDAY_NAMES[day]}>\n {label}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} sx={{ mt: 2 }}>\n <TextField\n select\n size=\"small\"\n label=\"From\"\n value={draft.window.startMinute}\n onChange={(event) =>\n setDraft({ ...draft, window: { ...draft.window, startMinute: Number(event.target.value) } })\n }\n >\n {MINUTE_OPTIONS.slice(0, -1).map((minute) => (\n <MenuItem key={minute} value={minute}>\n {formatMinuteOfDay(minute)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n size=\"small\"\n label=\"Until\"\n value={draft.window.endMinute}\n onChange={(event) =>\n setDraft({ ...draft, window: { ...draft.window, endMinute: Number(event.target.value) } })\n }\n >\n {MINUTE_OPTIONS.slice(1).map((minute) => (\n <MenuItem key={minute} value={minute}>\n {formatMinuteOfDay(minute)}\n </MenuItem>\n ))}\n </TextField>\n <Autocomplete\n size=\"small\"\n disableClearable\n options={timezoneOptions(mailbox.timezone)}\n value={draft.timezone}\n onChange={(_event, timezone) => setDraft({ ...draft, timezone: timezone ?? draft.timezone })}\n sx={{ minWidth: 240 }}\n renderInput={(params) => <TextField {...params} label=\"Timezone\" />}\n />\n </Stack>\n {'days' in windowCheck ? null : <FormHelperText error>{windowCheck.message}</FormHelperText>}\n </Box>\n {saveError ? <FormHelperText error>{saveError}</FormHelperText> : null}\n <Box>\n <Button variant=\"contained\" disabled={!dirty || invalid || saving} onClick={() => void save()}>\n {MAILBOX_ACTION_LABELS.save}\n </Button>\n </Box>\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Sends as ${sendAsLabel(stored.sendAs)}, at most ${stored.dailyCap} a day, ` +\n `${stored.window.days.map((day) => OUTREACH_WEEKDAY_LABELS[day]).join(', ')} ` +\n `${formatMinuteOfDay(stored.window.startMinute)}–${formatMinuteOfDay(stored.window.endMinute)} ` +\n `(${stored.timezone}).`}\n </Typography>\n )}\n\n {isMine && !needsReconnect ? (\n <TextField\n label={MAILBOX_ACTION_LABELS.testAddress}\n value={testAddress}\n onChange={(event) => setTestAddress(event.target.value)}\n disabled={busy !== null}\n size=\"small\"\n type=\"email\"\n helperText={\n 'Leave empty to send the test to yourself. A test to an outside mailbox you can ' +\n 'read is the only one whose original source shows the sender authentication results.'\n }\n fullWidth\n />\n ) : null}\n\n {canManage || isMine ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {canManage && !needsReconnect ? (\n <Button variant=\"outlined\" disabled={busy !== null} onClick={() => void togglePaused()}>\n {mailbox.status === 'paused' ? MAILBOX_ACTION_LABELS.resume : MAILBOX_ACTION_LABELS.pause}\n </Button>\n ) : null}\n {isMine && !needsReconnect ? (\n <Button variant=\"outlined\" disabled={busy !== null} onClick={() => void sendTest()}>\n {testAddress.trim() ? MAILBOX_ACTION_LABELS.testElsewhere : MAILBOX_ACTION_LABELS.test}\n </Button>\n ) : null}\n {canManage ? (\n <Button color=\"error\" disabled={busy !== null} onClick={() => void disconnect()}>\n {MAILBOX_ACTION_LABELS.disconnect}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nMailboxCard.displayName = 'MailboxCard'\n\nexport default MailboxCard\n"],"names":["pluginDocsHelp","CardDisplay","useConfirmationContext","useSnackbar","Alert","Autocomplete","Box","Button","Chip","FormControlLabel","FormHelperText","MenuItem","Stack","Switch","TextField","ToggleButton","ToggleButtonGroup","Typography","useEffect","useMemo","useRef","useState","formatMinuteOfDay","isValidTimezone","OUTREACH_DEFAULT_WINDOW","OUTREACH_MAILBOX_STATUS_LABELS","OUTREACH_MAX_DAILY_CAP","OUTREACH_RAMP_STEPS","OUTREACH_WEEKDAY_LABELS","outreachEffectiveDailyCap","outreachLocalDay","summarizeMailboxHealth","validateDailyCap","validateDisplayName","validateSendWindow","autoPauseSentence","mailbox","pause","autoPause","day","atMs","Intl","DateTimeFormat","dateStyle","timeZone","timezone","format","until","untilMs","message","MAILBOX_ACTION_LABELS","save","resume","test","testElsewhere","testAddress","warmUp","disconnect","reconnect","WEEKDAY_NAMES","STATUS_COLOR","connected","paused","reconnect_required","disconnected","MINUTE_OPTIONS","Array","from","length","_","index","timezoneOptions","current","supported","supportedValuesOf","zones","includes","RAMP_SENTENCE","map","step","week","limit","join","draftOf","sendAs","email","displayName","dailyCap","String","window","rampStartedAtMs","MailboxCard","props","isMine","canManage","api","onReconnect","reconnecting","nowMs","Date","now","enqueueSnackbar","confirm","draft","setDraft","saving","setSaving","saveError","setSaveError","busy","setBusy","setTestAddress","stored","storedKey","JSON","stringify","seededFrom","dirty","previous","health","testsToday","Math","max","Number","daily","tests","needsReconnect","status","capCheck","NaN","windowCheck","nameCheck","timezoneValid","invalid","todayLimit","saveSettings","mailboxId","id","variant","error","togglePaused","setPaused","allowDuplicate","sendTest","to","trim","sent","undefined","sentTo","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","revocation","persist","sendAsLabel","option","sendAsOptions","find","entry","header","subheader","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","size","label","spacing","severity","disabled","onClick","role","hasSent","direction","sx","mt","bounces","replies","value","aria-label","component","onSubmit","event","preventDefault","select","onChange","target","helperText","type","slotProps","htmlInput","min","control","checked","gutterBottom","days","_event","sort","a","b","xs","sm","startMinute","slice","minute","endMinute","disableClearable","options","minWidth","renderInput","params","fullWidth","useFlexGap","flexWrap"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/mailbox-card.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 { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport SenderReadinessPanel from '@aglyn/tenant-feature-instance/components/sender-readiness-panel'\nimport {\n Alert,\n Autocomplete,\n Box,\n Button,\n Chip,\n FormControlLabel,\n FormHelperText,\n MenuItem,\n Stack,\n Switch,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport {\n formatMinuteOfDay,\n isValidTimezone,\n OUTREACH_DEFAULT_WINDOW,\n OUTREACH_MAILBOX_STATUS_LABELS,\n OUTREACH_MAX_DAILY_CAP,\n OUTREACH_RAMP_STEPS,\n OUTREACH_WEEKDAY_LABELS,\n outreachEffectiveDailyCap,\n outreachLocalDay,\n summarizeMailboxHealth,\n validateDailyCap,\n validateDisplayName,\n validateSendWindow,\n} from '../mailboxes/mailbox-settings'\nimport type { OutreachMailboxReadinessResponse } from '../mailboxes/mailbox-api'\nimport type {\n OutreachMailbox,\n OutreachMailboxStatus,\n OutreachSendWindow,\n} from '../model/outreach.types'\nimport type { OutreachMailboxApi } from './use-outreach-mailbox-api'\n\nexport interface MailboxCardProps {\n mailbox: OutreachMailbox\n /** The viewer is the member who connected it. */\n isMine: boolean\n /** The viewer may change, pause and disconnect it: its member, or an org owner or admin. */\n canManage: boolean\n api: OutreachMailboxApi\n /** Starts a new Google connect, for a mailbox that needs reconnecting. */\n onReconnect(): void\n /** True while that connect is on its way to Google. */\n reconnecting?: boolean\n /** The clock the health window and today's limit are read on. */\n nowMs?: number\n}\n\n/**\n * What the card says about a mailbox that paused ITSELF (AGL-2981): the\n * engine's own sentence, when it paused, and — after a complaint — the day\n * resuming stops being premature. Nothing sends from it until a member\n * resumes it.\n */\nexport function autoPauseSentence(mailbox: Pick<OutreachMailbox, 'autoPause' | 'timezone'>): string {\n const pause = mailbox.autoPause\n if (!pause) return ''\n const day = (atMs: number) => {\n try {\n return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeZone: mailbox.timezone }).format(atMs)\n } catch {\n return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeZone: 'UTC' }).format(atMs)\n }\n }\n const until = typeof pause.untilMs === 'number' ? ` Wait until ${day(pause.untilMs)} before resuming it.` : ''\n return `Sequences paused this mailbox on ${day(pause.atMs)}. ${pause.message}${until} Nothing sends from it until it is resumed.`\n}\n\n/** Accessible names of the card's actions, spelled once for the specs. */\nexport const MAILBOX_ACTION_LABELS = {\n save: 'Save settings',\n pause: 'Pause',\n resume: 'Resume',\n test: 'Send a test to myself',\n testElsewhere: 'Send a test',\n testAddress: 'Test address',\n warmUp: 'Warm up gradually',\n disconnect: 'Disconnect',\n reconnect: 'Reconnect',\n recheckReadiness: 'Check DNS again',\n} as const\n\nconst WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']\n\nconst STATUS_COLOR: Record<OutreachMailboxStatus, 'success' | 'default' | 'warning' | 'error'> = {\n connected: 'success',\n paused: 'default',\n reconnect_required: 'warning',\n disconnected: 'error',\n}\n\n/** Every half hour of the day, and midnight at its end. */\nconst MINUTE_OPTIONS = Array.from({ length: 49 }, (_, index) => index * 30)\n\n/** The zones this runtime knows, the mailbox's own included whatever it is. */\nfunction timezoneOptions(current: string): string[] {\n const supported = (Intl as { supportedValuesOf?: (key: string) => string[] }).supportedValuesOf?.('timeZone') ?? []\n const zones = supported.length ? supported : ['UTC']\n return zones.includes(current) ? zones : [current, ...zones]\n}\n\n/** The ramp, as the cap's helper text reads it. */\nconst RAMP_SENTENCE = `Warm-up: ${OUTREACH_RAMP_STEPS.map((step, index) =>\n `week ${step.week}${index === OUTREACH_RAMP_STEPS.length - 1 ? '+' : ''}: ${step.limit}`,\n).join(' · ')} a day, never above the cap.`\n\ninterface Draft {\n sendAs: string\n displayName: string\n dailyCap: string\n window: OutreachSendWindow\n timezone: string\n /** Whether the warm-up ramp applies — `rampStartedAtMs` as a switch. */\n warmUp: boolean\n}\n\nconst draftOf = (mailbox: OutreachMailbox): Draft => ({\n sendAs: mailbox.sendAs ?? mailbox.email,\n displayName: mailbox.displayName ?? '',\n dailyCap: String(mailbox.dailyCap ?? ''),\n window: mailbox.window ?? OUTREACH_DEFAULT_WINDOW,\n timezone: mailbox.timezone ?? 'UTC',\n warmUp: typeof mailbox.rampStartedAtMs === 'number',\n})\n\n/**\n * One connected mailbox (AGL-2978): its account and status, its last seven\n * days, its settings, and pause, test and disconnect.\n *\n * Every control is offered only where the route will allow it: the settings\n * form and pause and disconnect to the member who connected the mailbox or an\n * org owner or admin, and the test and the reconnect to its member alone,\n * because both act in that member's own Google account.\n */\nexport function MailboxCard(props: MailboxCardProps) {\n const { mailbox, isMine, canManage, api, onReconnect, reconnecting = false, nowMs = Date.now() } = props\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [draft, setDraft] = useState<Draft>(() => draftOf(mailbox))\n const [saving, setSaving] = useState(false)\n const [saveError, setSaveError] = useState<string | null>(null)\n const [busy, setBusy] = useState<'status' | 'test' | 'disconnect' | null>(null)\n // Where a test goes when not to the member themself (AGL-3228): a test to\n // one's own address never leaves Google and shows no authentication result.\n const [testAddress, setTestAddress] = useState('')\n\n /*\n * SPF, DKIM and DMARC for the address this mailbox sends as (AGL-3328),\n * read by the server and remembered there briefly. Re-read when the\n * send-as address changes; a failed read shows nothing rather than a\n * verdict nobody made.\n */\n const sendAsAddress = mailbox.sendAs || mailbox.email\n const [readiness, setReadiness] = useState<OutreachMailboxReadinessResponse | null>(null)\n const [readinessBusy, setReadinessBusy] = useState(false)\n useEffect(() => {\n let active = true\n api\n .readiness(mailbox.id)\n .then((answer) => {\n if (active) setReadiness(answer)\n })\n .catch(() => {\n if (active) setReadiness(null)\n })\n return () => {\n active = false\n }\n }, [api, mailbox.id, sendAsAddress])\n const recheckReadiness = async () => {\n setReadinessBusy(true)\n try {\n setReadiness(await api.readiness(mailbox.id, true))\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n } finally {\n setReadinessBusy(false)\n }\n }\n\n // A save here, a change made from another tab, or a reconnect rewrites the\n // stored settings; the form follows them, unless the member has unsaved\n // edits of their own, which are never thrown away.\n const stored = useMemo(() => draftOf(mailbox), [mailbox])\n const storedKey = JSON.stringify(stored)\n const seededFrom = useRef(storedKey)\n const dirty = JSON.stringify(draft) !== storedKey\n useEffect(() => {\n if (seededFrom.current === storedKey) return\n const previous = seededFrom.current\n seededFrom.current = storedKey\n setDraft((current) => (JSON.stringify(current) === previous ? stored : current))\n }, [storedKey, stored])\n\n const health = summarizeMailboxHealth(mailbox.health, mailbox.timezone, nowMs)\n // Tests of a sequence step sent today (AGL-3325): counted beside the\n // sends, never among them, so the cap and the seven-day sum stay honest.\n const testsToday = Math.max(\n 0,\n Number(mailbox.health?.daily?.[outreachLocalDay(nowMs, mailbox.timezone)]?.tests) || 0,\n )\n const needsReconnect = mailbox.status === 'reconnect_required' || mailbox.status === 'disconnected'\n const capCheck = validateDailyCap(draft.dailyCap === '' ? Number.NaN : Number(draft.dailyCap))\n const windowCheck = validateSendWindow(draft.window)\n const nameCheck = validateDisplayName(draft.displayName)\n const timezoneValid = isValidTimezone(draft.timezone)\n const invalid =\n typeof capCheck !== 'number' || !('days' in windowCheck) || typeof nameCheck !== 'string' || !timezoneValid\n const todayLimit = outreachEffectiveDailyCap(\n {\n dailyCap: typeof capCheck === 'number' ? capCheck : mailbox.dailyCap,\n // A ramp switched on in the draft reads as starting today.\n rampStartedAtMs: draft.warmUp ? (mailbox.rampStartedAtMs ?? nowMs) : null,\n timezone: timezoneValid ? draft.timezone : mailbox.timezone,\n },\n nowMs,\n )\n\n const save = async () => {\n setSaving(true)\n setSaveError(null)\n try {\n await api.saveSettings({\n mailboxId: mailbox.id,\n ...(draft.sendAs !== stored.sendAs ? { sendAs: draft.sendAs } : {}),\n ...(draft.displayName !== stored.displayName ? { displayName: draft.displayName } : {}),\n ...(draft.dailyCap !== stored.dailyCap ? { dailyCap: Number(draft.dailyCap) } : {}),\n ...(JSON.stringify(draft.window) !== JSON.stringify(stored.window) ? { window: draft.window } : {}),\n ...(draft.timezone !== stored.timezone ? { timezone: draft.timezone } : {}),\n ...(draft.warmUp !== stored.warmUp ? { warmUp: draft.warmUp } : {}),\n })\n enqueueSnackbar('Mailbox settings saved', { variant: 'success' })\n } catch (error) {\n setSaveError((error as Error).message)\n } finally {\n setSaving(false)\n }\n }\n\n const togglePaused = async () => {\n const paused = mailbox.status !== 'paused'\n setBusy('status')\n try {\n await api.setPaused(mailbox.id, paused)\n enqueueSnackbar(paused ? 'Mailbox paused' : 'Mailbox resumed', { variant: 'success' })\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(null)\n }\n }\n\n const sendTest = async () => {\n setBusy('test')\n try {\n const to = testAddress.trim()\n const sent = await api.sendTest(mailbox.id, to || undefined)\n enqueueSnackbar(\n to\n ? `Test sent to ${sent.sentTo}. Its original source shows whether the sender authenticated.`\n : `Test sent to ${sent.sentTo}. Check your inbox.`,\n { variant: 'success' },\n )\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(null)\n }\n }\n\n const disconnect = async () => {\n const confirmed = await confirm({\n title: 'Disconnect this mailbox?',\n description:\n 'Disconnecting deletes the access stored for this mailbox and asks Google to revoke it. ' +\n 'Nothing is sent from it again unless it is connected again.',\n confirmationText: MAILBOX_ACTION_LABELS.disconnect,\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n setBusy('disconnect')\n try {\n const { revocation } = await api.disconnect(mailbox.id)\n if (revocation === 'failed') {\n enqueueSnackbar(\n 'Mailbox disconnected, but Google could not confirm the access was revoked. ' +\n 'Remove it from the Google Account’s third-party connections to be sure.',\n { variant: 'warning', persist: true },\n )\n } else if (revocation === 'kept-for-other-mailbox') {\n enqueueSnackbar(\n 'Mailbox disconnected. Google access stays in place for another connected mailbox on the same account.',\n { variant: 'success' },\n )\n } else {\n enqueueSnackbar('Mailbox disconnected', { variant: 'success' })\n }\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n setBusy(null)\n }\n }\n\n const sendAsLabel = (email: string) => {\n const option = mailbox.sendAsOptions?.find((entry) => entry.email === email)\n return option?.displayName ? `${option.displayName} <${option.email}>` : email\n }\n\n return (\n <CardDisplay\n header={mailbox.email}\n subheader={isMine ? 'Your mailbox' : 'A teammate’s mailbox'}\n help={pluginDocsHelp('sequences', { anchor: '#connect-a-mailbox' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Chip\n size=\"small\"\n label={OUTREACH_MAILBOX_STATUS_LABELS[mailbox.status] ?? mailbox.status}\n color={STATUS_COLOR[mailbox.status] ?? 'default'}\n />\n ),\n }}\n >\n <Stack spacing={2}>\n {needsReconnect ? (\n <Alert\n severity=\"warning\"\n action={\n isMine ? (\n <Button color=\"inherit\" size=\"small\" disabled={reconnecting} onClick={onReconnect}>\n {MAILBOX_ACTION_LABELS.reconnect}\n </Button>\n ) : undefined\n }\n >\n {`${\n mailbox.status === 'disconnected'\n ? 'This mailbox was disconnected.'\n : 'Google stopped accepting this mailbox’s connection.'\n } ${\n isMine\n ? 'Nothing sends from it until you connect it again.'\n : 'Only the member who connected it can reconnect it.'\n }`}\n </Alert>\n ) : null}\n {mailbox.status === 'paused' && mailbox.autoPause ? (\n <Alert severity=\"error\" role=\"alert\">\n {autoPauseSentence(mailbox)}\n </Alert>\n ) : null}\n\n <Box>\n <Typography variant=\"subtitle2\">Last 7 days</Typography>\n {health.hasSent ? null : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n No sends yet\n </Typography>\n )}\n <Stack direction=\"row\" spacing={3} sx={{ mt: 0.5 }}>\n {[\n ['Sent', health.sent],\n ['Bounces', health.bounces],\n ['Replies', health.replies],\n ].map(([label, value]) => (\n <Box key={label as string} aria-label={`${label} in the last 7 days`}>\n <Typography variant=\"h6\" component=\"p\">\n {value}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {label}\n </Typography>\n </Box>\n ))}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\" aria-label=\"Tests today\">\n {`Tests today: ${testsToday}`}\n </Typography>\n </Box>\n\n {readiness?.readiness ? (\n <Stack spacing={1}>\n <SenderReadinessPanel readiness={readiness.readiness} />\n <Box>\n <Button size=\"small\" disabled={readinessBusy} onClick={() => void recheckReadiness()}>\n {MAILBOX_ACTION_LABELS.recheckReadiness}\n </Button>\n </Box>\n </Stack>\n ) : readiness ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Google authenticates mail from ${readiness.sendAs.slice(readiness.sendAs.lastIndexOf('@') + 1)} ` +\n 'itself, so there are no records to publish for this address.'}\n </Typography>\n ) : null}\n\n {canManage ? (\n <Stack spacing={2} component=\"form\" onSubmit={(event) => event.preventDefault()}>\n <TextField\n select\n size=\"small\"\n label=\"Send as\"\n value={draft.sendAs}\n onChange={(event) => setDraft({ ...draft, sendAs: event.target.value })}\n helperText=\"Addresses Gmail has verified for this account.\"\n >\n {(mailbox.sendAsOptions?.length ? mailbox.sendAsOptions : [{ email: mailbox.sendAs }]).map((option) => (\n <MenuItem key={option.email} value={option.email}>\n {sendAsLabel(option.email)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Display name\"\n value={draft.displayName}\n onChange={(event) => setDraft({ ...draft, displayName: event.target.value })}\n error={typeof nameCheck !== 'string'}\n helperText={typeof nameCheck !== 'string' ? nameCheck.message : 'The name recipients see beside the address.'}\n />\n <TextField\n size=\"small\"\n type=\"number\"\n label=\"Daily cap\"\n value={draft.dailyCap}\n onChange={(event) => setDraft({ ...draft, dailyCap: event.target.value })}\n error={typeof capCheck !== 'number'}\n slotProps={{ htmlInput: { min: 1, max: OUTREACH_MAX_DAILY_CAP, step: 1 } }}\n helperText={\n typeof capCheck !== 'number'\n ? capCheck.message\n : draft.warmUp\n ? `${RAMP_SENTENCE} Today’s limit: ${todayLimit}.`\n : `No warm-up: today’s limit is the cap, ${todayLimit}.`\n }\n />\n <FormControlLabel\n control={\n <Switch\n checked={draft.warmUp}\n onChange={(event) => setDraft({ ...draft, warmUp: event.target.checked })}\n />\n }\n label={MAILBOX_ACTION_LABELS.warmUp}\n />\n <FormHelperText sx={{ mt: -1 }}>\n {'For a mailbox that is new or has not sent much. One that has sent mail for years already has ' +\n 'its reputation and can start at its cap.'}\n </FormHelperText>\n <Box>\n <Typography variant=\"subtitle2\" gutterBottom>\n Sending window\n </Typography>\n <ToggleButtonGroup\n size=\"small\"\n aria-label=\"Sending days\"\n value={draft.window.days}\n onChange={(_event, days: number[]) =>\n setDraft({ ...draft, window: { ...draft.window, days: [...days].sort((a, b) => a - b) } })\n }\n >\n {OUTREACH_WEEKDAY_LABELS.map((label, day) => (\n <ToggleButton key={label} value={day} aria-label={WEEKDAY_NAMES[day]}>\n {label}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} sx={{ mt: 2 }}>\n <TextField\n select\n size=\"small\"\n label=\"From\"\n value={draft.window.startMinute}\n onChange={(event) =>\n setDraft({ ...draft, window: { ...draft.window, startMinute: Number(event.target.value) } })\n }\n >\n {MINUTE_OPTIONS.slice(0, -1).map((minute) => (\n <MenuItem key={minute} value={minute}>\n {formatMinuteOfDay(minute)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n size=\"small\"\n label=\"Until\"\n value={draft.window.endMinute}\n onChange={(event) =>\n setDraft({ ...draft, window: { ...draft.window, endMinute: Number(event.target.value) } })\n }\n >\n {MINUTE_OPTIONS.slice(1).map((minute) => (\n <MenuItem key={minute} value={minute}>\n {formatMinuteOfDay(minute)}\n </MenuItem>\n ))}\n </TextField>\n <Autocomplete\n size=\"small\"\n disableClearable\n options={timezoneOptions(mailbox.timezone)}\n value={draft.timezone}\n onChange={(_event, timezone) => setDraft({ ...draft, timezone: timezone ?? draft.timezone })}\n sx={{ minWidth: 240 }}\n renderInput={(params) => <TextField {...params} label=\"Timezone\" />}\n />\n </Stack>\n {'days' in windowCheck ? null : <FormHelperText error>{windowCheck.message}</FormHelperText>}\n </Box>\n {saveError ? <FormHelperText error>{saveError}</FormHelperText> : null}\n <Box>\n <Button variant=\"contained\" disabled={!dirty || invalid || saving} onClick={() => void save()}>\n {MAILBOX_ACTION_LABELS.save}\n </Button>\n </Box>\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Sends as ${sendAsLabel(stored.sendAs)}, at most ${stored.dailyCap} a day, ` +\n `${stored.window.days.map((day) => OUTREACH_WEEKDAY_LABELS[day]).join(', ')} ` +\n `${formatMinuteOfDay(stored.window.startMinute)}–${formatMinuteOfDay(stored.window.endMinute)} ` +\n `(${stored.timezone}).`}\n </Typography>\n )}\n\n {isMine && !needsReconnect ? (\n <TextField\n label={MAILBOX_ACTION_LABELS.testAddress}\n value={testAddress}\n onChange={(event) => setTestAddress(event.target.value)}\n disabled={busy !== null}\n size=\"small\"\n type=\"email\"\n helperText={\n 'Leave empty to send the test to yourself. A test to an outside mailbox you can ' +\n 'read is the only one whose original source shows the sender authentication results.'\n }\n fullWidth\n />\n ) : null}\n\n {canManage || isMine ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {canManage && !needsReconnect ? (\n <Button variant=\"outlined\" disabled={busy !== null} onClick={() => void togglePaused()}>\n {mailbox.status === 'paused' ? MAILBOX_ACTION_LABELS.resume : MAILBOX_ACTION_LABELS.pause}\n </Button>\n ) : null}\n {isMine && !needsReconnect ? (\n <Button variant=\"outlined\" disabled={busy !== null} onClick={() => void sendTest()}>\n {testAddress.trim() ? MAILBOX_ACTION_LABELS.testElsewhere : MAILBOX_ACTION_LABELS.test}\n </Button>\n ) : null}\n {canManage ? (\n <Button color=\"error\" disabled={busy !== null} onClick={() => void disconnect()}>\n {MAILBOX_ACTION_LABELS.disconnect}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nMailboxCard.displayName = 'MailboxCard'\n\nexport default MailboxCard\n"],"names":["pluginDocsHelp","CardDisplay","useConfirmationContext","useSnackbar","SenderReadinessPanel","Alert","Autocomplete","Box","Button","Chip","FormControlLabel","FormHelperText","MenuItem","Stack","Switch","TextField","ToggleButton","ToggleButtonGroup","Typography","useEffect","useMemo","useRef","useState","formatMinuteOfDay","isValidTimezone","OUTREACH_DEFAULT_WINDOW","OUTREACH_MAILBOX_STATUS_LABELS","OUTREACH_MAX_DAILY_CAP","OUTREACH_RAMP_STEPS","OUTREACH_WEEKDAY_LABELS","outreachEffectiveDailyCap","outreachLocalDay","summarizeMailboxHealth","validateDailyCap","validateDisplayName","validateSendWindow","autoPauseSentence","mailbox","pause","autoPause","day","atMs","Intl","DateTimeFormat","dateStyle","timeZone","timezone","format","until","untilMs","message","MAILBOX_ACTION_LABELS","save","resume","test","testElsewhere","testAddress","warmUp","disconnect","reconnect","recheckReadiness","WEEKDAY_NAMES","STATUS_COLOR","connected","paused","reconnect_required","disconnected","MINUTE_OPTIONS","Array","from","length","_","index","timezoneOptions","current","supported","supportedValuesOf","zones","includes","RAMP_SENTENCE","map","step","week","limit","join","draftOf","sendAs","email","displayName","dailyCap","String","window","rampStartedAtMs","MailboxCard","props","isMine","canManage","api","onReconnect","reconnecting","nowMs","Date","now","enqueueSnackbar","confirm","draft","setDraft","saving","setSaving","saveError","setSaveError","busy","setBusy","setTestAddress","sendAsAddress","readiness","setReadiness","readinessBusy","setReadinessBusy","active","id","then","answer","catch","error","variant","allowDuplicate","stored","storedKey","JSON","stringify","seededFrom","dirty","previous","health","testsToday","Math","max","Number","daily","tests","needsReconnect","status","capCheck","NaN","windowCheck","nameCheck","timezoneValid","invalid","todayLimit","saveSettings","mailboxId","togglePaused","setPaused","sendTest","to","trim","sent","undefined","sentTo","confirmed","title","description","confirmationText","confirmationButtonProps","color","revocation","persist","sendAsLabel","option","sendAsOptions","find","entry","header","subheader","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","size","label","spacing","severity","disabled","onClick","role","hasSent","direction","sx","mt","bounces","replies","value","aria-label","component","slice","lastIndexOf","onSubmit","event","preventDefault","select","onChange","target","helperText","type","slotProps","htmlInput","min","control","checked","gutterBottom","days","_event","sort","a","b","xs","sm","startMinute","minute","endMinute","disableClearable","options","minWidth","renderInput","params","fullWidth","useFlexGap","flexWrap"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAASA,cAAc,QAAQ,eAAc;AAC7C,SAASC,WAAW,EAAEC,sBAAsB,QAAQ,uBAAsB;AAC1E,SAASC,WAAW,QAAQ,8BAA6B;AACzD,OAAOC,0BAA0B,mEAAkE;AACnG,SACEC,KAAK,EACLC,YAAY,EACZC,GAAG,EACHC,MAAM,EACNC,IAAI,EACJC,gBAAgB,EAChBC,cAAc,EACdC,QAAQ,EACRC,KAAK,EACLC,MAAM,EACNC,SAAS,EACTC,YAAY,EACZC,iBAAiB,EACjBC,UAAU,QACL,gBAAe;AACtB,SAASC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AAC5D,SACEC,iBAAiB,EACjBC,eAAe,EACfC,uBAAuB,EACvBC,8BAA8B,EAC9BC,sBAAsB,EACtBC,mBAAmB,EACnBC,uBAAuB,EACvBC,yBAAyB,EACzBC,gBAAgB,EAChBC,sBAAsB,EACtBC,gBAAgB,EAChBC,mBAAmB,EACnBC,kBAAkB,QACb,mCAA+B;AAwBtC;;;;;CAKC,GACD,OAAO,SAASC,kBAAkBC,OAAwD;IACxF,MAAMC,QAAQD,QAAQE,SAAS;IAC/B,IAAI,CAACD,OAAO,OAAO;IACnB,MAAME,MAAM,CAACC;QACX,IAAI;YACF,OAAO,IAAIC,KAAKC,cAAc,CAAC,SAAS;gBAAEC,WAAW;gBAAUC,UAAUR,QAAQS,QAAQ;YAAC,GAAGC,MAAM,CAACN;QACtG,EAAE,eAAM;YACN,OAAO,IAAIC,KAAKC,cAAc,CAAC,SAAS;gBAAEC,WAAW;gBAAUC,UAAU;YAAM,GAAGE,MAAM,CAACN;QAC3F;IACF;IACA,MAAMO,QAAQ,OAAOV,MAAMW,OAAO,KAAK,WAAW,CAAC,YAAY,EAAET,IAAIF,MAAMW,OAAO,EAAE,oBAAoB,CAAC,GAAG;IAC5G,OAAO,CAAC,iCAAiC,EAAET,IAAIF,MAAMG,IAAI,EAAE,EAAE,EAAEH,MAAMY,OAAO,GAAGF,MAAM,2CAA2C,CAAC;AACnI;AAEA,wEAAwE,GACxE,OAAO,MAAMG,wBAAwB;IACnCC,MAAM;IACNd,OAAO;IACPe,QAAQ;IACRC,MAAM;IACNC,eAAe;IACfC,aAAa;IACbC,QAAQ;IACRC,YAAY;IACZC,WAAW;IACXC,kBAAkB;AACpB,EAAU;AAEV,MAAMC,gBAAgB;IAAC;IAAU;IAAU;IAAW;IAAa;IAAY;IAAU;CAAW;AAEpG,MAAMC,eAA2F;IAC/FC,WAAW;IACXC,QAAQ;IACRC,oBAAoB;IACpBC,cAAc;AAChB;AAEA,yDAAyD,GACzD,MAAMC,iBAAiBC,MAAMC,IAAI,CAAC;IAAEC,QAAQ;AAAG,GAAG,CAACC,GAAGC,QAAUA,QAAQ;AAExE,6EAA6E,GAC7E,SAASC,gBAAgBC,OAAe;;IACtC,MAAMC,oBAAY,AAACjC,KAA2DkC,iBAAiB,oBAA7E,AAAClC,KAA2DkC,iBAAiB,MAA5ElC,MAA+E,8BAAe,EAAE;IACnH,MAAMmC,QAAQF,UAAUL,MAAM,GAAGK,YAAY;QAAC;KAAM;IACpD,OAAOE,MAAMC,QAAQ,CAACJ,WAAWG,QAAQ;QAACH;WAAYG;KAAM;AAC9D;AAEA,iDAAiD,GACjD,MAAME,gBAAgB,CAAC,SAAS,EAAEnD,oBAAoBoD,GAAG,CAAC,CAACC,MAAMT,QAC/D,CAAC,KAAK,EAAES,KAAKC,IAAI,GAAGV,UAAU5C,oBAAoB0C,MAAM,GAAG,IAAI,MAAM,GAAG,EAAE,EAAEW,KAAKE,KAAK,EAAE,EACxFC,IAAI,CAAC,OAAO,4BAA4B,CAAC;AAY3C,MAAMC,UAAU,CAAChD;QACPA,iBACKA,sBACIA,mBACTA,iBACEA;WAL0C;QACpDiD,MAAM,GAAEjD,kBAAAA,QAAQiD,MAAM,YAAdjD,kBAAkBA,QAAQkD,KAAK;QACvCC,WAAW,GAAEnD,uBAAAA,QAAQmD,WAAW,YAAnBnD,uBAAuB;QACpCoD,UAAUC,QAAOrD,oBAAAA,QAAQoD,QAAQ,YAAhBpD,oBAAoB;QACrCsD,MAAM,GAAEtD,kBAAAA,QAAQsD,MAAM,YAAdtD,kBAAkBZ;QAC1BqB,QAAQ,GAAET,oBAAAA,QAAQS,QAAQ,YAAhBT,oBAAoB;QAC9BoB,QAAQ,OAAOpB,QAAQuD,eAAe,KAAK;IAC7C;;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASC,YAAYC,KAAuB;QA8EZzD,0BA6GpBX,gDACAoC;QA3HRzB,wCAAAA,uBAAAA,iBAmNKA;IAnRd,MAAM,EAAEA,OAAO,EAAE0D,MAAM,EAAEC,SAAS,EAAEC,GAAG,EAAEC,WAAW,EAAEC,eAAe,KAAK,EAAEC,QAAQC,KAAKC,GAAG,EAAE,EAAE,GAAGR;IACnG,MAAM,EAAES,eAAe,EAAE,GAAGpG;IAC5B,MAAM,EAAEqG,OAAO,EAAE,GAAGtG;IACpB,MAAM,CAACuG,OAAOC,SAAS,GAAGpF,SAAgB,IAAM+D,QAAQhD;IACxD,MAAM,CAACsE,QAAQC,UAAU,GAAGtF,SAAS;IACrC,MAAM,CAACuF,WAAWC,aAAa,GAAGxF,SAAwB;IAC1D,MAAM,CAACyF,MAAMC,QAAQ,GAAG1F,SAAkD;IAC1E,0EAA0E;IAC1E,4EAA4E;IAC5E,MAAM,CAACkC,aAAayD,eAAe,GAAG3F,SAAS;IAE/C;;;;;GAKC,GACD,MAAM4F,gBAAgB7E,QAAQiD,MAAM,IAAIjD,QAAQkD,KAAK;IACrD,MAAM,CAAC4B,WAAWC,aAAa,GAAG9F,SAAkD;IACpF,MAAM,CAAC+F,eAAeC,iBAAiB,GAAGhG,SAAS;IACnDH,UAAU;QACR,IAAIoG,SAAS;QACbtB,IACGkB,SAAS,CAAC9E,QAAQmF,EAAE,EACpBC,IAAI,CAAC,CAACC;YACL,IAAIH,QAAQH,aAAaM;QAC3B,GACCC,KAAK,CAAC;YACL,IAAIJ,QAAQH,aAAa;QAC3B;QACF,OAAO;YACLG,SAAS;QACX;IACF,GAAG;QAACtB;QAAK5D,QAAQmF,EAAE;QAAEN;KAAc;IACnC,MAAMtD,mBAAmB;QACvB0D,iBAAiB;QACjB,IAAI;YACFF,aAAa,MAAMnB,IAAIkB,SAAS,CAAC9E,QAAQmF,EAAE,EAAE;QAC/C,EAAE,OAAOI,OAAO;YACdrB,gBAAgB,AAACqB,MAAgB1E,OAAO,EAAE;gBAAE2E,SAAS;gBAASC,gBAAgB;YAAK;QACrF,SAAU;YACRR,iBAAiB;QACnB;IACF;IAEA,2EAA2E;IAC3E,wEAAwE;IACxE,mDAAmD;IACnD,MAAMS,SAAS3G,QAAQ,IAAMiE,QAAQhD,UAAU;QAACA;KAAQ;IACxD,MAAM2F,YAAYC,KAAKC,SAAS,CAACH;IACjC,MAAMI,aAAa9G,OAAO2G;IAC1B,MAAMI,QAAQH,KAAKC,SAAS,CAACzB,WAAWuB;IACxC7G,UAAU;QACR,IAAIgH,WAAWzD,OAAO,KAAKsD,WAAW;QACtC,MAAMK,WAAWF,WAAWzD,OAAO;QACnCyD,WAAWzD,OAAO,GAAGsD;QACrBtB,SAAS,CAAChC,UAAauD,KAAKC,SAAS,CAACxD,aAAa2D,WAAWN,SAASrD;IACzE,GAAG;QAACsD;QAAWD;KAAO;IAEtB,MAAMO,SAAStG,uBAAuBK,QAAQiG,MAAM,EAAEjG,QAAQS,QAAQ,EAAEsD;IACxE,qEAAqE;IACrE,yEAAyE;IACzE,MAAMmC,aAAaC,KAAKC,GAAG,CACzB,GACAC,QAAOrG,kBAAAA,QAAQiG,MAAM,sBAAdjG,wBAAAA,gBAAgBsG,KAAK,sBAArBtG,yCAAAA,qBAAuB,CAACN,iBAAiBqE,OAAO/D,QAAQS,QAAQ,EAAE,qBAAlET,uCAAoEuG,KAAK,KAAK;IAEvF,MAAMC,iBAAiBxG,QAAQyG,MAAM,KAAK,wBAAwBzG,QAAQyG,MAAM,KAAK;IACrF,MAAMC,WAAW9G,iBAAiBwE,MAAMhB,QAAQ,KAAK,KAAKiD,OAAOM,GAAG,GAAGN,OAAOjC,MAAMhB,QAAQ;IAC5F,MAAMwD,cAAc9G,mBAAmBsE,MAAMd,MAAM;IACnD,MAAMuD,YAAYhH,oBAAoBuE,MAAMjB,WAAW;IACvD,MAAM2D,gBAAgB3H,gBAAgBiF,MAAM3D,QAAQ;IACpD,MAAMsG,UACJ,OAAOL,aAAa,YAAY,CAAE,CAAA,UAAUE,WAAU,KAAM,OAAOC,cAAc,YAAY,CAACC;IAChG,MAAME,aAAavH,0BACjB;QACE2D,UAAU,OAAOsD,aAAa,WAAWA,WAAW1G,QAAQoD,QAAQ;QACpE,2DAA2D;QAC3DG,iBAAiBa,MAAMhD,MAAM,IAAIpB,2BAAAA,QAAQuD,eAAe,YAAvBvD,2BAA2B+D,QAAS;QACrEtD,UAAUqG,gBAAgB1C,MAAM3D,QAAQ,GAAGT,QAAQS,QAAQ;IAC7D,GACAsD;IAGF,MAAMhD,OAAO;QACXwD,UAAU;QACVE,aAAa;QACb,IAAI;YACF,MAAMb,IAAIqD,YAAY,CAAC;gBACrBC,WAAWlH,QAAQmF,EAAE;eACjBf,MAAMnB,MAAM,KAAKyC,OAAOzC,MAAM,GAAG;gBAAEA,QAAQmB,MAAMnB,MAAM;YAAC,IAAI,CAAC,GAC7DmB,MAAMjB,WAAW,KAAKuC,OAAOvC,WAAW,GAAG;gBAAEA,aAAaiB,MAAMjB,WAAW;YAAC,IAAI,CAAC,GACjFiB,MAAMhB,QAAQ,KAAKsC,OAAOtC,QAAQ,GAAG;gBAAEA,UAAUiD,OAAOjC,MAAMhB,QAAQ;YAAE,IAAI,CAAC,GAC7EwC,KAAKC,SAAS,CAACzB,MAAMd,MAAM,MAAMsC,KAAKC,SAAS,CAACH,OAAOpC,MAAM,IAAI;gBAAEA,QAAQc,MAAMd,MAAM;YAAC,IAAI,CAAC,GAC7Fc,MAAM3D,QAAQ,KAAKiF,OAAOjF,QAAQ,GAAG;gBAAEA,UAAU2D,MAAM3D,QAAQ;YAAC,IAAI,CAAC,GACrE2D,MAAMhD,MAAM,KAAKsE,OAAOtE,MAAM,GAAG;gBAAEA,QAAQgD,MAAMhD,MAAM;YAAC,IAAI,CAAC;YAEnE8C,gBAAgB,0BAA0B;gBAAEsB,SAAS;YAAU;QACjE,EAAE,OAAOD,OAAO;YACdd,aAAa,AAACc,MAAgB1E,OAAO;QACvC,SAAU;YACR0D,UAAU;QACZ;IACF;IAEA,MAAM4C,eAAe;QACnB,MAAMxF,SAAS3B,QAAQyG,MAAM,KAAK;QAClC9B,QAAQ;QACR,IAAI;YACF,MAAMf,IAAIwD,SAAS,CAACpH,QAAQmF,EAAE,EAAExD;YAChCuC,gBAAgBvC,SAAS,mBAAmB,mBAAmB;gBAAE6D,SAAS;YAAU;QACtF,EAAE,OAAOD,OAAO;YACdrB,gBAAgB,AAACqB,MAAgB1E,OAAO,EAAE;gBAAE2E,SAAS;gBAASC,gBAAgB;YAAK;QACrF,SAAU;YACRd,QAAQ;QACV;IACF;IAEA,MAAM0C,WAAW;QACf1C,QAAQ;QACR,IAAI;YACF,MAAM2C,KAAKnG,YAAYoG,IAAI;YAC3B,MAAMC,OAAO,MAAM5D,IAAIyD,QAAQ,CAACrH,QAAQmF,EAAE,EAAEmC,MAAMG;YAClDvD,gBACEoD,KACI,CAAC,aAAa,EAAEE,KAAKE,MAAM,CAAC,6DAA6D,CAAC,GAC1F,CAAC,aAAa,EAAEF,KAAKE,MAAM,CAAC,mBAAmB,CAAC,EACpD;gBAAElC,SAAS;YAAU;QAEzB,EAAE,OAAOD,OAAO;YACdrB,gBAAgB,AAACqB,MAAgB1E,OAAO,EAAE;gBAAE2E,SAAS;gBAASC,gBAAgB;YAAK;QACrF,SAAU;YACRd,QAAQ;QACV;IACF;IAEA,MAAMtD,aAAa;QACjB,MAAMsG,YAAY,MAAMxD,QAAQ;YAC9ByD,OAAO;YACPC,aACE,4FACA;YACFC,kBAAkBhH,sBAAsBO,UAAU;YAClD0G,yBAAyB;gBAAEC,OAAO;YAAQ;QAC5C,GACG5C,IAAI,CAAC,IAAM,MACXE,KAAK,CAAC,IAAM;QACf,IAAI,CAACqC,WAAW;QAChBhD,QAAQ;QACR,IAAI;YACF,MAAM,EAAEsD,UAAU,EAAE,GAAG,MAAMrE,IAAIvC,UAAU,CAACrB,QAAQmF,EAAE;YACtD,IAAI8C,eAAe,UAAU;gBAC3B/D,gBACE,gFACE,2EACF;oBAAEsB,SAAS;oBAAW0C,SAAS;gBAAK;YAExC,OAAO,IAAID,eAAe,0BAA0B;gBAClD/D,gBACE,yGACA;oBAAEsB,SAAS;gBAAU;YAEzB,OAAO;gBACLtB,gBAAgB,wBAAwB;oBAAEsB,SAAS;gBAAU;YAC/D;QACF,EAAE,OAAOD,OAAO;YACdrB,gBAAgB,AAACqB,MAAgB1E,OAAO,EAAE;gBAAE2E,SAAS;gBAASC,gBAAgB;YAAK;YACnFd,QAAQ;QACV;IACF;IAEA,MAAMwD,cAAc,CAACjF;YACJlD;QAAf,MAAMoI,UAASpI,yBAAAA,QAAQqI,aAAa,qBAArBrI,uBAAuBsI,IAAI,CAAC,CAACC,QAAUA,MAAMrF,KAAK,KAAKA;QACtE,OAAOkF,CAAAA,0BAAAA,OAAQjF,WAAW,IAAG,GAAGiF,OAAOjF,WAAW,CAAC,EAAE,EAAEiF,OAAOlF,KAAK,CAAC,CAAC,CAAC,GAAGA;IAC3E;IAEA,qBACE,KAACtF;QACC4K,QAAQxI,QAAQkD,KAAK;QACrBuF,WAAW/E,SAAS,iBAAiB;QACrCgF,MAAM/K,eAAe,aAAa;YAAEgL,QAAQ;QAAqB;QACjEC,cAAc;QACdC,cAAc;QACdC,aAAa;YACXC,sBACE,KAAC3K;gBACC4K,MAAK;gBACLC,KAAK,GAAE5J,iDAAAA,8BAA8B,CAACW,QAAQyG,MAAM,CAAC,YAA9CpH,iDAAkDW,QAAQyG,MAAM;gBACvEuB,KAAK,GAAEvG,+BAAAA,YAAY,CAACzB,QAAQyG,MAAM,CAAC,YAA5BhF,+BAAgC;;QAG7C;kBAEA,cAAA,MAACjD;YAAM0K,SAAS;;gBACb1C,+BACC,KAACxI;oBACCmL,UAAS;oBACTJ,QACErF,uBACE,KAACvF;wBAAO6J,OAAM;wBAAUgB,MAAK;wBAAQI,UAAUtF;wBAAcuF,SAASxF;kCACnE/C,sBAAsBQ,SAAS;yBAEhCmG;8BAGL,GACCzH,QAAQyG,MAAM,KAAK,iBACf,mCACA,sDACL,CAAC,EACA/C,SACI,sDACA,sDACJ;qBAEF;gBACH1D,QAAQyG,MAAM,KAAK,YAAYzG,QAAQE,SAAS,iBAC/C,KAAClC;oBAAMmL,UAAS;oBAAQG,MAAK;8BAC1BvJ,kBAAkBC;qBAEnB;8BAEJ,MAAC9B;;sCACC,KAACW;4BAAW2G,SAAQ;sCAAY;;wBAC/BS,OAAOsD,OAAO,GAAG,qBAChB,KAAC1K;4BAAW2G,SAAQ;4BAAQwC,OAAM;sCAAiB;;sCAIrD,KAACxJ;4BAAMgL,WAAU;4BAAMN,SAAS;4BAAGO,IAAI;gCAAEC,IAAI;4BAAI;sCAC9C;gCACC;oCAAC;oCAAQzD,OAAOuB,IAAI;iCAAC;gCACrB;oCAAC;oCAAWvB,OAAO0D,OAAO;iCAAC;gCAC3B;oCAAC;oCAAW1D,OAAO2D,OAAO;iCAAC;6BAC5B,CAACjH,GAAG,CAAC,CAAC,CAACsG,OAAOY,MAAM,iBACnB,MAAC3L;oCAA0B4L,cAAY,GAAGb,MAAM,mBAAmB,CAAC;;sDAClE,KAACpK;4CAAW2G,SAAQ;4CAAKuE,WAAU;sDAChCF;;sDAEH,KAAChL;4CAAW2G,SAAQ;4CAAUwC,OAAM;sDACjCiB;;;mCALKA;;sCAUd,KAACpK;4BAAW2G,SAAQ;4BAAUwC,OAAM;4BAAiB8B,cAAW;sCAC7D,CAAC,aAAa,EAAE5D,YAAY;;;;gBAIhCpB,CAAAA,6BAAAA,UAAWA,SAAS,kBACnB,MAACtG;oBAAM0K,SAAS;;sCACd,KAACnL;4BAAqB+G,WAAWA,UAAUA,SAAS;;sCACpD,KAAC5G;sCACC,cAAA,KAACC;gCAAO6K,MAAK;gCAAQI,UAAUpE;gCAAeqE,SAAS,IAAM,KAAK9H;0CAC/DT,sBAAsBS,gBAAgB;;;;qBAI3CuD,0BACF,KAACjG;oBAAW2G,SAAQ;oBAAUwC,OAAM;8BACjC,CAAC,+BAA+B,EAAElD,UAAU7B,MAAM,CAAC+G,KAAK,CAAClF,UAAU7B,MAAM,CAACgH,WAAW,CAAC,OAAO,GAAG,CAAC,CAAC,GACjG;qBAEF;gBAEHtG,0BACC,MAACnF;oBAAM0K,SAAS;oBAAGa,WAAU;oBAAOG,UAAU,CAACC,QAAUA,MAAMC,cAAc;;sCAC3E,KAAC1L;4BACC2L,MAAM;4BACNrB,MAAK;4BACLC,OAAM;4BACNY,OAAOzF,MAAMnB,MAAM;4BACnBqH,UAAU,CAACH,QAAU9F,SAAS,aAAKD;oCAAOnB,QAAQkH,MAAMI,MAAM,CAACV,KAAK;;4BACpEW,YAAW;sCAEV,AAACxK,CAAAA,EAAAA,yBAAAA,QAAQqI,aAAa,qBAArBrI,uBAAuBiC,MAAM,IAAGjC,QAAQqI,aAAa,GAAG;gCAAC;oCAAEnF,OAAOlD,QAAQiD,MAAM;gCAAC;6BAAE,AAAD,EAAGN,GAAG,CAAC,CAACyF,uBAC1F,KAAC7J;oCAA4BsL,OAAOzB,OAAOlF,KAAK;8CAC7CiF,YAAYC,OAAOlF,KAAK;mCADZkF,OAAOlF,KAAK;;sCAK/B,KAACxE;4BACCsK,MAAK;4BACLC,OAAM;4BACNY,OAAOzF,MAAMjB,WAAW;4BACxBmH,UAAU,CAACH,QAAU9F,SAAS,aAAKD;oCAAOjB,aAAagH,MAAMI,MAAM,CAACV,KAAK;;4BACzEtE,OAAO,OAAOsB,cAAc;4BAC5B2D,YAAY,OAAO3D,cAAc,WAAWA,UAAUhG,OAAO,GAAG;;sCAElE,KAACnC;4BACCsK,MAAK;4BACLyB,MAAK;4BACLxB,OAAM;4BACNY,OAAOzF,MAAMhB,QAAQ;4BACrBkH,UAAU,CAACH,QAAU9F,SAAS,aAAKD;oCAAOhB,UAAU+G,MAAMI,MAAM,CAACV,KAAK;;4BACtEtE,OAAO,OAAOmB,aAAa;4BAC3BgE,WAAW;gCAAEC,WAAW;oCAAEC,KAAK;oCAAGxE,KAAK9G;oCAAwBsD,MAAM;gCAAE;4BAAE;4BACzE4H,YACE,OAAO9D,aAAa,WAChBA,SAAS7F,OAAO,GAChBuD,MAAMhD,MAAM,GACV,GAAGsB,cAAc,gBAAgB,EAAEsE,WAAW,CAAC,CAAC,GAChD,CAAC,sCAAsC,EAAEA,WAAW,CAAC,CAAC;;sCAGhE,KAAC3I;4BACCwM,uBACE,KAACpM;gCACCqM,SAAS1G,MAAMhD,MAAM;gCACrBkJ,UAAU,CAACH,QAAU9F,SAAS,aAAKD;wCAAOhD,QAAQ+I,MAAMI,MAAM,CAACO,OAAO;;;4BAG1E7B,OAAOnI,sBAAsBM,MAAM;;sCAErC,KAAC9C;4BAAemL,IAAI;gCAAEC,IAAI,CAAC;4BAAE;sCAC1B,kGACC;;sCAEJ,MAACxL;;8CACC,KAACW;oCAAW2G,SAAQ;oCAAYuF,YAAY;8CAAC;;8CAG7C,KAACnM;oCACCoK,MAAK;oCACLc,cAAW;oCACXD,OAAOzF,MAAMd,MAAM,CAAC0H,IAAI;oCACxBV,UAAU,CAACW,QAAQD,OACjB3G,SAAS,aAAKD;4CAAOd,QAAQ,aAAKc,MAAMd,MAAM;gDAAE0H,MAAM;uDAAIA;iDAAK,CAACE,IAAI,CAAC,CAACC,GAAGC,IAAMD,IAAIC;;;8CAGpF5L,wBAAwBmD,GAAG,CAAC,CAACsG,OAAO9I,oBACnC,KAACxB;4CAAyBkL,OAAO1J;4CAAK2J,cAAYtI,aAAa,CAACrB,IAAI;sDACjE8I;2CADgBA;;8CAKvB,MAACzK;oCAAMgL,WAAW;wCAAE6B,IAAI;wCAAUC,IAAI;oCAAM;oCAAGpC,SAAS;oCAAGO,IAAI;wCAAEC,IAAI;oCAAE;;sDACrE,KAAChL;4CACC2L,MAAM;4CACNrB,MAAK;4CACLC,OAAM;4CACNY,OAAOzF,MAAMd,MAAM,CAACiI,WAAW;4CAC/BjB,UAAU,CAACH,QACT9F,SAAS,aAAKD;oDAAOd,QAAQ,aAAKc,MAAMd,MAAM;wDAAEiI,aAAalF,OAAO8D,MAAMI,MAAM,CAACV,KAAK;;;sDAGvF/H,eAAekI,KAAK,CAAC,GAAG,CAAC,GAAGrH,GAAG,CAAC,CAAC6I,uBAChC,KAACjN;oDAAsBsL,OAAO2B;8DAC3BtM,kBAAkBsM;mDADNA;;sDAKnB,KAAC9M;4CACC2L,MAAM;4CACNrB,MAAK;4CACLC,OAAM;4CACNY,OAAOzF,MAAMd,MAAM,CAACmI,SAAS;4CAC7BnB,UAAU,CAACH,QACT9F,SAAS,aAAKD;oDAAOd,QAAQ,aAAKc,MAAMd,MAAM;wDAAEmI,WAAWpF,OAAO8D,MAAMI,MAAM,CAACV,KAAK;;;sDAGrF/H,eAAekI,KAAK,CAAC,GAAGrH,GAAG,CAAC,CAAC6I,uBAC5B,KAACjN;oDAAsBsL,OAAO2B;8DAC3BtM,kBAAkBsM;mDADNA;;sDAKnB,KAACvN;4CACC+K,MAAK;4CACL0C,gBAAgB;4CAChBC,SAASvJ,gBAAgBpC,QAAQS,QAAQ;4CACzCoJ,OAAOzF,MAAM3D,QAAQ;4CACrB6J,UAAU,CAACW,QAAQxK,WAAa4D,SAAS,aAAKD;oDAAO3D,QAAQ,EAAEA,mBAAAA,WAAY2D,MAAM3D,QAAQ;;4CACzFgJ,IAAI;gDAAEmC,UAAU;4CAAI;4CACpBC,aAAa,CAACC,uBAAW,KAACpN,wBAAcoN;oDAAQ7C,OAAM;;;;;gCAGzD,UAAUrC,cAAc,qBAAO,KAACtI;oCAAeiH,KAAK;8CAAEqB,YAAY/F,OAAO;;;;wBAE3E2D,0BAAY,KAAClG;4BAAeiH,KAAK;sCAAEf;6BAA8B;sCAClE,KAACtG;sCACC,cAAA,KAACC;gCAAOqH,SAAQ;gCAAY4D,UAAU,CAACrD,SAASgB,WAAWzC;gCAAQ+E,SAAS,IAAM,KAAKtI;0CACpFD,sBAAsBC,IAAI;;;;mCAKjC,KAAClC;oBAAW2G,SAAQ;oBAAQwC,OAAM;8BAC/B,CAAC,SAAS,EAAEG,YAAYzC,OAAOzC,MAAM,EAAE,UAAU,EAAEyC,OAAOtC,QAAQ,CAAC,QAAQ,CAAC,GAC3E,GAAGsC,OAAOpC,MAAM,CAAC0H,IAAI,CAACrI,GAAG,CAAC,CAACxC,MAAQX,uBAAuB,CAACW,IAAI,EAAE4C,IAAI,CAAC,MAAM,CAAC,CAAC,GAC9E,GAAG7D,kBAAkBwG,OAAOpC,MAAM,CAACiI,WAAW,EAAE,CAAC,EAAErM,kBAAkBwG,OAAOpC,MAAM,CAACmI,SAAS,EAAE,CAAC,CAAC,GAChG,CAAC,CAAC,EAAE/F,OAAOjF,QAAQ,CAAC,EAAE,CAAC;;gBAI5BiD,UAAU,CAAC8C,+BACV,KAAC9H;oBACCuK,OAAOnI,sBAAsBK,WAAW;oBACxC0I,OAAO1I;oBACPmJ,UAAU,CAACH,QAAUvF,eAAeuF,MAAMI,MAAM,CAACV,KAAK;oBACtDT,UAAU1E,SAAS;oBACnBsE,MAAK;oBACLyB,MAAK;oBACLD,YACE,oFACA;oBAEFuB,SAAS;qBAET;gBAEHpI,aAAaD,uBACZ,MAAClF;oBAAMgL,WAAU;oBAAMN,SAAS;oBAAG8C,UAAU;oBAACvC,IAAI;wBAAEwC,UAAU;oBAAO;;wBAClEtI,aAAa,CAAC6C,+BACb,KAACrI;4BAAOqH,SAAQ;4BAAW4D,UAAU1E,SAAS;4BAAM2E,SAAS,IAAM,KAAKlC;sCACrEnH,QAAQyG,MAAM,KAAK,WAAW3F,sBAAsBE,MAAM,GAAGF,sBAAsBb,KAAK;6BAEzF;wBACHyD,UAAU,CAAC8C,+BACV,KAACrI;4BAAOqH,SAAQ;4BAAW4D,UAAU1E,SAAS;4BAAM2E,SAAS,IAAM,KAAKhC;sCACrElG,YAAYoG,IAAI,KAAKzG,sBAAsBI,aAAa,GAAGJ,sBAAsBG,IAAI;6BAEtF;wBACH0C,0BACC,KAACxF;4BAAO6J,OAAM;4BAAQoB,UAAU1E,SAAS;4BAAM2E,SAAS,IAAM,KAAKhI;sCAChEP,sBAAsBO,UAAU;6BAEjC;;qBAEJ;;;;AAIZ;AACAmC,YAAYL,WAAW,GAAG;AAE1B,eAAeK,YAAW"}
@@ -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"}
@@ -14,6 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
+ import type { SenderReadiness } from '@aglyn/shared-util-email';
17
18
  import type { OutreachMailbox, OutreachSendWindow } from '../model/outreach.types';
18
19
  /**
19
20
  * THE MAILBOX ROUTES' CONTRACT (AGL-2978): what the Mailboxes panel sends,
@@ -124,6 +125,18 @@ export interface OutreachMailboxDisconnectResponse {
124
125
  */
125
126
  revocation: 'revoked' | 'already-invalid' | 'kept-for-other-mailbox' | 'failed';
126
127
  }
128
+ /**
129
+ * `POST outreach/mailboxes/readiness` — `{ mailboxId, fresh? }`. How
130
+ * receivers judge mail from the address the mailbox sends as (AGL-3328):
131
+ * `readiness` is null for a consumer Gmail address, whose records are
132
+ * Google's own and have nothing for its owner to publish.
133
+ */
134
+ export interface OutreachMailboxReadinessResponse {
135
+ ok: true;
136
+ /** The address whose domain was read. */
137
+ sendAs: string;
138
+ readiness: SenderReadiness | null;
139
+ }
127
140
  /**
128
141
  * The fragment key a connect returns through (AGL-2978).
129
142
  *
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/mailboxes/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\nimport type { OutreachMailbox, OutreachSendWindow } from '../model/outreach.types'\n\n/**\n * THE MAILBOX ROUTES' CONTRACT (AGL-2978): what the Mailboxes panel sends,\n * what the routes answer, and the fragment a connect comes back through.\n * Client-safe — types, constants and two string functions.\n */\n\n/** Every reason a mailbox route refuses with, for a caller to branch on. */\nexport type OutreachApiRefusalReason =\n | 'unauthenticated'\n | 'email-unverified'\n | 'org-required'\n | 'not-a-member'\n | 'not-org-wide'\n | 'permission'\n | 'entitlement'\n | 'not-configured'\n | 'method-not-allowed'\n | 'invalid-request'\n | 'mailbox-not-found'\n | 'not-your-mailbox'\n | 'mailbox-limit'\n | 'state-invalid'\n | 'state-expired'\n | 'state-user-mismatch'\n | 'state-org-mismatch'\n | 'state-replayed'\n | 'state-superseded'\n | 'code-rejected'\n | 'refresh-token-missing'\n | 'scopes-missing'\n | 'identity-unverified'\n | 'account-mismatch'\n | 'reconnect-required'\n | 'invalid-settings'\n | 'rate-limited'\n | 'google-unavailable'\n\nexport interface OutreachApiRefusal {\n error: string\n reason: OutreachApiRefusalReason\n}\n\n/** A reason `GET outreach/mailboxes/availability` answers `configured: false`. */\nexport type OutreachMailboxAvailabilityGate =\n | { gate: 'google'; missing: string[] }\n | { gate: 'state' }\n | { gate: 'redirect' }\n\n/** `GET outreach/mailboxes/availability` */\nexport interface OutreachMailboxAvailability {\n configured: boolean\n /**\n * When not configured, which gates refused, so an operator reading the\n * response knows what to set: the Google client (`google`, with the names\n * of the missing variables), the state signing secret (`state`), or the\n * console origin to register (`redirect`). Absent when configured.\n */\n missing?: OutreachMailboxAvailabilityGate[]\n /**\n * Whether the viewer is an organization owner or admin, who may change,\n * pause and disconnect any member's mailbox. The routes decide this on\n * every call; the panel reads it to offer only what will be allowed.\n */\n canManageAll: boolean\n}\n\n/** `POST outreach/mailboxes/connect` */\nexport interface OutreachConnectRequest {\n orgId: string\n}\nexport interface OutreachConnectResponse {\n /** Google's consent address; the browser goes there. */\n url: string\n}\n\n/** `POST outreach/mailboxes/connect/complete` */\nexport interface OutreachConnectCompleteRequest {\n orgId: string\n code: string\n state: string\n /** The browser's IANA timezone, for a new mailbox's sending window. */\n timezone?: string\n}\nexport interface OutreachConnectCompleteResponse {\n ok: true\n mailbox: OutreachMailbox\n /** False when an existing mailbox for the account was reconnected. */\n created: boolean\n /** Pending addresses of the member's that Gmail's send-as list confirmed. */\n confirmedAliases: string[]\n}\n\n/** `POST outreach/mailboxes/settings` — every field but the ids is optional. */\nexport interface OutreachMailboxSettingsRequest {\n orgId: string\n mailboxId: string\n sendAs?: string\n displayName?: string\n dailyCap?: number\n window?: OutreachSendWindow\n timezone?: string\n /**\n * Whether the warm-up ramp applies (AGL-3228). `false` clears it, so an\n * established mailbox sends at its cap from today; `true` starts one now\n * when none is running.\n */\n warmUp?: boolean\n}\n\n/** `POST outreach/mailboxes/status` */\nexport interface OutreachMailboxStatusRequest {\n orgId: string\n mailboxId: string\n paused: boolean\n}\n\nexport interface OutreachMailboxResponse {\n ok: true\n mailbox: OutreachMailbox\n}\n\n/**\n * `POST outreach/mailboxes/test` — `{ mailboxId, to? }`. `to` is the address\n * the test goes to; left off, the account's own (AGL-3228).\n */\nexport interface OutreachMailboxTestResponse {\n ok: true\n /** The address the test went to. */\n sentTo: string\n gmailMessageId: string\n sentAtMs: number\n}\n\n/** `POST outreach/mailboxes/disconnect` */\nexport interface OutreachMailboxDisconnectResponse {\n ok: true\n /**\n * What became of the grant at Google: `revoked`, `already-invalid`,\n * `kept-for-other-mailbox` when another mailbox still uses the account,\n * or `failed` when Google could not be told — the stored grant is deleted\n * either way.\n */\n revocation: 'revoked' | 'already-invalid' | 'kept-for-other-mailbox' | 'failed'\n}\n\n/**\n * The fragment key a connect returns through (AGL-2978).\n *\n * Google redirects to the callback route with the authorization code in the\n * query; the callback moves it into the FRAGMENT of the Mailboxes page, which\n * no server receives — not in a request line, not in a `Referer` — and the\n * page takes it out of the address bar before it finishes the connect with\n * the member's own session. The cross-domain session handoff uses the\n * fragment for the same reason.\n */\nexport const OUTREACH_CONNECT_FRAGMENT_KEY = 'outreachConnect'\n\n/** Why a connect came back without a code. */\nexport type OutreachConnectReturnError = 'access_denied' | 'expired' | 'google_error'\n\nexport type OutreachConnectReturn =\n | { kind: 'code'; code: string; state: string }\n | { kind: 'error'; reason: OutreachConnectReturnError }\n\n/** The fragment for a connect's return, without the leading `#`. */\nexport function buildConnectReturnFragment(value: OutreachConnectReturn): string {\n const params = new URLSearchParams()\n if (value.kind === 'code') {\n params.set(OUTREACH_CONNECT_FRAGMENT_KEY, 'code')\n params.set('code', value.code)\n params.set('state', value.state)\n } else {\n params.set(OUTREACH_CONNECT_FRAGMENT_KEY, 'error')\n params.set('reason', value.reason)\n }\n return params.toString()\n}\n\nconst RETURN_ERRORS: readonly OutreachConnectReturnError[] = ['access_denied', 'expired', 'google_error']\n\n/** A connect's return read off a location hash, or `null` when it carries none. */\nexport function parseConnectReturnFragment(hash: string | null | undefined): OutreachConnectReturn | null {\n const params = new URLSearchParams(String(hash ?? '').replace(/^#/, ''))\n const kind = params.get(OUTREACH_CONNECT_FRAGMENT_KEY)\n if (kind === 'code') {\n const code = params.get('code') ?? ''\n const state = params.get('state') ?? ''\n return code && state ? { kind: 'code', code, state } : null\n }\n if (kind === 'error') {\n const reason = params.get('reason') as OutreachConnectReturnError | null\n return { kind: 'error', reason: reason && RETURN_ERRORS.includes(reason) ? reason : 'google_error' }\n }\n return null\n}\n"],"names":["OUTREACH_CONNECT_FRAGMENT_KEY","buildConnectReturnFragment","value","params","URLSearchParams","kind","set","code","state","reason","toString","RETURN_ERRORS","parseConnectReturnFragment","hash","String","replace","get","includes"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAqJD;;;;;;;;;CASC,GACD,OAAO,MAAMA,gCAAgC,kBAAiB;AAS9D,kEAAkE,GAClE,OAAO,SAASC,2BAA2BC,KAA4B;IACrE,MAAMC,SAAS,IAAIC;IACnB,IAAIF,MAAMG,IAAI,KAAK,QAAQ;QACzBF,OAAOG,GAAG,CAACN,+BAA+B;QAC1CG,OAAOG,GAAG,CAAC,QAAQJ,MAAMK,IAAI;QAC7BJ,OAAOG,GAAG,CAAC,SAASJ,MAAMM,KAAK;IACjC,OAAO;QACLL,OAAOG,GAAG,CAACN,+BAA+B;QAC1CG,OAAOG,GAAG,CAAC,UAAUJ,MAAMO,MAAM;IACnC;IACA,OAAON,OAAOO,QAAQ;AACxB;AAEA,MAAMC,gBAAuD;IAAC;IAAiB;IAAW;CAAe;AAEzG,iFAAiF,GACjF,OAAO,SAASC,2BAA2BC,IAA+B;IACxE,MAAMV,SAAS,IAAIC,gBAAgBU,OAAOD,eAAAA,OAAQ,IAAIE,OAAO,CAAC,MAAM;IACpE,MAAMV,OAAOF,OAAOa,GAAG,CAAChB;IACxB,IAAIK,SAAS,QAAQ;YACNF,aACCA;QADd,MAAMI,QAAOJ,cAAAA,OAAOa,GAAG,CAAC,mBAAXb,cAAsB;QACnC,MAAMK,SAAQL,eAAAA,OAAOa,GAAG,CAAC,oBAAXb,eAAuB;QACrC,OAAOI,QAAQC,QAAQ;YAAEH,MAAM;YAAQE;YAAMC;QAAM,IAAI;IACzD;IACA,IAAIH,SAAS,SAAS;QACpB,MAAMI,SAASN,OAAOa,GAAG,CAAC;QAC1B,OAAO;YAAEX,MAAM;YAASI,QAAQA,UAAUE,cAAcM,QAAQ,CAACR,UAAUA,SAAS;QAAe;IACrG;IACA,OAAO;AACT"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/mailboxes/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\nimport type { SenderReadiness } from '@aglyn/shared-util-email'\nimport type { OutreachMailbox, OutreachSendWindow } from '../model/outreach.types'\n\n/**\n * THE MAILBOX ROUTES' CONTRACT (AGL-2978): what the Mailboxes panel sends,\n * what the routes answer, and the fragment a connect comes back through.\n * Client-safe — types, constants and two string functions.\n */\n\n/** Every reason a mailbox route refuses with, for a caller to branch on. */\nexport type OutreachApiRefusalReason =\n | 'unauthenticated'\n | 'email-unverified'\n | 'org-required'\n | 'not-a-member'\n | 'not-org-wide'\n | 'permission'\n | 'entitlement'\n | 'not-configured'\n | 'method-not-allowed'\n | 'invalid-request'\n | 'mailbox-not-found'\n | 'not-your-mailbox'\n | 'mailbox-limit'\n | 'state-invalid'\n | 'state-expired'\n | 'state-user-mismatch'\n | 'state-org-mismatch'\n | 'state-replayed'\n | 'state-superseded'\n | 'code-rejected'\n | 'refresh-token-missing'\n | 'scopes-missing'\n | 'identity-unverified'\n | 'account-mismatch'\n | 'reconnect-required'\n | 'invalid-settings'\n | 'rate-limited'\n | 'google-unavailable'\n\nexport interface OutreachApiRefusal {\n error: string\n reason: OutreachApiRefusalReason\n}\n\n/** A reason `GET outreach/mailboxes/availability` answers `configured: false`. */\nexport type OutreachMailboxAvailabilityGate =\n | { gate: 'google'; missing: string[] }\n | { gate: 'state' }\n | { gate: 'redirect' }\n\n/** `GET outreach/mailboxes/availability` */\nexport interface OutreachMailboxAvailability {\n configured: boolean\n /**\n * When not configured, which gates refused, so an operator reading the\n * response knows what to set: the Google client (`google`, with the names\n * of the missing variables), the state signing secret (`state`), or the\n * console origin to register (`redirect`). Absent when configured.\n */\n missing?: OutreachMailboxAvailabilityGate[]\n /**\n * Whether the viewer is an organization owner or admin, who may change,\n * pause and disconnect any member's mailbox. The routes decide this on\n * every call; the panel reads it to offer only what will be allowed.\n */\n canManageAll: boolean\n}\n\n/** `POST outreach/mailboxes/connect` */\nexport interface OutreachConnectRequest {\n orgId: string\n}\nexport interface OutreachConnectResponse {\n /** Google's consent address; the browser goes there. */\n url: string\n}\n\n/** `POST outreach/mailboxes/connect/complete` */\nexport interface OutreachConnectCompleteRequest {\n orgId: string\n code: string\n state: string\n /** The browser's IANA timezone, for a new mailbox's sending window. */\n timezone?: string\n}\nexport interface OutreachConnectCompleteResponse {\n ok: true\n mailbox: OutreachMailbox\n /** False when an existing mailbox for the account was reconnected. */\n created: boolean\n /** Pending addresses of the member's that Gmail's send-as list confirmed. */\n confirmedAliases: string[]\n}\n\n/** `POST outreach/mailboxes/settings` — every field but the ids is optional. */\nexport interface OutreachMailboxSettingsRequest {\n orgId: string\n mailboxId: string\n sendAs?: string\n displayName?: string\n dailyCap?: number\n window?: OutreachSendWindow\n timezone?: string\n /**\n * Whether the warm-up ramp applies (AGL-3228). `false` clears it, so an\n * established mailbox sends at its cap from today; `true` starts one now\n * when none is running.\n */\n warmUp?: boolean\n}\n\n/** `POST outreach/mailboxes/status` */\nexport interface OutreachMailboxStatusRequest {\n orgId: string\n mailboxId: string\n paused: boolean\n}\n\nexport interface OutreachMailboxResponse {\n ok: true\n mailbox: OutreachMailbox\n}\n\n/**\n * `POST outreach/mailboxes/test` — `{ mailboxId, to? }`. `to` is the address\n * the test goes to; left off, the account's own (AGL-3228).\n */\nexport interface OutreachMailboxTestResponse {\n ok: true\n /** The address the test went to. */\n sentTo: string\n gmailMessageId: string\n sentAtMs: number\n}\n\n/** `POST outreach/mailboxes/disconnect` */\nexport interface OutreachMailboxDisconnectResponse {\n ok: true\n /**\n * What became of the grant at Google: `revoked`, `already-invalid`,\n * `kept-for-other-mailbox` when another mailbox still uses the account,\n * or `failed` when Google could not be told — the stored grant is deleted\n * either way.\n */\n revocation: 'revoked' | 'already-invalid' | 'kept-for-other-mailbox' | 'failed'\n}\n\n/**\n * `POST outreach/mailboxes/readiness` — `{ mailboxId, fresh? }`. How\n * receivers judge mail from the address the mailbox sends as (AGL-3328):\n * `readiness` is null for a consumer Gmail address, whose records are\n * Google's own and have nothing for its owner to publish.\n */\nexport interface OutreachMailboxReadinessResponse {\n ok: true\n /** The address whose domain was read. */\n sendAs: string\n readiness: SenderReadiness | null\n}\n\n/**\n * The fragment key a connect returns through (AGL-2978).\n *\n * Google redirects to the callback route with the authorization code in the\n * query; the callback moves it into the FRAGMENT of the Mailboxes page, which\n * no server receives — not in a request line, not in a `Referer` — and the\n * page takes it out of the address bar before it finishes the connect with\n * the member's own session. The cross-domain session handoff uses the\n * fragment for the same reason.\n */\nexport const OUTREACH_CONNECT_FRAGMENT_KEY = 'outreachConnect'\n\n/** Why a connect came back without a code. */\nexport type OutreachConnectReturnError = 'access_denied' | 'expired' | 'google_error'\n\nexport type OutreachConnectReturn =\n | { kind: 'code'; code: string; state: string }\n | { kind: 'error'; reason: OutreachConnectReturnError }\n\n/** The fragment for a connect's return, without the leading `#`. */\nexport function buildConnectReturnFragment(value: OutreachConnectReturn): string {\n const params = new URLSearchParams()\n if (value.kind === 'code') {\n params.set(OUTREACH_CONNECT_FRAGMENT_KEY, 'code')\n params.set('code', value.code)\n params.set('state', value.state)\n } else {\n params.set(OUTREACH_CONNECT_FRAGMENT_KEY, 'error')\n params.set('reason', value.reason)\n }\n return params.toString()\n}\n\nconst RETURN_ERRORS: readonly OutreachConnectReturnError[] = ['access_denied', 'expired', 'google_error']\n\n/** A connect's return read off a location hash, or `null` when it carries none. */\nexport function parseConnectReturnFragment(hash: string | null | undefined): OutreachConnectReturn | null {\n const params = new URLSearchParams(String(hash ?? '').replace(/^#/, ''))\n const kind = params.get(OUTREACH_CONNECT_FRAGMENT_KEY)\n if (kind === 'code') {\n const code = params.get('code') ?? ''\n const state = params.get('state') ?? ''\n return code && state ? { kind: 'code', code, state } : null\n }\n if (kind === 'error') {\n const reason = params.get('reason') as OutreachConnectReturnError | null\n return { kind: 'error', reason: reason && RETURN_ERRORS.includes(reason) ? reason : 'google_error' }\n }\n return null\n}\n"],"names":["OUTREACH_CONNECT_FRAGMENT_KEY","buildConnectReturnFragment","value","params","URLSearchParams","kind","set","code","state","reason","toString","RETURN_ERRORS","parseConnectReturnFragment","hash","String","replace","get","includes"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAmKD;;;;;;;;;CASC,GACD,OAAO,MAAMA,gCAAgC,kBAAiB;AAS9D,kEAAkE,GAClE,OAAO,SAASC,2BAA2BC,KAA4B;IACrE,MAAMC,SAAS,IAAIC;IACnB,IAAIF,MAAMG,IAAI,KAAK,QAAQ;QACzBF,OAAOG,GAAG,CAACN,+BAA+B;QAC1CG,OAAOG,GAAG,CAAC,QAAQJ,MAAMK,IAAI;QAC7BJ,OAAOG,GAAG,CAAC,SAASJ,MAAMM,KAAK;IACjC,OAAO;QACLL,OAAOG,GAAG,CAACN,+BAA+B;QAC1CG,OAAOG,GAAG,CAAC,UAAUJ,MAAMO,MAAM;IACnC;IACA,OAAON,OAAOO,QAAQ;AACxB;AAEA,MAAMC,gBAAuD;IAAC;IAAiB;IAAW;CAAe;AAEzG,iFAAiF,GACjF,OAAO,SAASC,2BAA2BC,IAA+B;IACxE,MAAMV,SAAS,IAAIC,gBAAgBU,OAAOD,eAAAA,OAAQ,IAAIE,OAAO,CAAC,MAAM;IACpE,MAAMV,OAAOF,OAAOa,GAAG,CAAChB;IACxB,IAAIK,SAAS,QAAQ;YACNF,aACCA;QADd,MAAMI,QAAOJ,cAAAA,OAAOa,GAAG,CAAC,mBAAXb,cAAsB;QACnC,MAAMK,SAAQL,eAAAA,OAAOa,GAAG,CAAC,oBAAXb,eAAuB;QACrC,OAAOI,QAAQC,QAAQ;YAAEH,MAAM;YAAQE;YAAMC;QAAM,IAAI;IACzD;IACA,IAAIH,SAAS,SAAS;QACpB,MAAMI,SAASN,OAAOa,GAAG,CAAC;QAC1B,OAAO;YAAEX,MAAM;YAASI,QAAQA,UAAUE,cAAcM,QAAQ,CAACR,UAAUA,SAAS;QAAe;IACrG;IACA,OAAO;AACT"}
@@ -15,6 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import type { PluginWebApiHandler } from '@aglyn/aglyn/server';
18
+ import { type SenderReadiness, type SenderReadinessExpectation } from '@aglyn/shared-util-email';
18
19
  import type { TransportDeps } from '../transport/http';
19
20
  import { type OutreachGateDeps } from './mailbox-gate';
20
21
  import { type OutreachGoogleConfigResult } from './outreach-config';
@@ -98,6 +99,10 @@ export interface OutreachMailboxRouteDeps {
98
99
  ok: boolean;
99
100
  confirmed?: string[];
100
101
  }>;
102
+ /** SPF, DKIM and DMARC for one sender, looked up and briefly remembered. */
103
+ readSenderReadiness(expectation: SenderReadinessExpectation, options: {
104
+ fresh?: boolean;
105
+ }): Promise<SenderReadiness>;
101
106
  }
102
107
  export interface OutreachMailboxRoutes {
103
108
  availability: PluginWebApiHandler;
@@ -108,6 +113,7 @@ export interface OutreachMailboxRoutes {
108
113
  status: PluginWebApiHandler;
109
114
  test: PluginWebApiHandler;
110
115
  disconnect: PluginWebApiHandler;
116
+ readiness: PluginWebApiHandler;
111
117
  }
112
118
  /** Test sends one mailbox may make in an hour. */
113
119
  export declare const OUTREACH_TEST_SENDS_PER_HOUR = 5;
@@ -15,6 +15,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
17
  */ import { normalizeContactEmail } from "@aglyn/aglyn/app-utils/contacts";
18
+ import { googleMailboxSenderExpectation } from "@aglyn/shared-util-email";
18
19
  import { createGmailClient } from "../transport/gmail-client.js";
19
20
  import { GmailTransportError } from "../transport/gmail-errors.js";
20
21
  import { buildGoogleAuthorizationUrl, exchangeGoogleAuthorizationCode, grantedScopesInclude, OUTREACH_REQUIRED_GRANTED_SCOPES, pkceChallenge, readGoogleIdToken } from "../transport/google-oauth.js";
@@ -630,6 +631,31 @@ export function createOutreachMailboxRoutes(deps) {
630
631
  revocation
631
632
  });
632
633
  };
634
+ /**
635
+ * How receivers judge mail from the address a mailbox sends as. Any member
636
+ * who can see the mailbox can read it: it is a reading of public DNS about
637
+ * the organization's own domain, and changes nothing.
638
+ */ const readiness = async (request)=>{
639
+ if (request.method !== 'POST') return methodNotAllowed('POST');
640
+ const body = await readBody(request);
641
+ const gate = await outreachMemberGate(request, body['orgId'], deps.gate);
642
+ if (gate instanceof Response) return gate;
643
+ const mailboxId = typeof body['mailboxId'] === 'string' ? body['mailboxId'] : '';
644
+ if (!MAILBOX_ID.test(mailboxId)) return refusal(400, 'invalid-request', 'Name the mailbox.');
645
+ const snapshot = await mailboxRef(deps.firestore(), gate.orgId, mailboxId).get();
646
+ const mailbox = readMailbox(mailboxId, snapshot.exists ? snapshot.data() : undefined);
647
+ if (!mailbox) return refusal(404, 'mailbox-not-found', 'That mailbox is not connected to this organization.');
648
+ const sendAs = mailbox.sendAs || mailbox.email;
649
+ const expectation = googleMailboxSenderExpectation(sendAs);
650
+ const read = expectation ? await deps.readSenderReadiness(expectation, {
651
+ fresh: body['fresh'] === true
652
+ }).catch(()=>null) : null;
653
+ return ok({
654
+ ok: true,
655
+ sendAs,
656
+ readiness: read
657
+ });
658
+ };
633
659
  return {
634
660
  availability,
635
661
  connect,
@@ -638,7 +664,8 @@ export function createOutreachMailboxRoutes(deps) {
638
664
  settings,
639
665
  status,
640
666
  test,
641
- disconnect
667
+ disconnect,
668
+ readiness
642
669
  };
643
670
  }
644
671
  function googleUnavailable(error) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/mailboxes/mailbox-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\nimport { normalizeContactEmail } from '@aglyn/aglyn/app-utils/contacts'\nimport type { PluginWebApiHandler } from '@aglyn/aglyn/server'\nimport type {\n OutreachMailbox,\n OutreachMailboxStatus,\n} from '../model/outreach.types'\nimport { createGmailClient } from '../transport/gmail-client'\nimport { GmailTransportError } from '../transport/gmail-errors'\nimport {\n buildGoogleAuthorizationUrl,\n exchangeGoogleAuthorizationCode,\n grantedScopesInclude,\n OUTREACH_REQUIRED_GRANTED_SCOPES,\n pkceChallenge,\n readGoogleIdToken,\n type GoogleTokenResponse,\n} from '../transport/google-oauth'\nimport type { TransportDeps } from '../transport/http'\nimport { Rfc5322MessageError } from '../transport/rfc5322'\nimport { sendOutreachMessage } from '../transport/send-message'\nimport {\n buildConnectReturnFragment,\n type OutreachApiRefusalReason,\n type OutreachConnectReturn,\n type OutreachMailboxAvailability,\n type OutreachMailboxAvailabilityGate,\n type OutreachMailboxDisconnectResponse,\n} from './mailbox-api'\nimport {\n mailboxCredentialsRef,\n mailboxRef,\n outreachMailboxId,\n readMailboxCredentials,\n sealMailboxRefreshToken,\n type OutreachGoogleMailboxCredentials,\n} from './mailbox-credentials'\nimport { revokeMailboxGrant } from './mailbox-revoke'\nimport {\n outreachMemberGate,\n refusal,\n type OutreachGateContext,\n type OutreachGateDeps,\n} from './mailbox-gate'\nimport {\n defaultSendAs,\n isValidTimezone,\n OUTREACH_DEFAULT_DAILY_CAP,\n OUTREACH_DEFAULT_WINDOW,\n OUTREACH_MAX_MAILBOXES_PER_MEMBER,\n validateDailyCap,\n validateDisplayName,\n validateSendWindow,\n verifiedSendAsAddresses,\n} from './mailbox-settings'\nimport {\n markOutreachMailboxReconnectRequired,\n openOutreachMailboxClient,\n} from './mailbox-transport'\nimport {\n consumeOutreachOAuthState,\n mintOutreachOAuthState,\n outreachOidcNonce,\n outreachPkceVerifier,\n readOutreachOAuthState,\n recordOutreachOAuthState,\n} from './oauth-state'\nimport {\n OUTREACH_NOT_CONFIGURED_MESSAGE,\n type OutreachGoogleConfigResult,\n} from './outreach-config'\n\n/**\n * THE MAILBOX ROUTES (AGL-2978): connect a rep's Google mailbox, keep its\n * settings, pause it, test it, and disconnect it.\n *\n * ## The connect, end to end\n *\n * 1. `connect` (the member's session) mints a signed, single-use state bound\n * to the member and the organization, records its pending entry, and\n * answers Google's consent address — offline access, the consent prompt,\n * a PKCE challenge and an OpenID nonce, both derived from the state.\n * 2. Google redirects the browser to `oauth/callback`. That request carries\n * no bearer token, so it DOES nothing: it verifies the state's signature,\n * finds the organization's console path, and redirects to the Mailboxes\n * page with the code in the URL FRAGMENT, where no server and no `Referer`\n * sees it. Its release gate was asked about the organization and the\n * member the state names (the route's declared subject).\n * 3. The page takes the code out of the address bar and posts it to\n * `connect/complete` with the member's own session. That is where the\n * state is consumed, where it is refused if it was minted for anyone else,\n * and where the code is exchanged. So a consent screen someone else was\n * tricked into completing cannot connect THEIR mailbox to the member who\n * started it: the code arrives in the wrong browser, whose session is not\n * the state's member.\n *\n * ## Refusals, in order, on `connect/complete`\n *\n * A state that is invalid, minted for another member or organization,\n * expired, already used or superseded; a code Google refuses; a grant with no\n * refresh token; a grant missing `gmail.send` or `gmail.readonly` (the consent\n * screen lets a person untick them); an ID token that is not about this\n * exchange or whose address Google has not verified; a Gmail profile whose\n * address is not the ID token's; and a member at their mailbox limit.\n *\n * A grant refused after the code exchange is dropped, not revoked. Google's\n * revocation ends the whole grant this client holds for the account, and the\n * same account may already be connected — by this member in another\n * organization, or by a teammate — so revoking a token this route merely\n * declines to keep could disconnect a mailbox that is working.\n *\n * ## Who may do what\n *\n * Every route but the callback climbs `outreachMemberGate`. The member who\n * connected a mailbox manages it; an organization owner or admin may also\n * change its settings, pause it and disconnect it — a departing rep's mailbox\n * has to be stoppable by somebody. Only its own member can send a test from\n * it, since the test lands in their inbox.\n */\n\nexport interface OutreachMailboxRouteDeps {\n firestore(): FirebaseFirestore.Firestore\n gate: OutreachGateDeps\n readConfig(): OutreachGoogleConfigResult\n /** Whether the signing secret the OAuth state needs is configured. */\n stateSigningConfigured(): boolean\n redirectUri(requestUrl: string): string | null\n now(): number\n /** The network and retry policy every Google call uses. */\n transport: TransportDeps\n consumeRateLimit(key: string, options: { limit: number; windowMs: number }): Promise<{ allowed: boolean }>\n logOrgActivity(\n orgId: string,\n actor: { uid: string; email?: string | null },\n action: string,\n target: { type: typeof OUTREACH_MAILBOX_ACTIVITY_TARGET; id: string; name: string },\n ): Promise<void>\n confirmAliasesByProvider(\n firestore: FirebaseFirestore.Firestore,\n input: { orgId: string; uid: string; addresses: readonly string[]; nowMs: number },\n ): Promise<{ ok: boolean; confirmed?: string[] }>\n}\n\nexport interface OutreachMailboxRoutes {\n availability: PluginWebApiHandler\n connect: PluginWebApiHandler\n oauthCallback: PluginWebApiHandler\n connectComplete: PluginWebApiHandler\n settings: PluginWebApiHandler\n status: PluginWebApiHandler\n test: PluginWebApiHandler\n disconnect: PluginWebApiHandler\n}\n\n/** Test sends one mailbox may make in an hour. */\nexport const OUTREACH_TEST_SENDS_PER_HOUR = 5\n\n/**\n * The org activity target a mailbox's rows are filed under: Outreach's own\n * namespaced resource, which the feed reads as \"Mailbox\".\n */\nexport const OUTREACH_MAILBOX_ACTIVITY_TARGET = 'outreach:mailbox'\n\nconst MAILBOX_ID = /^[A-Za-z0-9_-]{1,128}$/\nconst MAX_CODE_CHARS = 2048\n\nconst noStore = { 'Cache-Control': 'no-store' }\n\nconst ok = (body: unknown) => Response.json(body, { status: 200, headers: noStore })\n\nfunction methodNotAllowed(allow: string): Response {\n return Response.json(\n { error: 'Method not allowed', reason: 'method-not-allowed' satisfies OutreachApiRefusalReason },\n { status: 405, headers: { ...noStore, Allow: allow } },\n )\n}\n\nasync function readBody(request: Request): Promise<Record<string, unknown>> {\n const body = await request.json().catch(() => null)\n return body && typeof body === 'object' && !Array.isArray(body) ? (body as Record<string, unknown>) : {}\n}\n\nfunction notConfigured(): Response {\n return refusal(503, 'not-configured', OUTREACH_NOT_CONFIGURED_MESSAGE)\n}\n\n/** A mailbox document read defensively into the model shape. */\nfunction readMailbox(id: string, data: Record<string, unknown> | undefined): OutreachMailbox | null {\n if (!data) return null\n return { ...(data as unknown as OutreachMailbox), id }\n}\n\nconst activityName = (mailbox: Pick<OutreachMailbox, 'email'>) => mailbox.email\n\nexport function createOutreachMailboxRoutes(deps: OutreachMailboxRouteDeps): OutreachMailboxRoutes {\n /** The gate, then the mailbox the body names, then who may touch it. */\n async function loadManagedMailbox(\n request: Request,\n body: Record<string, unknown>,\n who: 'member-or-admin' | 'member',\n ): Promise<Response | { gate: OutreachGateContext; mailbox: OutreachMailbox }> {\n const gate = await outreachMemberGate(request, body['orgId'], deps.gate)\n if (gate instanceof Response) return gate\n const mailboxId = typeof body['mailboxId'] === 'string' ? body['mailboxId'] : ''\n if (!MAILBOX_ID.test(mailboxId)) return refusal(400, 'invalid-request', 'Name the mailbox.')\n const snapshot = await mailboxRef(deps.firestore(), gate.orgId, mailboxId).get()\n const mailbox = readMailbox(mailboxId, snapshot.exists ? snapshot.data() : undefined)\n if (!mailbox) return refusal(404, 'mailbox-not-found', 'That mailbox is not connected to this organization.')\n const own = mailbox.connectedByUid === gate.uid\n if (!own && !(who === 'member-or-admin' && gate.isOrgAdmin)) {\n return refusal(\n 403,\n 'not-your-mailbox',\n who === 'member'\n ? 'Only the member who connected this mailbox can do that.'\n : 'Only the member who connected this mailbox, or an organization owner or admin, can change it.',\n )\n }\n return { gate, mailbox }\n }\n\n const availability: PluginWebApiHandler = async (request) => {\n if (request.method !== 'GET') return methodNotAllowed('GET')\n const gate = await outreachMemberGate(request, new URL(request.url).searchParams.get('orgId'), deps.gate)\n if (gate instanceof Response) return gate\n // Every gate is named, not the first: an operator fixing a deployment\n // should see the whole list once rather than one variable per redeploy.\n const missing: OutreachMailboxAvailabilityGate[] = []\n const config = deps.readConfig()\n if (config.configured === false) missing.push({ gate: 'google', missing: config.missing })\n if (!deps.stateSigningConfigured()) missing.push({ gate: 'state' })\n if (!deps.redirectUri(request.url)) missing.push({ gate: 'redirect' })\n return ok({\n configured: missing.length === 0,\n canManageAll: gate.isOrgAdmin,\n ...(missing.length ? { missing } : {}),\n } satisfies OutreachMailboxAvailability)\n }\n\n const connect: PluginWebApiHandler = async (request) => {\n if (request.method !== 'POST') return methodNotAllowed('POST')\n const body = await readBody(request)\n const gate = await outreachMemberGate(request, body['orgId'], deps.gate)\n if (gate instanceof Response) return gate\n const config = deps.readConfig()\n const redirectUri = deps.redirectUri(request.url)\n if (!config.configured || !redirectUri || !deps.stateSigningConfigured()) return notConfigured()\n\n const nowMs = deps.now()\n const { state, claims } = mintOutreachOAuthState({ orgId: gate.orgId, uid: gate.uid, nowMs })\n await recordOutreachOAuthState(deps.firestore(), { claims, redirectUri, nowMs })\n const url = buildGoogleAuthorizationUrl({\n clientId: config.config.clientId,\n redirectUri,\n state,\n codeChallenge: pkceChallenge(outreachPkceVerifier(claims.nonce)),\n nonce: outreachOidcNonce(claims.nonce),\n loginHint: gate.email,\n })\n return ok({ url })\n }\n\n const oauthCallback: PluginWebApiHandler = async (request) => {\n if (request.method !== 'GET') return methodNotAllowed('GET')\n const params = new URL(request.url).searchParams\n const read = readOutreachOAuthState(params.get('state'), deps.now())\n const plain = (status: number, text: string) =>\n new Response(text, {\n status,\n headers: { ...noStore, 'Content-Type': 'text/plain; charset=utf-8', 'Referrer-Policy': 'no-referrer' },\n })\n if (read.ok === false && read.refusal === 'state-invalid') {\n return plain(400, 'This connection link is not valid. Return to the console and connect the mailbox again.')\n }\n const claims = read.ok ? read.claims : (read as { claims: { orgId: string } }).claims\n const org = await deps.gate.readOrg(claims.orgId)\n const slug = typeof org?.['slug'] === 'string' ? org['slug'] : ''\n if (!slug) return plain(404, 'That organization could not be found.')\n\n let fragment: OutreachConnectReturn\n const googleError = params.get('error')\n const code = params.get('code') ?? ''\n if (googleError) {\n fragment = { kind: 'error', reason: googleError === 'access_denied' ? 'access_denied' : 'google_error' }\n } else if (!read.ok) {\n fragment = { kind: 'error', reason: 'expired' }\n } else if (!code || code.length > MAX_CODE_CHARS) {\n fragment = { kind: 'error', reason: 'google_error' }\n } else {\n fragment = { kind: 'code', code, state: params.get('state') ?? '' }\n }\n // A RELATIVE location on the callback's own origin: the path is built from\n // the organization's slug, never from anything the request supplied.\n return new Response(null, {\n status: 303,\n headers: {\n ...noStore,\n 'Referrer-Policy': 'no-referrer',\n Location: `/${encodeURIComponent(slug)}/outreach/mailboxes#${buildConnectReturnFragment(fragment)}`,\n },\n })\n }\n\n const connectComplete: PluginWebApiHandler = async (request) => {\n if (request.method !== 'POST') return methodNotAllowed('POST')\n const body = await readBody(request)\n const gate = await outreachMemberGate(request, body['orgId'], deps.gate)\n if (gate instanceof Response) return gate\n const config = deps.readConfig()\n if (!config.configured || !deps.stateSigningConfigured()) return notConfigured()\n const code = typeof body['code'] === 'string' ? body['code'] : ''\n if (!code || code.length > MAX_CODE_CHARS) return refusal(400, 'invalid-request', 'The connection did not return a code.')\n\n const firestore = deps.firestore()\n const nowMs = deps.now()\n const read = readOutreachOAuthState(body['state'], nowMs)\n if (read.ok === false && read.refusal === 'state-invalid') {\n return refusal(400, 'state-invalid', 'This connection could not be verified. Connect the mailbox again.')\n }\n const claims = read.ok ? read.claims : (read as { claims: typeof read.claims }).claims\n if (claims.uid !== gate.uid) {\n return refusal(\n 403,\n 'state-user-mismatch',\n 'This connection was started by another member. Sign in as that member, or connect the mailbox again.',\n )\n }\n if (claims.orgId !== gate.orgId) {\n return refusal(400, 'state-org-mismatch', 'This connection was started in another organization.')\n }\n if (!read.ok) return refusal(410, 'state-expired', 'This connection took too long. Connect the mailbox again.')\n\n const consumed = await consumeOutreachOAuthState(firestore, { claims, nowMs })\n if (consumed.ok === false) {\n if (consumed.refusal === 'state-expired') {\n return refusal(410, 'state-expired', 'This connection took too long. Connect the mailbox again.')\n }\n return refusal(\n 409,\n consumed.refusal,\n consumed.refusal === 'state-superseded'\n ? 'A newer connection was started. Finish that one, or connect the mailbox again.'\n : 'This connection was already used. Connect the mailbox again.',\n )\n }\n\n let grant: GoogleTokenResponse\n try {\n grant = await exchangeGoogleAuthorizationCode(\n {\n clientId: config.config.clientId,\n clientSecret: config.config.clientSecret,\n code,\n redirectUri: consumed.redirectUri,\n codeVerifier: outreachPkceVerifier(claims.nonce),\n },\n deps.transport,\n )\n } catch (error) {\n if (error instanceof GmailTransportError && error.code === 'invalid_grant') {\n return refusal(400, 'code-rejected', \"Google's sign-in expired or was already used. Connect the mailbox again.\")\n }\n if (error instanceof GmailTransportError && error.code === 'client_misconfigured') {\n return refusal(503, 'not-configured', \"Google refused this deployment's OAuth client.\")\n }\n return googleUnavailable(error)\n }\n if (!grant.refreshToken) {\n return refusal(\n 422,\n 'refresh-token-missing',\n 'Google did not grant offline access, so the mailbox could not send later. Connect it again.',\n )\n }\n if (!grantedScopesInclude(grant.scope, OUTREACH_REQUIRED_GRANTED_SCOPES)) {\n return refusal(\n 422,\n 'scopes-missing',\n 'Sequences needs permission to send and to read your mail. Connect again and allow both.',\n )\n }\n const identity = readGoogleIdToken(grant.idToken, {\n clientId: config.config.clientId,\n nonce: outreachOidcNonce(claims.nonce),\n nowMs,\n })\n if (identity.ok === false) {\n return refusal(\n 422,\n 'identity-unverified',\n identity.refusal === 'email-unverified'\n ? \"Google has not verified this account's email address.\"\n : \"Google's answer could not be verified. Connect the mailbox again.\",\n )\n }\n\n const client = createGmailClient({\n ...deps.transport,\n clientId: config.config.clientId,\n clientSecret: config.config.clientSecret,\n refreshToken: grant.refreshToken,\n })\n let profileEmail: string\n let sendAsOptions: ReturnType<typeof verifiedSendAsAddresses>\n try {\n const [profile, sendAs] = await Promise.all([client.getProfile(), client.listSendAs()])\n profileEmail = profile.emailAddress\n sendAsOptions = verifiedSendAsAddresses(sendAs)\n } catch (error) {\n if (error instanceof GmailTransportError && error.reconnectRequired) {\n return refusal(\n 422,\n 'scopes-missing',\n 'Sequences needs permission to send and to read your mail. Connect again and allow both.',\n )\n }\n return googleUnavailable(error)\n }\n if (profileEmail !== identity.identity.email) {\n return refusal(422, 'account-mismatch', 'The Gmail account and the Google sign-in did not match. Connect again.')\n }\n if (!sendAsOptions.some((option) => option.email === profileEmail)) {\n sendAsOptions = [\n { email: profileEmail, displayName: '', isPrimary: true, isDefault: !sendAsOptions.some((o) => o.isDefault) },\n ...sendAsOptions,\n ]\n }\n\n const mailboxId = outreachMailboxId(gate.orgId, gate.uid, identity.identity.sub)\n const mailboxDoc = mailboxRef(firestore, gate.orgId, mailboxId)\n const credentialDoc = mailboxCredentialsRef(firestore, mailboxId)\n const [existingMailbox, existingCredential] = await Promise.all([mailboxDoc.get(), credentialDoc.get()])\n const current = readMailbox(mailboxId, existingMailbox.exists ? existingMailbox.data() : undefined)\n if (!current) {\n const mine = await firestore\n .collection('orgs')\n .doc(gate.orgId)\n .collection('outreachMailboxes')\n .where('connectedByUid', '==', gate.uid)\n .limit(OUTREACH_MAX_MAILBOXES_PER_MEMBER)\n .get()\n if (mine.size >= OUTREACH_MAX_MAILBOXES_PER_MEMBER) {\n return refusal(\n 409,\n 'mailbox-limit',\n `You can connect up to ${OUTREACH_MAX_MAILBOXES_PER_MEMBER} mailboxes. Disconnect one before connecting another.`,\n )\n }\n }\n\n const defaultOption = sendAsOptions.find((option) => option.email === defaultSendAs(sendAsOptions, profileEmail))\n let mailbox: OutreachMailbox\n /** On a reconnect, only the fields a connect decides — see the write below. */\n let reconnected: Partial<OutreachMailbox> | null = null\n if (current) {\n const keepSendAs = sendAsOptions.some((option) => option.email === current.sendAs)\n reconnected = {\n provider: 'google',\n email: profileEmail,\n sendAs: keepSendAs ? current.sendAs : (defaultOption?.email ?? profileEmail),\n sendAsOptions,\n // A reconnect restores a mailbox Google had cut off, and leaves a\n // mailbox its member paused paused.\n status: (current.status === 'paused' ? 'paused' : 'connected') satisfies OutreachMailboxStatus,\n connectedAtMs: nowMs,\n updatedAtMs: nowMs,\n }\n mailbox = { ...current, ...reconnected }\n } else {\n const timezone = isValidTimezone(body['timezone']) ? String(body['timezone']) : 'UTC'\n const displayName = validateDisplayName(defaultOption?.displayName || gate.name || '')\n mailbox = {\n id: mailboxId,\n provider: 'google',\n email: profileEmail,\n sendAs: defaultOption?.email ?? profileEmail,\n sendAsOptions,\n displayName: typeof displayName === 'string' ? displayName : '',\n status: 'connected',\n dailyCap: OUTREACH_DEFAULT_DAILY_CAP,\n window: { ...OUTREACH_DEFAULT_WINDOW, days: [...OUTREACH_DEFAULT_WINDOW.days] },\n timezone,\n rampStartedAtMs: nowMs,\n health: {\n sentToday: 0,\n sentOnDay: null,\n bounces: 0,\n replies: 0,\n lastSentAtMs: null,\n lastErrorAtMs: null,\n lastErrorCode: null,\n daily: {},\n },\n connectedByUid: gate.uid,\n connectedAtMs: nowMs,\n createdAtMs: nowMs,\n updatedAtMs: nowMs,\n }\n }\n const credential: OutreachGoogleMailboxCredentials = {\n id: mailboxId,\n orgId: gate.orgId,\n mailboxId,\n provider: 'google',\n connectedByUid: gate.uid,\n providerAccountId: identity.identity.sub,\n email: profileEmail,\n scopes: grant.scope.split(/\\s+/).filter(Boolean),\n ...sealMailboxRefreshToken(grant.refreshToken, mailboxId, config.config.keyring),\n createdAtMs: Number(existingCredential.exists ? existingCredential.get('createdAtMs') : 0) || nowMs,\n updatedAtMs: nowMs,\n }\n const batch = firestore.batch()\n // A reconnect MERGES only what a connect decides, so the settings a member\n // chose and the health counters the sending runtime increments are never\n // written back from a read that may already be stale.\n if (reconnected) batch.set(mailboxDoc, reconnected, { merge: true })\n else batch.set(mailboxDoc, mailbox)\n batch.set(credentialDoc, credential)\n await batch.commit()\n\n const aliases: { ok: boolean; confirmed?: string[] } = await deps\n .confirmAliasesByProvider(firestore, {\n orgId: gate.orgId,\n uid: gate.uid,\n addresses: sendAsOptions.map((option) => option.email),\n nowMs,\n })\n .catch((error: unknown) => {\n console.error('[outreach] confirming member aliases from send-as failed', error)\n return { ok: false }\n })\n await deps.logOrgActivity(\n gate.orgId,\n { uid: gate.uid, email: gate.email },\n current ? 'Reconnected a mailbox in Sequences' : 'Connected a mailbox in Sequences',\n { type: OUTREACH_MAILBOX_ACTIVITY_TARGET, id: mailboxId, name: activityName(mailbox) },\n )\n return ok({\n ok: true,\n mailbox,\n created: !current,\n confirmedAliases: aliases.ok ? (aliases.confirmed ?? []) : [],\n })\n }\n\n const settings: PluginWebApiHandler = async (request) => {\n if (request.method !== 'POST') return methodNotAllowed('POST')\n const body = await readBody(request)\n const loaded = await loadManagedMailbox(request, body, 'member-or-admin')\n if (loaded instanceof Response) return loaded\n const { gate, mailbox } = loaded\n const update: Partial<OutreachMailbox> = {}\n const invalid = (message: string) => refusal(400, 'invalid-settings', message)\n\n if (body['sendAs'] !== undefined) {\n const sendAs = typeof body['sendAs'] === 'string' ? body['sendAs'].trim().toLowerCase() : ''\n if (!(mailbox.sendAsOptions ?? []).some((option) => option.email === sendAs)) {\n return invalid('Choose one of the addresses Gmail has verified for this account.')\n }\n update.sendAs = sendAs\n }\n if (body['displayName'] !== undefined) {\n const name = validateDisplayName(body['displayName'])\n if (typeof name !== 'string') return invalid(name.message)\n update.displayName = name\n }\n if (body['dailyCap'] !== undefined) {\n const cap = validateDailyCap(body['dailyCap'])\n if (typeof cap !== 'number') return invalid(cap.message)\n update.dailyCap = cap\n }\n if (body['window'] !== undefined) {\n const window = validateSendWindow(body['window'])\n if (!('days' in window)) return invalid(window.message)\n update.window = window\n }\n if (body['timezone'] !== undefined) {\n if (!isValidTimezone(body['timezone'])) return invalid('Choose a timezone from the list.')\n update.timezone = body['timezone']\n }\n if (body['warmUp'] !== undefined) {\n /*\n * The ramp guards a NEW sender's reputation; a mailbox that has sent\n * mail for years has one already, and ramping it to ten a day guards\n * nothing (AGL-3228). Its member decides. Turning the ramp back on\n * starts a fresh one from today, which is what \"warm up\" means.\n */\n if (typeof body['warmUp'] !== 'boolean') return invalid('Say whether to warm the mailbox up gradually.')\n const running = typeof mailbox.rampStartedAtMs === 'number'\n if (body['warmUp'] && !running) update.rampStartedAtMs = deps.now()\n if (!body['warmUp'] && running) update.rampStartedAtMs = null\n }\n if (!Object.keys(update).length) return ok({ ok: true, mailbox })\n const nowMs = deps.now()\n await mailboxRef(deps.firestore(), gate.orgId, mailbox.id).update({ ...update, updatedAtMs: nowMs })\n return ok({ ok: true, mailbox: { ...mailbox, ...update, updatedAtMs: nowMs } })\n }\n\n const status: PluginWebApiHandler = async (request) => {\n if (request.method !== 'POST') return methodNotAllowed('POST')\n const body = await readBody(request)\n if (typeof body['paused'] !== 'boolean') return refusal(400, 'invalid-request', 'Say whether to pause the mailbox.')\n const loaded = await loadManagedMailbox(request, body, 'member-or-admin')\n if (loaded instanceof Response) return loaded\n const { gate, mailbox } = loaded\n if (mailbox.status === 'reconnect_required' || mailbox.status === 'disconnected') {\n return refusal(409, 'reconnect-required', 'Reconnect this mailbox before pausing or resuming it.')\n }\n const next: OutreachMailboxStatus = body['paused'] ? 'paused' : 'connected'\n if (mailbox.status === next) return ok({ ok: true, mailbox })\n const nowMs = deps.now()\n // A member's pause or resume answers an automatic pause (AGL-2981): its\n // reason goes, and the next bounce is judged as new evidence.\n await mailboxRef(deps.firestore(), gate.orgId, mailbox.id).update({\n status: next,\n autoPause: null,\n updatedAtMs: nowMs,\n })\n await deps.logOrgActivity(\n gate.orgId,\n { uid: gate.uid, email: gate.email },\n next === 'paused' ? 'Paused a mailbox in Sequences' : 'Resumed a mailbox in Sequences',\n { type: OUTREACH_MAILBOX_ACTIVITY_TARGET, id: mailbox.id, name: activityName(mailbox) },\n )\n return ok({ ok: true, mailbox: { ...mailbox, status: next, autoPause: null, updatedAtMs: nowMs } })\n }\n\n const test: PluginWebApiHandler = async (request) => {\n if (request.method !== 'POST') return methodNotAllowed('POST')\n const body = await readBody(request)\n const loaded = await loadManagedMailbox(request, body, 'member')\n if (loaded instanceof Response) return loaded\n const { gate, mailbox } = loaded\n if (mailbox.status === 'reconnect_required' || mailbox.status === 'disconnected') {\n return refusal(409, 'reconnect-required', 'Reconnect this mailbox before sending from it.')\n }\n if (!deps.readConfig().configured) return notConfigured()\n const limited = await deps.consumeRateLimit(`outreach-mailbox-test:${mailbox.id}`, {\n limit: OUTREACH_TEST_SENDS_PER_HOUR,\n windowMs: 60 * 60 * 1000,\n })\n if (!limited.allowed) {\n return refusal(429, 'rate-limited', `A mailbox can send ${OUTREACH_TEST_SENDS_PER_HOUR} tests an hour. Try again later.`)\n }\n /*\n * To the member's own address unless they name another (AGL-3228). A\n * test to oneself never leaves Google, so it arrives with no\n * Authentication-Results at all and cannot show whether the send-as\n * domain's DKIM and DMARC hold; an outside mailbox the member can read\n * is the only place that can be seen. The rate limit above is the\n * whole guard on who may be written to — it is the member's own\n * account sending, as it could from its own compose window.\n */\n const named = body['to']\n const to =\n named === undefined || named === null || String(named).trim() === ''\n ? mailbox.email\n : normalizeContactEmail(String(named))\n if (!to) {\n return refusal(400, 'invalid-request', `\"${String(named).trim()}\" isn't a valid email address.`)\n }\n const firestore = deps.firestore()\n const nowMs = deps.now()\n const opened = await openOutreachMailboxClient(\n firestore,\n { mailboxId: mailbox.id },\n { ...deps.transport, readConfig: deps.readConfig, now: deps.now },\n )\n if (opened.ok === false) {\n if (opened.reason === 'not-configured') return notConfigured()\n await markOutreachMailboxReconnectRequired(firestore, {\n orgId: gate.orgId,\n mailboxId: mailbox.id,\n errorCode: opened.reason,\n nowMs,\n })\n return refusal(409, 'reconnect-required', 'This mailbox needs to be connected again before it can send.')\n }\n try {\n const sent = await sendOutreachMessage(opened.client, {\n from: { address: mailbox.sendAs, name: mailbox.displayName },\n to,\n subject: 'Sequences test message',\n text: [\n 'This is a test message from Sequences.',\n '',\n `It was sent through the connected mailbox ${mailbox.email} as ${mailbox.sendAs}. ` +\n 'If it reached the inbox, this mailbox can send.',\n ...(to === mailbox.email\n ? []\n : [\n '',\n 'To read whether the sender is authenticated, open this message\\u2019s original source ' +\n 'and look for dkim=pass, spf=pass and dmarc=pass in Authentication-Results.',\n ]),\n ].join('\\n'),\n })\n return ok({ ok: true, sentTo: to, gmailMessageId: sent.gmailMessageId, sentAtMs: nowMs })\n } catch (error) {\n if (error instanceof GmailTransportError && error.reconnectRequired) {\n await markOutreachMailboxReconnectRequired(firestore, {\n orgId: gate.orgId,\n mailboxId: mailbox.id,\n errorCode: error.code,\n nowMs,\n })\n return refusal(409, 'reconnect-required', 'Google refused this mailbox’s access. Connect it again.')\n }\n if (error instanceof Rfc5322MessageError) {\n return refusal(400, 'invalid-settings', `The test could not be written: ${error.message}`)\n }\n return googleUnavailable(error)\n }\n }\n\n const disconnect: PluginWebApiHandler = async (request) => {\n if (request.method !== 'POST') return methodNotAllowed('POST')\n const body = await readBody(request)\n const loaded = await loadManagedMailbox(request, body, 'member-or-admin')\n if (loaded instanceof Response) return loaded\n const { gate, mailbox } = loaded\n const firestore = deps.firestore()\n const credentialDoc = mailboxCredentialsRef(firestore, mailbox.id)\n const snapshot = await credentialDoc.get()\n const credential = readMailboxCredentials(snapshot.exists ? snapshot.data() : null)\n const revocation = credential\n ? await revokeMailboxGrant(firestore, credential, deps)\n : ('already-invalid' as const)\n const batch = firestore.batch()\n batch.delete(credentialDoc)\n batch.delete(mailboxRef(firestore, gate.orgId, mailbox.id))\n await batch.commit()\n await deps.logOrgActivity(\n gate.orgId,\n { uid: gate.uid, email: gate.email },\n 'Disconnected a mailbox in Sequences',\n { type: OUTREACH_MAILBOX_ACTIVITY_TARGET, id: mailbox.id, name: activityName(mailbox) },\n )\n return ok({ ok: true, revocation } satisfies OutreachMailboxDisconnectResponse)\n }\n\n return { availability, connect, oauthCallback, connectComplete, settings, status, test, disconnect }\n}\n\nfunction googleUnavailable(error: unknown): Response {\n if (!(error instanceof GmailTransportError)) throw error\n return refusal(\n 502,\n 'google-unavailable',\n error.retryable ? 'Google did not answer. Try again in a moment.' : `Google refused the request: ${error.message}`,\n )\n}\n"],"names":["normalizeContactEmail","createGmailClient","GmailTransportError","buildGoogleAuthorizationUrl","exchangeGoogleAuthorizationCode","grantedScopesInclude","OUTREACH_REQUIRED_GRANTED_SCOPES","pkceChallenge","readGoogleIdToken","Rfc5322MessageError","sendOutreachMessage","buildConnectReturnFragment","mailboxCredentialsRef","mailboxRef","outreachMailboxId","readMailboxCredentials","sealMailboxRefreshToken","revokeMailboxGrant","outreachMemberGate","refusal","defaultSendAs","isValidTimezone","OUTREACH_DEFAULT_DAILY_CAP","OUTREACH_DEFAULT_WINDOW","OUTREACH_MAX_MAILBOXES_PER_MEMBER","validateDailyCap","validateDisplayName","validateSendWindow","verifiedSendAsAddresses","markOutreachMailboxReconnectRequired","openOutreachMailboxClient","consumeOutreachOAuthState","mintOutreachOAuthState","outreachOidcNonce","outreachPkceVerifier","readOutreachOAuthState","recordOutreachOAuthState","OUTREACH_NOT_CONFIGURED_MESSAGE","OUTREACH_TEST_SENDS_PER_HOUR","OUTREACH_MAILBOX_ACTIVITY_TARGET","MAILBOX_ID","MAX_CODE_CHARS","noStore","ok","body","Response","json","status","headers","methodNotAllowed","allow","error","reason","Allow","readBody","request","catch","Array","isArray","notConfigured","readMailbox","id","data","activityName","mailbox","email","createOutreachMailboxRoutes","deps","loadManagedMailbox","who","gate","mailboxId","test","snapshot","firestore","orgId","get","exists","undefined","own","connectedByUid","uid","isOrgAdmin","availability","method","URL","url","searchParams","missing","config","readConfig","configured","push","stateSigningConfigured","redirectUri","length","canManageAll","connect","nowMs","now","state","claims","clientId","codeChallenge","nonce","loginHint","oauthCallback","params","read","plain","text","org","readOrg","slug","fragment","googleError","code","kind","Location","encodeURIComponent","connectComplete","aliases","consumed","grant","clientSecret","codeVerifier","transport","googleUnavailable","refreshToken","scope","identity","idToken","client","profileEmail","sendAsOptions","profile","sendAs","Promise","all","getProfile","listSendAs","emailAddress","reconnectRequired","some","option","displayName","isPrimary","isDefault","o","sub","mailboxDoc","credentialDoc","existingMailbox","existingCredential","current","mine","collection","doc","where","limit","size","defaultOption","find","reconnected","keepSendAs","provider","connectedAtMs","updatedAtMs","timezone","String","name","dailyCap","window","days","rampStartedAtMs","health","sentToday","sentOnDay","bounces","replies","lastSentAtMs","lastErrorAtMs","lastErrorCode","daily","createdAtMs","credential","providerAccountId","scopes","split","filter","Boolean","keyring","Number","batch","set","merge","commit","confirmAliasesByProvider","addresses","map","console","logOrgActivity","type","created","confirmedAliases","confirmed","settings","loaded","update","invalid","message","trim","toLowerCase","cap","running","Object","keys","next","autoPause","limited","consumeRateLimit","windowMs","allowed","named","to","opened","errorCode","sent","from","address","subject","join","sentTo","gmailMessageId","sentAtMs","disconnect","revocation","delete","retryable"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/mailboxes/mailbox-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\nimport { normalizeContactEmail } from '@aglyn/aglyn/app-utils/contacts'\nimport type { PluginWebApiHandler } from '@aglyn/aglyn/server'\nimport {\n googleMailboxSenderExpectation,\n type SenderReadiness,\n type SenderReadinessExpectation,\n} from '@aglyn/shared-util-email'\nimport type {\n OutreachMailbox,\n OutreachMailboxStatus,\n} from '../model/outreach.types'\nimport { createGmailClient } from '../transport/gmail-client'\nimport { GmailTransportError } from '../transport/gmail-errors'\nimport {\n buildGoogleAuthorizationUrl,\n exchangeGoogleAuthorizationCode,\n grantedScopesInclude,\n OUTREACH_REQUIRED_GRANTED_SCOPES,\n pkceChallenge,\n readGoogleIdToken,\n type GoogleTokenResponse,\n} from '../transport/google-oauth'\nimport type { TransportDeps } from '../transport/http'\nimport { Rfc5322MessageError } from '../transport/rfc5322'\nimport { sendOutreachMessage } from '../transport/send-message'\nimport {\n buildConnectReturnFragment,\n type OutreachApiRefusalReason,\n type OutreachConnectReturn,\n type OutreachMailboxAvailability,\n type OutreachMailboxAvailabilityGate,\n type OutreachMailboxDisconnectResponse,\n type OutreachMailboxReadinessResponse,\n} from './mailbox-api'\nimport {\n mailboxCredentialsRef,\n mailboxRef,\n outreachMailboxId,\n readMailboxCredentials,\n sealMailboxRefreshToken,\n type OutreachGoogleMailboxCredentials,\n} from './mailbox-credentials'\nimport { revokeMailboxGrant } from './mailbox-revoke'\nimport {\n outreachMemberGate,\n refusal,\n type OutreachGateContext,\n type OutreachGateDeps,\n} from './mailbox-gate'\nimport {\n defaultSendAs,\n isValidTimezone,\n OUTREACH_DEFAULT_DAILY_CAP,\n OUTREACH_DEFAULT_WINDOW,\n OUTREACH_MAX_MAILBOXES_PER_MEMBER,\n validateDailyCap,\n validateDisplayName,\n validateSendWindow,\n verifiedSendAsAddresses,\n} from './mailbox-settings'\nimport {\n markOutreachMailboxReconnectRequired,\n openOutreachMailboxClient,\n} from './mailbox-transport'\nimport {\n consumeOutreachOAuthState,\n mintOutreachOAuthState,\n outreachOidcNonce,\n outreachPkceVerifier,\n readOutreachOAuthState,\n recordOutreachOAuthState,\n} from './oauth-state'\nimport {\n OUTREACH_NOT_CONFIGURED_MESSAGE,\n type OutreachGoogleConfigResult,\n} from './outreach-config'\n\n/**\n * THE MAILBOX ROUTES (AGL-2978): connect a rep's Google mailbox, keep its\n * settings, pause it, test it, and disconnect it.\n *\n * ## The connect, end to end\n *\n * 1. `connect` (the member's session) mints a signed, single-use state bound\n * to the member and the organization, records its pending entry, and\n * answers Google's consent address — offline access, the consent prompt,\n * a PKCE challenge and an OpenID nonce, both derived from the state.\n * 2. Google redirects the browser to `oauth/callback`. That request carries\n * no bearer token, so it DOES nothing: it verifies the state's signature,\n * finds the organization's console path, and redirects to the Mailboxes\n * page with the code in the URL FRAGMENT, where no server and no `Referer`\n * sees it. Its release gate was asked about the organization and the\n * member the state names (the route's declared subject).\n * 3. The page takes the code out of the address bar and posts it to\n * `connect/complete` with the member's own session. That is where the\n * state is consumed, where it is refused if it was minted for anyone else,\n * and where the code is exchanged. So a consent screen someone else was\n * tricked into completing cannot connect THEIR mailbox to the member who\n * started it: the code arrives in the wrong browser, whose session is not\n * the state's member.\n *\n * ## Refusals, in order, on `connect/complete`\n *\n * A state that is invalid, minted for another member or organization,\n * expired, already used or superseded; a code Google refuses; a grant with no\n * refresh token; a grant missing `gmail.send` or `gmail.readonly` (the consent\n * screen lets a person untick them); an ID token that is not about this\n * exchange or whose address Google has not verified; a Gmail profile whose\n * address is not the ID token's; and a member at their mailbox limit.\n *\n * A grant refused after the code exchange is dropped, not revoked. Google's\n * revocation ends the whole grant this client holds for the account, and the\n * same account may already be connected — by this member in another\n * organization, or by a teammate — so revoking a token this route merely\n * declines to keep could disconnect a mailbox that is working.\n *\n * ## Who may do what\n *\n * Every route but the callback climbs `outreachMemberGate`. The member who\n * connected a mailbox manages it; an organization owner or admin may also\n * change its settings, pause it and disconnect it — a departing rep's mailbox\n * has to be stoppable by somebody. Only its own member can send a test from\n * it, since the test lands in their inbox.\n */\n\nexport interface OutreachMailboxRouteDeps {\n firestore(): FirebaseFirestore.Firestore\n gate: OutreachGateDeps\n readConfig(): OutreachGoogleConfigResult\n /** Whether the signing secret the OAuth state needs is configured. */\n stateSigningConfigured(): boolean\n redirectUri(requestUrl: string): string | null\n now(): number\n /** The network and retry policy every Google call uses. */\n transport: TransportDeps\n consumeRateLimit(key: string, options: { limit: number; windowMs: number }): Promise<{ allowed: boolean }>\n logOrgActivity(\n orgId: string,\n actor: { uid: string; email?: string | null },\n action: string,\n target: { type: typeof OUTREACH_MAILBOX_ACTIVITY_TARGET; id: string; name: string },\n ): Promise<void>\n confirmAliasesByProvider(\n firestore: FirebaseFirestore.Firestore,\n input: { orgId: string; uid: string; addresses: readonly string[]; nowMs: number },\n ): Promise<{ ok: boolean; confirmed?: string[] }>\n /** SPF, DKIM and DMARC for one sender, looked up and briefly remembered. */\n readSenderReadiness(expectation: SenderReadinessExpectation, options: { fresh?: boolean }): Promise<SenderReadiness>\n}\n\nexport interface OutreachMailboxRoutes {\n availability: PluginWebApiHandler\n connect: PluginWebApiHandler\n oauthCallback: PluginWebApiHandler\n connectComplete: PluginWebApiHandler\n settings: PluginWebApiHandler\n status: PluginWebApiHandler\n test: PluginWebApiHandler\n disconnect: PluginWebApiHandler\n readiness: PluginWebApiHandler\n}\n\n/** Test sends one mailbox may make in an hour. */\nexport const OUTREACH_TEST_SENDS_PER_HOUR = 5\n\n/**\n * The org activity target a mailbox's rows are filed under: Outreach's own\n * namespaced resource, which the feed reads as \"Mailbox\".\n */\nexport const OUTREACH_MAILBOX_ACTIVITY_TARGET = 'outreach:mailbox'\n\nconst MAILBOX_ID = /^[A-Za-z0-9_-]{1,128}$/\nconst MAX_CODE_CHARS = 2048\n\nconst noStore = { 'Cache-Control': 'no-store' }\n\nconst ok = (body: unknown) => Response.json(body, { status: 200, headers: noStore })\n\nfunction methodNotAllowed(allow: string): Response {\n return Response.json(\n { error: 'Method not allowed', reason: 'method-not-allowed' satisfies OutreachApiRefusalReason },\n { status: 405, headers: { ...noStore, Allow: allow } },\n )\n}\n\nasync function readBody(request: Request): Promise<Record<string, unknown>> {\n const body = await request.json().catch(() => null)\n return body && typeof body === 'object' && !Array.isArray(body) ? (body as Record<string, unknown>) : {}\n}\n\nfunction notConfigured(): Response {\n return refusal(503, 'not-configured', OUTREACH_NOT_CONFIGURED_MESSAGE)\n}\n\n/** A mailbox document read defensively into the model shape. */\nfunction readMailbox(id: string, data: Record<string, unknown> | undefined): OutreachMailbox | null {\n if (!data) return null\n return { ...(data as unknown as OutreachMailbox), id }\n}\n\nconst activityName = (mailbox: Pick<OutreachMailbox, 'email'>) => mailbox.email\n\nexport function createOutreachMailboxRoutes(deps: OutreachMailboxRouteDeps): OutreachMailboxRoutes {\n /** The gate, then the mailbox the body names, then who may touch it. */\n async function loadManagedMailbox(\n request: Request,\n body: Record<string, unknown>,\n who: 'member-or-admin' | 'member',\n ): Promise<Response | { gate: OutreachGateContext; mailbox: OutreachMailbox }> {\n const gate = await outreachMemberGate(request, body['orgId'], deps.gate)\n if (gate instanceof Response) return gate\n const mailboxId = typeof body['mailboxId'] === 'string' ? body['mailboxId'] : ''\n if (!MAILBOX_ID.test(mailboxId)) return refusal(400, 'invalid-request', 'Name the mailbox.')\n const snapshot = await mailboxRef(deps.firestore(), gate.orgId, mailboxId).get()\n const mailbox = readMailbox(mailboxId, snapshot.exists ? snapshot.data() : undefined)\n if (!mailbox) return refusal(404, 'mailbox-not-found', 'That mailbox is not connected to this organization.')\n const own = mailbox.connectedByUid === gate.uid\n if (!own && !(who === 'member-or-admin' && gate.isOrgAdmin)) {\n return refusal(\n 403,\n 'not-your-mailbox',\n who === 'member'\n ? 'Only the member who connected this mailbox can do that.'\n : 'Only the member who connected this mailbox, or an organization owner or admin, can change it.',\n )\n }\n return { gate, mailbox }\n }\n\n const availability: PluginWebApiHandler = async (request) => {\n if (request.method !== 'GET') return methodNotAllowed('GET')\n const gate = await outreachMemberGate(request, new URL(request.url).searchParams.get('orgId'), deps.gate)\n if (gate instanceof Response) return gate\n // Every gate is named, not the first: an operator fixing a deployment\n // should see the whole list once rather than one variable per redeploy.\n const missing: OutreachMailboxAvailabilityGate[] = []\n const config = deps.readConfig()\n if (config.configured === false) missing.push({ gate: 'google', missing: config.missing })\n if (!deps.stateSigningConfigured()) missing.push({ gate: 'state' })\n if (!deps.redirectUri(request.url)) missing.push({ gate: 'redirect' })\n return ok({\n configured: missing.length === 0,\n canManageAll: gate.isOrgAdmin,\n ...(missing.length ? { missing } : {}),\n } satisfies OutreachMailboxAvailability)\n }\n\n const connect: PluginWebApiHandler = async (request) => {\n if (request.method !== 'POST') return methodNotAllowed('POST')\n const body = await readBody(request)\n const gate = await outreachMemberGate(request, body['orgId'], deps.gate)\n if (gate instanceof Response) return gate\n const config = deps.readConfig()\n const redirectUri = deps.redirectUri(request.url)\n if (!config.configured || !redirectUri || !deps.stateSigningConfigured()) return notConfigured()\n\n const nowMs = deps.now()\n const { state, claims } = mintOutreachOAuthState({ orgId: gate.orgId, uid: gate.uid, nowMs })\n await recordOutreachOAuthState(deps.firestore(), { claims, redirectUri, nowMs })\n const url = buildGoogleAuthorizationUrl({\n clientId: config.config.clientId,\n redirectUri,\n state,\n codeChallenge: pkceChallenge(outreachPkceVerifier(claims.nonce)),\n nonce: outreachOidcNonce(claims.nonce),\n loginHint: gate.email,\n })\n return ok({ url })\n }\n\n const oauthCallback: PluginWebApiHandler = async (request) => {\n if (request.method !== 'GET') return methodNotAllowed('GET')\n const params = new URL(request.url).searchParams\n const read = readOutreachOAuthState(params.get('state'), deps.now())\n const plain = (status: number, text: string) =>\n new Response(text, {\n status,\n headers: { ...noStore, 'Content-Type': 'text/plain; charset=utf-8', 'Referrer-Policy': 'no-referrer' },\n })\n if (read.ok === false && read.refusal === 'state-invalid') {\n return plain(400, 'This connection link is not valid. Return to the console and connect the mailbox again.')\n }\n const claims = read.ok ? read.claims : (read as { claims: { orgId: string } }).claims\n const org = await deps.gate.readOrg(claims.orgId)\n const slug = typeof org?.['slug'] === 'string' ? org['slug'] : ''\n if (!slug) return plain(404, 'That organization could not be found.')\n\n let fragment: OutreachConnectReturn\n const googleError = params.get('error')\n const code = params.get('code') ?? ''\n if (googleError) {\n fragment = { kind: 'error', reason: googleError === 'access_denied' ? 'access_denied' : 'google_error' }\n } else if (!read.ok) {\n fragment = { kind: 'error', reason: 'expired' }\n } else if (!code || code.length > MAX_CODE_CHARS) {\n fragment = { kind: 'error', reason: 'google_error' }\n } else {\n fragment = { kind: 'code', code, state: params.get('state') ?? '' }\n }\n // A RELATIVE location on the callback's own origin: the path is built from\n // the organization's slug, never from anything the request supplied.\n return new Response(null, {\n status: 303,\n headers: {\n ...noStore,\n 'Referrer-Policy': 'no-referrer',\n Location: `/${encodeURIComponent(slug)}/outreach/mailboxes#${buildConnectReturnFragment(fragment)}`,\n },\n })\n }\n\n const connectComplete: PluginWebApiHandler = async (request) => {\n if (request.method !== 'POST') return methodNotAllowed('POST')\n const body = await readBody(request)\n const gate = await outreachMemberGate(request, body['orgId'], deps.gate)\n if (gate instanceof Response) return gate\n const config = deps.readConfig()\n if (!config.configured || !deps.stateSigningConfigured()) return notConfigured()\n const code = typeof body['code'] === 'string' ? body['code'] : ''\n if (!code || code.length > MAX_CODE_CHARS) return refusal(400, 'invalid-request', 'The connection did not return a code.')\n\n const firestore = deps.firestore()\n const nowMs = deps.now()\n const read = readOutreachOAuthState(body['state'], nowMs)\n if (read.ok === false && read.refusal === 'state-invalid') {\n return refusal(400, 'state-invalid', 'This connection could not be verified. Connect the mailbox again.')\n }\n const claims = read.ok ? read.claims : (read as { claims: typeof read.claims }).claims\n if (claims.uid !== gate.uid) {\n return refusal(\n 403,\n 'state-user-mismatch',\n 'This connection was started by another member. Sign in as that member, or connect the mailbox again.',\n )\n }\n if (claims.orgId !== gate.orgId) {\n return refusal(400, 'state-org-mismatch', 'This connection was started in another organization.')\n }\n if (!read.ok) return refusal(410, 'state-expired', 'This connection took too long. Connect the mailbox again.')\n\n const consumed = await consumeOutreachOAuthState(firestore, { claims, nowMs })\n if (consumed.ok === false) {\n if (consumed.refusal === 'state-expired') {\n return refusal(410, 'state-expired', 'This connection took too long. Connect the mailbox again.')\n }\n return refusal(\n 409,\n consumed.refusal,\n consumed.refusal === 'state-superseded'\n ? 'A newer connection was started. Finish that one, or connect the mailbox again.'\n : 'This connection was already used. Connect the mailbox again.',\n )\n }\n\n let grant: GoogleTokenResponse\n try {\n grant = await exchangeGoogleAuthorizationCode(\n {\n clientId: config.config.clientId,\n clientSecret: config.config.clientSecret,\n code,\n redirectUri: consumed.redirectUri,\n codeVerifier: outreachPkceVerifier(claims.nonce),\n },\n deps.transport,\n )\n } catch (error) {\n if (error instanceof GmailTransportError && error.code === 'invalid_grant') {\n return refusal(400, 'code-rejected', \"Google's sign-in expired or was already used. Connect the mailbox again.\")\n }\n if (error instanceof GmailTransportError && error.code === 'client_misconfigured') {\n return refusal(503, 'not-configured', \"Google refused this deployment's OAuth client.\")\n }\n return googleUnavailable(error)\n }\n if (!grant.refreshToken) {\n return refusal(\n 422,\n 'refresh-token-missing',\n 'Google did not grant offline access, so the mailbox could not send later. Connect it again.',\n )\n }\n if (!grantedScopesInclude(grant.scope, OUTREACH_REQUIRED_GRANTED_SCOPES)) {\n return refusal(\n 422,\n 'scopes-missing',\n 'Sequences needs permission to send and to read your mail. Connect again and allow both.',\n )\n }\n const identity = readGoogleIdToken(grant.idToken, {\n clientId: config.config.clientId,\n nonce: outreachOidcNonce(claims.nonce),\n nowMs,\n })\n if (identity.ok === false) {\n return refusal(\n 422,\n 'identity-unverified',\n identity.refusal === 'email-unverified'\n ? \"Google has not verified this account's email address.\"\n : \"Google's answer could not be verified. Connect the mailbox again.\",\n )\n }\n\n const client = createGmailClient({\n ...deps.transport,\n clientId: config.config.clientId,\n clientSecret: config.config.clientSecret,\n refreshToken: grant.refreshToken,\n })\n let profileEmail: string\n let sendAsOptions: ReturnType<typeof verifiedSendAsAddresses>\n try {\n const [profile, sendAs] = await Promise.all([client.getProfile(), client.listSendAs()])\n profileEmail = profile.emailAddress\n sendAsOptions = verifiedSendAsAddresses(sendAs)\n } catch (error) {\n if (error instanceof GmailTransportError && error.reconnectRequired) {\n return refusal(\n 422,\n 'scopes-missing',\n 'Sequences needs permission to send and to read your mail. Connect again and allow both.',\n )\n }\n return googleUnavailable(error)\n }\n if (profileEmail !== identity.identity.email) {\n return refusal(422, 'account-mismatch', 'The Gmail account and the Google sign-in did not match. Connect again.')\n }\n if (!sendAsOptions.some((option) => option.email === profileEmail)) {\n sendAsOptions = [\n { email: profileEmail, displayName: '', isPrimary: true, isDefault: !sendAsOptions.some((o) => o.isDefault) },\n ...sendAsOptions,\n ]\n }\n\n const mailboxId = outreachMailboxId(gate.orgId, gate.uid, identity.identity.sub)\n const mailboxDoc = mailboxRef(firestore, gate.orgId, mailboxId)\n const credentialDoc = mailboxCredentialsRef(firestore, mailboxId)\n const [existingMailbox, existingCredential] = await Promise.all([mailboxDoc.get(), credentialDoc.get()])\n const current = readMailbox(mailboxId, existingMailbox.exists ? existingMailbox.data() : undefined)\n if (!current) {\n const mine = await firestore\n .collection('orgs')\n .doc(gate.orgId)\n .collection('outreachMailboxes')\n .where('connectedByUid', '==', gate.uid)\n .limit(OUTREACH_MAX_MAILBOXES_PER_MEMBER)\n .get()\n if (mine.size >= OUTREACH_MAX_MAILBOXES_PER_MEMBER) {\n return refusal(\n 409,\n 'mailbox-limit',\n `You can connect up to ${OUTREACH_MAX_MAILBOXES_PER_MEMBER} mailboxes. Disconnect one before connecting another.`,\n )\n }\n }\n\n const defaultOption = sendAsOptions.find((option) => option.email === defaultSendAs(sendAsOptions, profileEmail))\n let mailbox: OutreachMailbox\n /** On a reconnect, only the fields a connect decides — see the write below. */\n let reconnected: Partial<OutreachMailbox> | null = null\n if (current) {\n const keepSendAs = sendAsOptions.some((option) => option.email === current.sendAs)\n reconnected = {\n provider: 'google',\n email: profileEmail,\n sendAs: keepSendAs ? current.sendAs : (defaultOption?.email ?? profileEmail),\n sendAsOptions,\n // A reconnect restores a mailbox Google had cut off, and leaves a\n // mailbox its member paused paused.\n status: (current.status === 'paused' ? 'paused' : 'connected') satisfies OutreachMailboxStatus,\n connectedAtMs: nowMs,\n updatedAtMs: nowMs,\n }\n mailbox = { ...current, ...reconnected }\n } else {\n const timezone = isValidTimezone(body['timezone']) ? String(body['timezone']) : 'UTC'\n const displayName = validateDisplayName(defaultOption?.displayName || gate.name || '')\n mailbox = {\n id: mailboxId,\n provider: 'google',\n email: profileEmail,\n sendAs: defaultOption?.email ?? profileEmail,\n sendAsOptions,\n displayName: typeof displayName === 'string' ? displayName : '',\n status: 'connected',\n dailyCap: OUTREACH_DEFAULT_DAILY_CAP,\n window: { ...OUTREACH_DEFAULT_WINDOW, days: [...OUTREACH_DEFAULT_WINDOW.days] },\n timezone,\n rampStartedAtMs: nowMs,\n health: {\n sentToday: 0,\n sentOnDay: null,\n bounces: 0,\n replies: 0,\n lastSentAtMs: null,\n lastErrorAtMs: null,\n lastErrorCode: null,\n daily: {},\n },\n connectedByUid: gate.uid,\n connectedAtMs: nowMs,\n createdAtMs: nowMs,\n updatedAtMs: nowMs,\n }\n }\n const credential: OutreachGoogleMailboxCredentials = {\n id: mailboxId,\n orgId: gate.orgId,\n mailboxId,\n provider: 'google',\n connectedByUid: gate.uid,\n providerAccountId: identity.identity.sub,\n email: profileEmail,\n scopes: grant.scope.split(/\\s+/).filter(Boolean),\n ...sealMailboxRefreshToken(grant.refreshToken, mailboxId, config.config.keyring),\n createdAtMs: Number(existingCredential.exists ? existingCredential.get('createdAtMs') : 0) || nowMs,\n updatedAtMs: nowMs,\n }\n const batch = firestore.batch()\n // A reconnect MERGES only what a connect decides, so the settings a member\n // chose and the health counters the sending runtime increments are never\n // written back from a read that may already be stale.\n if (reconnected) batch.set(mailboxDoc, reconnected, { merge: true })\n else batch.set(mailboxDoc, mailbox)\n batch.set(credentialDoc, credential)\n await batch.commit()\n\n const aliases: { ok: boolean; confirmed?: string[] } = await deps\n .confirmAliasesByProvider(firestore, {\n orgId: gate.orgId,\n uid: gate.uid,\n addresses: sendAsOptions.map((option) => option.email),\n nowMs,\n })\n .catch((error: unknown) => {\n console.error('[outreach] confirming member aliases from send-as failed', error)\n return { ok: false }\n })\n await deps.logOrgActivity(\n gate.orgId,\n { uid: gate.uid, email: gate.email },\n current ? 'Reconnected a mailbox in Sequences' : 'Connected a mailbox in Sequences',\n { type: OUTREACH_MAILBOX_ACTIVITY_TARGET, id: mailboxId, name: activityName(mailbox) },\n )\n return ok({\n ok: true,\n mailbox,\n created: !current,\n confirmedAliases: aliases.ok ? (aliases.confirmed ?? []) : [],\n })\n }\n\n const settings: PluginWebApiHandler = async (request) => {\n if (request.method !== 'POST') return methodNotAllowed('POST')\n const body = await readBody(request)\n const loaded = await loadManagedMailbox(request, body, 'member-or-admin')\n if (loaded instanceof Response) return loaded\n const { gate, mailbox } = loaded\n const update: Partial<OutreachMailbox> = {}\n const invalid = (message: string) => refusal(400, 'invalid-settings', message)\n\n if (body['sendAs'] !== undefined) {\n const sendAs = typeof body['sendAs'] === 'string' ? body['sendAs'].trim().toLowerCase() : ''\n if (!(mailbox.sendAsOptions ?? []).some((option) => option.email === sendAs)) {\n return invalid('Choose one of the addresses Gmail has verified for this account.')\n }\n update.sendAs = sendAs\n }\n if (body['displayName'] !== undefined) {\n const name = validateDisplayName(body['displayName'])\n if (typeof name !== 'string') return invalid(name.message)\n update.displayName = name\n }\n if (body['dailyCap'] !== undefined) {\n const cap = validateDailyCap(body['dailyCap'])\n if (typeof cap !== 'number') return invalid(cap.message)\n update.dailyCap = cap\n }\n if (body['window'] !== undefined) {\n const window = validateSendWindow(body['window'])\n if (!('days' in window)) return invalid(window.message)\n update.window = window\n }\n if (body['timezone'] !== undefined) {\n if (!isValidTimezone(body['timezone'])) return invalid('Choose a timezone from the list.')\n update.timezone = body['timezone']\n }\n if (body['warmUp'] !== undefined) {\n /*\n * The ramp guards a NEW sender's reputation; a mailbox that has sent\n * mail for years has one already, and ramping it to ten a day guards\n * nothing (AGL-3228). Its member decides. Turning the ramp back on\n * starts a fresh one from today, which is what \"warm up\" means.\n */\n if (typeof body['warmUp'] !== 'boolean') return invalid('Say whether to warm the mailbox up gradually.')\n const running = typeof mailbox.rampStartedAtMs === 'number'\n if (body['warmUp'] && !running) update.rampStartedAtMs = deps.now()\n if (!body['warmUp'] && running) update.rampStartedAtMs = null\n }\n if (!Object.keys(update).length) return ok({ ok: true, mailbox })\n const nowMs = deps.now()\n await mailboxRef(deps.firestore(), gate.orgId, mailbox.id).update({ ...update, updatedAtMs: nowMs })\n return ok({ ok: true, mailbox: { ...mailbox, ...update, updatedAtMs: nowMs } })\n }\n\n const status: PluginWebApiHandler = async (request) => {\n if (request.method !== 'POST') return methodNotAllowed('POST')\n const body = await readBody(request)\n if (typeof body['paused'] !== 'boolean') return refusal(400, 'invalid-request', 'Say whether to pause the mailbox.')\n const loaded = await loadManagedMailbox(request, body, 'member-or-admin')\n if (loaded instanceof Response) return loaded\n const { gate, mailbox } = loaded\n if (mailbox.status === 'reconnect_required' || mailbox.status === 'disconnected') {\n return refusal(409, 'reconnect-required', 'Reconnect this mailbox before pausing or resuming it.')\n }\n const next: OutreachMailboxStatus = body['paused'] ? 'paused' : 'connected'\n if (mailbox.status === next) return ok({ ok: true, mailbox })\n const nowMs = deps.now()\n // A member's pause or resume answers an automatic pause (AGL-2981): its\n // reason goes, and the next bounce is judged as new evidence.\n await mailboxRef(deps.firestore(), gate.orgId, mailbox.id).update({\n status: next,\n autoPause: null,\n updatedAtMs: nowMs,\n })\n await deps.logOrgActivity(\n gate.orgId,\n { uid: gate.uid, email: gate.email },\n next === 'paused' ? 'Paused a mailbox in Sequences' : 'Resumed a mailbox in Sequences',\n { type: OUTREACH_MAILBOX_ACTIVITY_TARGET, id: mailbox.id, name: activityName(mailbox) },\n )\n return ok({ ok: true, mailbox: { ...mailbox, status: next, autoPause: null, updatedAtMs: nowMs } })\n }\n\n const test: PluginWebApiHandler = async (request) => {\n if (request.method !== 'POST') return methodNotAllowed('POST')\n const body = await readBody(request)\n const loaded = await loadManagedMailbox(request, body, 'member')\n if (loaded instanceof Response) return loaded\n const { gate, mailbox } = loaded\n if (mailbox.status === 'reconnect_required' || mailbox.status === 'disconnected') {\n return refusal(409, 'reconnect-required', 'Reconnect this mailbox before sending from it.')\n }\n if (!deps.readConfig().configured) return notConfigured()\n const limited = await deps.consumeRateLimit(`outreach-mailbox-test:${mailbox.id}`, {\n limit: OUTREACH_TEST_SENDS_PER_HOUR,\n windowMs: 60 * 60 * 1000,\n })\n if (!limited.allowed) {\n return refusal(429, 'rate-limited', `A mailbox can send ${OUTREACH_TEST_SENDS_PER_HOUR} tests an hour. Try again later.`)\n }\n /*\n * To the member's own address unless they name another (AGL-3228). A\n * test to oneself never leaves Google, so it arrives with no\n * Authentication-Results at all and cannot show whether the send-as\n * domain's DKIM and DMARC hold; an outside mailbox the member can read\n * is the only place that can be seen. The rate limit above is the\n * whole guard on who may be written to — it is the member's own\n * account sending, as it could from its own compose window.\n */\n const named = body['to']\n const to =\n named === undefined || named === null || String(named).trim() === ''\n ? mailbox.email\n : normalizeContactEmail(String(named))\n if (!to) {\n return refusal(400, 'invalid-request', `\"${String(named).trim()}\" isn't a valid email address.`)\n }\n const firestore = deps.firestore()\n const nowMs = deps.now()\n const opened = await openOutreachMailboxClient(\n firestore,\n { mailboxId: mailbox.id },\n { ...deps.transport, readConfig: deps.readConfig, now: deps.now },\n )\n if (opened.ok === false) {\n if (opened.reason === 'not-configured') return notConfigured()\n await markOutreachMailboxReconnectRequired(firestore, {\n orgId: gate.orgId,\n mailboxId: mailbox.id,\n errorCode: opened.reason,\n nowMs,\n })\n return refusal(409, 'reconnect-required', 'This mailbox needs to be connected again before it can send.')\n }\n try {\n const sent = await sendOutreachMessage(opened.client, {\n from: { address: mailbox.sendAs, name: mailbox.displayName },\n to,\n subject: 'Sequences test message',\n text: [\n 'This is a test message from Sequences.',\n '',\n `It was sent through the connected mailbox ${mailbox.email} as ${mailbox.sendAs}. ` +\n 'If it reached the inbox, this mailbox can send.',\n ...(to === mailbox.email\n ? []\n : [\n '',\n 'To read whether the sender is authenticated, open this message\\u2019s original source ' +\n 'and look for dkim=pass, spf=pass and dmarc=pass in Authentication-Results.',\n ]),\n ].join('\\n'),\n })\n return ok({ ok: true, sentTo: to, gmailMessageId: sent.gmailMessageId, sentAtMs: nowMs })\n } catch (error) {\n if (error instanceof GmailTransportError && error.reconnectRequired) {\n await markOutreachMailboxReconnectRequired(firestore, {\n orgId: gate.orgId,\n mailboxId: mailbox.id,\n errorCode: error.code,\n nowMs,\n })\n return refusal(409, 'reconnect-required', 'Google refused this mailbox’s access. Connect it again.')\n }\n if (error instanceof Rfc5322MessageError) {\n return refusal(400, 'invalid-settings', `The test could not be written: ${error.message}`)\n }\n return googleUnavailable(error)\n }\n }\n\n const disconnect: PluginWebApiHandler = async (request) => {\n if (request.method !== 'POST') return methodNotAllowed('POST')\n const body = await readBody(request)\n const loaded = await loadManagedMailbox(request, body, 'member-or-admin')\n if (loaded instanceof Response) return loaded\n const { gate, mailbox } = loaded\n const firestore = deps.firestore()\n const credentialDoc = mailboxCredentialsRef(firestore, mailbox.id)\n const snapshot = await credentialDoc.get()\n const credential = readMailboxCredentials(snapshot.exists ? snapshot.data() : null)\n const revocation = credential\n ? await revokeMailboxGrant(firestore, credential, deps)\n : ('already-invalid' as const)\n const batch = firestore.batch()\n batch.delete(credentialDoc)\n batch.delete(mailboxRef(firestore, gate.orgId, mailbox.id))\n await batch.commit()\n await deps.logOrgActivity(\n gate.orgId,\n { uid: gate.uid, email: gate.email },\n 'Disconnected a mailbox in Sequences',\n { type: OUTREACH_MAILBOX_ACTIVITY_TARGET, id: mailbox.id, name: activityName(mailbox) },\n )\n return ok({ ok: true, revocation } satisfies OutreachMailboxDisconnectResponse)\n }\n\n /**\n * How receivers judge mail from the address a mailbox sends as. Any member\n * who can see the mailbox can read it: it is a reading of public DNS about\n * the organization's own domain, and changes nothing.\n */\n const readiness: PluginWebApiHandler = async (request) => {\n if (request.method !== 'POST') return methodNotAllowed('POST')\n const body = await readBody(request)\n const gate = await outreachMemberGate(request, body['orgId'], deps.gate)\n if (gate instanceof Response) return gate\n const mailboxId = typeof body['mailboxId'] === 'string' ? body['mailboxId'] : ''\n if (!MAILBOX_ID.test(mailboxId)) return refusal(400, 'invalid-request', 'Name the mailbox.')\n const snapshot = await mailboxRef(deps.firestore(), gate.orgId, mailboxId).get()\n const mailbox = readMailbox(mailboxId, snapshot.exists ? snapshot.data() : undefined)\n if (!mailbox) return refusal(404, 'mailbox-not-found', 'That mailbox is not connected to this organization.')\n const sendAs = mailbox.sendAs || mailbox.email\n const expectation = googleMailboxSenderExpectation(sendAs)\n const read = expectation\n ? await deps.readSenderReadiness(expectation, { fresh: body['fresh'] === true }).catch(() => null)\n : null\n return ok({ ok: true, sendAs, readiness: read } satisfies OutreachMailboxReadinessResponse)\n }\n\n return { availability, connect, oauthCallback, connectComplete, settings, status, test, disconnect, readiness }\n}\n\nfunction googleUnavailable(error: unknown): Response {\n if (!(error instanceof GmailTransportError)) throw error\n return refusal(\n 502,\n 'google-unavailable',\n error.retryable ? 'Google did not answer. Try again in a moment.' : `Google refused the request: ${error.message}`,\n )\n}\n"],"names":["normalizeContactEmail","googleMailboxSenderExpectation","createGmailClient","GmailTransportError","buildGoogleAuthorizationUrl","exchangeGoogleAuthorizationCode","grantedScopesInclude","OUTREACH_REQUIRED_GRANTED_SCOPES","pkceChallenge","readGoogleIdToken","Rfc5322MessageError","sendOutreachMessage","buildConnectReturnFragment","mailboxCredentialsRef","mailboxRef","outreachMailboxId","readMailboxCredentials","sealMailboxRefreshToken","revokeMailboxGrant","outreachMemberGate","refusal","defaultSendAs","isValidTimezone","OUTREACH_DEFAULT_DAILY_CAP","OUTREACH_DEFAULT_WINDOW","OUTREACH_MAX_MAILBOXES_PER_MEMBER","validateDailyCap","validateDisplayName","validateSendWindow","verifiedSendAsAddresses","markOutreachMailboxReconnectRequired","openOutreachMailboxClient","consumeOutreachOAuthState","mintOutreachOAuthState","outreachOidcNonce","outreachPkceVerifier","readOutreachOAuthState","recordOutreachOAuthState","OUTREACH_NOT_CONFIGURED_MESSAGE","OUTREACH_TEST_SENDS_PER_HOUR","OUTREACH_MAILBOX_ACTIVITY_TARGET","MAILBOX_ID","MAX_CODE_CHARS","noStore","ok","body","Response","json","status","headers","methodNotAllowed","allow","error","reason","Allow","readBody","request","catch","Array","isArray","notConfigured","readMailbox","id","data","activityName","mailbox","email","createOutreachMailboxRoutes","deps","loadManagedMailbox","who","gate","mailboxId","test","snapshot","firestore","orgId","get","exists","undefined","own","connectedByUid","uid","isOrgAdmin","availability","method","URL","url","searchParams","missing","config","readConfig","configured","push","stateSigningConfigured","redirectUri","length","canManageAll","connect","nowMs","now","state","claims","clientId","codeChallenge","nonce","loginHint","oauthCallback","params","read","plain","text","org","readOrg","slug","fragment","googleError","code","kind","Location","encodeURIComponent","connectComplete","aliases","consumed","grant","clientSecret","codeVerifier","transport","googleUnavailable","refreshToken","scope","identity","idToken","client","profileEmail","sendAsOptions","profile","sendAs","Promise","all","getProfile","listSendAs","emailAddress","reconnectRequired","some","option","displayName","isPrimary","isDefault","o","sub","mailboxDoc","credentialDoc","existingMailbox","existingCredential","current","mine","collection","doc","where","limit","size","defaultOption","find","reconnected","keepSendAs","provider","connectedAtMs","updatedAtMs","timezone","String","name","dailyCap","window","days","rampStartedAtMs","health","sentToday","sentOnDay","bounces","replies","lastSentAtMs","lastErrorAtMs","lastErrorCode","daily","createdAtMs","credential","providerAccountId","scopes","split","filter","Boolean","keyring","Number","batch","set","merge","commit","confirmAliasesByProvider","addresses","map","console","logOrgActivity","type","created","confirmedAliases","confirmed","settings","loaded","update","invalid","message","trim","toLowerCase","cap","running","Object","keys","next","autoPause","limited","consumeRateLimit","windowMs","allowed","named","to","opened","errorCode","sent","from","address","subject","join","sentTo","gmailMessageId","sentAtMs","disconnect","revocation","delete","readiness","expectation","readSenderReadiness","fresh","retryable"],"mappings":"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"}
@@ -59,7 +59,8 @@ import { readOutreachGoogleConfig } from "./outreach-config.js";
59
59
  transport: {},
60
60
  consumeRateLimit: async (key, options)=>(await import("@aglyn/tenant-data-admin/server/rate-limit-store")).consumeRateLimit(key, options),
61
61
  logOrgActivity: async (orgId, actor, action, target)=>(await import("@aglyn/tenant-data-admin/server/organizations")).logOrgActivity(orgId, actor, action, target),
62
- confirmAliasesByProvider: async (firestore, input)=>(await import("@aglyn/tenant-data-admin/server/member-email-aliases")).confirmMemberEmailAliasesByProvider(firestore, input)
62
+ confirmAliasesByProvider: async (firestore, input)=>(await import("@aglyn/tenant-data-admin/server/member-email-aliases")).confirmMemberEmailAliasesByProvider(firestore, input),
63
+ readSenderReadiness: async (expectation, options)=>(await import("@aglyn/tenant-data-admin/server/sender-readiness")).readSenderReadiness(expectation, options)
63
64
  };
64
65
  }
65
66
  /**
@@ -130,6 +131,9 @@ export function registerOutreachMailboxRoutes(deps = defaultOutreachMailboxRoute
130
131
  registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesDisconnect, {
131
132
  web: routes.disconnect
132
133
  }, orgSubject);
134
+ registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesReadiness, {
135
+ web: routes.readiness
136
+ }, orgSubject);
133
137
  }
134
138
 
135
139
  //# sourceMappingURL=register-mailbox-routes.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/mailboxes/register-mailbox-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\nimport {\n registerPluginApiRoute,\n type PluginApiRequestSubject,\n} from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { tokenSigningSecret } from '@aglyn/tenant-data-admin/server/media-signing'\nimport { OUTREACH_API_ROUTES } from '../constants/api-routes'\nimport { readOrgId } from './mailbox-gate'\nimport {\n createOutreachMailboxRoutes,\n type OutreachMailboxRouteDeps,\n} from './mailbox-routes'\nimport { readOutreachOAuthState } from './oauth-state'\nimport { outreachOAuthRedirectUri } from './oauth-redirect'\nimport { readOutreachGoogleConfig } from './outreach-config'\n\n/**\n * Wires the mailbox routes into the console dispatcher (AGL-2978), with the\n * production dependencies and each route's release subject.\n */\n\n/**\n * The platform's own dependencies. Specs build their own.\n *\n * The permission resolver, the lockdown verdict, the rate limiter, the\n * activity log and the alias store are imported when a request first needs\n * them rather than when the bundle registers: `org-permissions` reaches the\n * whole tenant data barrel, and the manifest loads this bundle into every\n * console API process whether or not a mailbox route is ever called.\n */\nexport function defaultOutreachMailboxRouteDeps(): OutreachMailboxRouteDeps {\n return {\n firestore: () => firebaseAdmin.app().firestore(),\n gate: {\n verifyIdToken: (idToken) => firebaseAdmin.app().auth().verifyIdToken(idToken),\n resolveOrgPermissions: async (uid, context) =>\n (await import('@aglyn/tenant-runtime/org-permissions')).resolveOrgPermissions(uid, context),\n readOrg: async (orgId) => {\n const snapshot = await firebaseAdmin.app().firestore().collection('orgs').doc(orgId).get()\n return snapshot.exists ? (snapshot.data() ?? {}) : null\n },\n lockdownRefusal: async (options) =>\n (await import('@aglyn/tenant-data-admin/server/lockdown')).lockdownRefusal(options),\n },\n readConfig: readOutreachGoogleConfig,\n stateSigningConfigured: () => {\n try {\n return Boolean(tokenSigningSecret())\n } catch {\n return false\n }\n },\n redirectUri: outreachOAuthRedirectUri,\n now: Date.now,\n transport: {},\n consumeRateLimit: async (key, options) =>\n (await import('@aglyn/tenant-data-admin/server/rate-limit-store')).consumeRateLimit(key, options),\n logOrgActivity: async (orgId, actor, action, target) =>\n (await import('@aglyn/tenant-data-admin/server/organizations')).logOrgActivity(orgId, actor, action, target),\n confirmAliasesByProvider: async (firestore, input) =>\n (await import('@aglyn/tenant-data-admin/server/member-email-aliases')).confirmMemberEmailAliasesByProvider(\n firestore,\n input,\n ),\n }\n}\n\n/**\n * The org an authenticated mailbox request names — query or JSON body — for\n * the release gate. Unverified, exactly as a `hostId` is: the gate only\n * chooses which organization's rollout to read, and the route's own gate\n * refuses anyone who is not a member of it.\n */\nexport async function outreachOrgSubject(request: Request): Promise<PluginApiRequestSubject | null> {\n const fromQuery = readOrgId(new URL(request.url).searchParams.get('orgId'))\n if (fromQuery) return { orgId: fromQuery }\n if (request.method === 'GET' || request.method === 'HEAD') return null\n const body = (await request.json().catch(() => null)) as { orgId?: unknown } | null\n const orgId = readOrgId(body?.orgId)\n return orgId ? { orgId } : null\n}\n\n/**\n * The org AND member Google's redirect is for, read off the signed state.\n * Only a state whose signature verifies names anyone; an expired one still\n * does, so the rep who took too long lands on a page that says so rather than\n * on a 404.\n */\nexport function outreachOAuthCallbackSubject(request: Request): PluginApiRequestSubject | null {\n const read = readOutreachOAuthState(new URL(request.url).searchParams.get('state'), Date.now())\n if (read.ok) return { orgId: read.claims.orgId, uid: read.claims.uid }\n if (read.ok === false && read.refusal === 'state-expired') {\n const { claims } = read as { claims: { orgId: string; uid: string } }\n return { orgId: claims.orgId, uid: claims.uid }\n }\n return null\n}\n\nexport function registerOutreachMailboxRoutes(\n deps: OutreachMailboxRouteDeps = defaultOutreachMailboxRouteDeps(),\n): void {\n const routes = createOutreachMailboxRoutes(deps)\n const orgSubject = { subject: outreachOrgSubject }\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesAvailability, { web: routes.availability }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesConnect, { web: routes.connect }, orgSubject)\n registerPluginApiRoute(\n OUTREACH_API_ROUTES.mailboxesOAuthCallback,\n { web: routes.oauthCallback },\n { subject: outreachOAuthCallbackSubject },\n )\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesConnectComplete, { web: routes.connectComplete }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesSettings, { web: routes.settings }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesStatus, { web: routes.status }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesTest, { web: routes.test }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesDisconnect, { web: routes.disconnect }, orgSubject)\n}\n"],"names":["registerPluginApiRoute","firebaseAdmin","tokenSigningSecret","OUTREACH_API_ROUTES","readOrgId","createOutreachMailboxRoutes","readOutreachOAuthState","outreachOAuthRedirectUri","readOutreachGoogleConfig","defaultOutreachMailboxRouteDeps","firestore","app","gate","verifyIdToken","idToken","auth","resolveOrgPermissions","uid","context","readOrg","orgId","snapshot","collection","doc","get","exists","data","lockdownRefusal","options","readConfig","stateSigningConfigured","Boolean","redirectUri","now","Date","transport","consumeRateLimit","key","logOrgActivity","actor","action","target","confirmAliasesByProvider","input","confirmMemberEmailAliasesByProvider","outreachOrgSubject","request","fromQuery","URL","url","searchParams","method","body","json","catch","outreachOAuthCallbackSubject","read","ok","claims","refusal","registerOutreachMailboxRoutes","deps","routes","orgSubject","subject","mailboxesAvailability","web","availability","mailboxesConnect","connect","mailboxesOAuthCallback","oauthCallback","mailboxesConnectComplete","connectComplete","mailboxesSettings","settings","mailboxesStatus","status","mailboxesTest","test","mailboxesDisconnect","disconnect"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/mailboxes/register-mailbox-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\nimport {\n registerPluginApiRoute,\n type PluginApiRequestSubject,\n} from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { tokenSigningSecret } from '@aglyn/tenant-data-admin/server/media-signing'\nimport { OUTREACH_API_ROUTES } from '../constants/api-routes'\nimport { readOrgId } from './mailbox-gate'\nimport {\n createOutreachMailboxRoutes,\n type OutreachMailboxRouteDeps,\n} from './mailbox-routes'\nimport { readOutreachOAuthState } from './oauth-state'\nimport { outreachOAuthRedirectUri } from './oauth-redirect'\nimport { readOutreachGoogleConfig } from './outreach-config'\n\n/**\n * Wires the mailbox routes into the console dispatcher (AGL-2978), with the\n * production dependencies and each route's release subject.\n */\n\n/**\n * The platform's own dependencies. Specs build their own.\n *\n * The permission resolver, the lockdown verdict, the rate limiter, the\n * activity log and the alias store are imported when a request first needs\n * them rather than when the bundle registers: `org-permissions` reaches the\n * whole tenant data barrel, and the manifest loads this bundle into every\n * console API process whether or not a mailbox route is ever called.\n */\nexport function defaultOutreachMailboxRouteDeps(): OutreachMailboxRouteDeps {\n return {\n firestore: () => firebaseAdmin.app().firestore(),\n gate: {\n verifyIdToken: (idToken) => firebaseAdmin.app().auth().verifyIdToken(idToken),\n resolveOrgPermissions: async (uid, context) =>\n (await import('@aglyn/tenant-runtime/org-permissions')).resolveOrgPermissions(uid, context),\n readOrg: async (orgId) => {\n const snapshot = await firebaseAdmin.app().firestore().collection('orgs').doc(orgId).get()\n return snapshot.exists ? (snapshot.data() ?? {}) : null\n },\n lockdownRefusal: async (options) =>\n (await import('@aglyn/tenant-data-admin/server/lockdown')).lockdownRefusal(options),\n },\n readConfig: readOutreachGoogleConfig,\n stateSigningConfigured: () => {\n try {\n return Boolean(tokenSigningSecret())\n } catch {\n return false\n }\n },\n redirectUri: outreachOAuthRedirectUri,\n now: Date.now,\n transport: {},\n consumeRateLimit: async (key, options) =>\n (await import('@aglyn/tenant-data-admin/server/rate-limit-store')).consumeRateLimit(key, options),\n logOrgActivity: async (orgId, actor, action, target) =>\n (await import('@aglyn/tenant-data-admin/server/organizations')).logOrgActivity(orgId, actor, action, target),\n confirmAliasesByProvider: async (firestore, input) =>\n (await import('@aglyn/tenant-data-admin/server/member-email-aliases')).confirmMemberEmailAliasesByProvider(\n firestore,\n input,\n ),\n readSenderReadiness: async (expectation, options) =>\n (await import('@aglyn/tenant-data-admin/server/sender-readiness')).readSenderReadiness(expectation, options),\n }\n}\n\n/**\n * The org an authenticated mailbox request names — query or JSON body — for\n * the release gate. Unverified, exactly as a `hostId` is: the gate only\n * chooses which organization's rollout to read, and the route's own gate\n * refuses anyone who is not a member of it.\n */\nexport async function outreachOrgSubject(request: Request): Promise<PluginApiRequestSubject | null> {\n const fromQuery = readOrgId(new URL(request.url).searchParams.get('orgId'))\n if (fromQuery) return { orgId: fromQuery }\n if (request.method === 'GET' || request.method === 'HEAD') return null\n const body = (await request.json().catch(() => null)) as { orgId?: unknown } | null\n const orgId = readOrgId(body?.orgId)\n return orgId ? { orgId } : null\n}\n\n/**\n * The org AND member Google's redirect is for, read off the signed state.\n * Only a state whose signature verifies names anyone; an expired one still\n * does, so the rep who took too long lands on a page that says so rather than\n * on a 404.\n */\nexport function outreachOAuthCallbackSubject(request: Request): PluginApiRequestSubject | null {\n const read = readOutreachOAuthState(new URL(request.url).searchParams.get('state'), Date.now())\n if (read.ok) return { orgId: read.claims.orgId, uid: read.claims.uid }\n if (read.ok === false && read.refusal === 'state-expired') {\n const { claims } = read as { claims: { orgId: string; uid: string } }\n return { orgId: claims.orgId, uid: claims.uid }\n }\n return null\n}\n\nexport function registerOutreachMailboxRoutes(\n deps: OutreachMailboxRouteDeps = defaultOutreachMailboxRouteDeps(),\n): void {\n const routes = createOutreachMailboxRoutes(deps)\n const orgSubject = { subject: outreachOrgSubject }\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesAvailability, { web: routes.availability }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesConnect, { web: routes.connect }, orgSubject)\n registerPluginApiRoute(\n OUTREACH_API_ROUTES.mailboxesOAuthCallback,\n { web: routes.oauthCallback },\n { subject: outreachOAuthCallbackSubject },\n )\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesConnectComplete, { web: routes.connectComplete }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesSettings, { web: routes.settings }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesStatus, { web: routes.status }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesTest, { web: routes.test }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesDisconnect, { web: routes.disconnect }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesReadiness, { web: routes.readiness }, orgSubject)\n}\n"],"names":["registerPluginApiRoute","firebaseAdmin","tokenSigningSecret","OUTREACH_API_ROUTES","readOrgId","createOutreachMailboxRoutes","readOutreachOAuthState","outreachOAuthRedirectUri","readOutreachGoogleConfig","defaultOutreachMailboxRouteDeps","firestore","app","gate","verifyIdToken","idToken","auth","resolveOrgPermissions","uid","context","readOrg","orgId","snapshot","collection","doc","get","exists","data","lockdownRefusal","options","readConfig","stateSigningConfigured","Boolean","redirectUri","now","Date","transport","consumeRateLimit","key","logOrgActivity","actor","action","target","confirmAliasesByProvider","input","confirmMemberEmailAliasesByProvider","readSenderReadiness","expectation","outreachOrgSubject","request","fromQuery","URL","url","searchParams","method","body","json","catch","outreachOAuthCallbackSubject","read","ok","claims","refusal","registerOutreachMailboxRoutes","deps","routes","orgSubject","subject","mailboxesAvailability","web","availability","mailboxesConnect","connect","mailboxesOAuthCallback","oauthCallback","mailboxesConnectComplete","connectComplete","mailboxesSettings","settings","mailboxesStatus","status","mailboxesTest","test","mailboxesDisconnect","disconnect","mailboxesReadiness","readiness"],"mappings":"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"}