@aglyn/plugins-outreach 1.0.0-beta.150 → 1.0.0-beta.152

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.150",
3
+ "version": "1.0.0-beta.152",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,17 +25,17 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.150",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.150",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.150",
31
- "@aglyn/shared-ui-next": "1.0.0-beta.150",
32
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.150",
33
- "@aglyn/shared-util-http": "1.0.0-beta.150",
34
- "@aglyn/shared-util-timestamp": "1.0.0-beta.150",
35
- "@aglyn/shared-util-tools": "1.0.0-beta.150",
36
- "@aglyn/tenant-data-admin": "1.0.0-beta.150",
37
- "@aglyn/tenant-feature-instance": "1.0.0-beta.150",
38
- "@aglyn/tenant-runtime": "1.0.0-beta.150",
28
+ "@aglyn/aglyn": "1.0.0-beta.152",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.152",
30
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.152",
31
+ "@aglyn/shared-ui-next": "1.0.0-beta.152",
32
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.152",
33
+ "@aglyn/shared-util-http": "1.0.0-beta.152",
34
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.152",
35
+ "@aglyn/shared-util-tools": "1.0.0-beta.152",
36
+ "@aglyn/tenant-data-admin": "1.0.0-beta.152",
37
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.152",
38
+ "@aglyn/tenant-runtime": "1.0.0-beta.152",
39
39
  "@swc/helpers": "0.5.23"
40
40
  },
41
41
  "peerDependencies": {
@@ -135,7 +135,7 @@ const EMPTY = {
135
135
  /*#__PURE__*/ _jsx(Typography, {
136
136
  variant: "body2",
137
137
  color: "text.secondary",
138
- children: 'Every email a sequence sends ends with a footer naming your organization, its postal ' + 'address, that the email is a business solicitation, and how to stop more of them. The law ' + 'requires these on commercial email, so every email gets the footer automatically.'
138
+ children: 'Every email a sequence sends ends with a footer naming your organization, its postal ' + 'address, that the email is a sales email, and how to stop more of them. The law ' + 'requires these on commercial email, so every email gets the footer automatically.'
139
139
  }),
140
140
  /*#__PURE__*/ _jsx(TextField, {
141
141
  label: "Legal name",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/compliance-section.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 } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Autocomplete,\n Button,\n Chip,\n Paper,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport { composeOutreachFooter } from '../engine/compose'\nimport {\n outreachComplianceSettingsEqual,\n outreachCountryLabel,\n outreachCountryOptions,\n OUTREACH_BRAND_NAME_MAX,\n OUTREACH_LEGAL_NAME_MAX,\n OUTREACH_POSTAL_ADDRESS_MAX,\n validateOutreachComplianceSettings,\n type OutreachComplianceField,\n type OutreachCountryOption,\n} from '../model/compliance-settings'\nimport type { OutreachComplianceSettings } from '../model/outreach.types'\nimport { OutreachLoading, OutreachLoadProblem } from './outreach-ui'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport { useOutreachComplianceSettings } from './use-outreach-settings'\n\nexport interface OutreachComplianceSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n}\n\n/** What CAN-SPAM accepts as the postal address, as the field says it. */\nexport const OUTREACH_POSTAL_ADDRESS_HELP =\n 'A street address, a post office box registered with the US Postal Service, or a private mailbox ' +\n 'registered with the US Postal Service. Every email prints it in its footer.'\n\nconst EMPTY: OutreachComplianceSettings = {\n legalName: '',\n brandName: '',\n postalAddress: '',\n allowedCountries: ['US'],\n}\n\n/**\n * Sequences → Compliance (AGL-2980): who every email says sent it, and the\n * countries a sequence may send to at all.\n *\n * The footer is previewed from what is typed, by the engine's own\n * `composeOutreachFooter`, so the page shows the lines every email will end\n * with — or, while the legal name or the address is empty, the sentence\n * that explains why nothing can be sent and no sequence activated.\n */\nexport function OutreachComplianceSection(\n props: OutreachComplianceSectionProps,\n) {\n const { orgId } = props\n const api = useOutreachApi(orgId)\n const loaded = useOutreachComplianceSettings(api, orgId)\n const { enqueueSnackbar } = useSnackbar()\n const [form, setForm] = useState<OutreachComplianceSettings>(EMPTY)\n const [saving, setSaving] = useState(false)\n const [serverIssues, setServerIssues] = useState<\n Partial<Record<OutreachComplianceField, string>>\n >({})\n const countries = useMemo(() => outreachCountryOptions(), [])\n\n const stored = loaded.settings\n useEffect(() => {\n if (stored) setForm({ ...stored })\n }, [stored])\n\n const { settings: normalized, issues } =\n validateOutreachComplianceSettings(form)\n const fieldIssue = (field: OutreachComplianceField) =>\n serverIssues[field] ??\n issues.find((issue) => issue.field === field)?.message\n const dirty = stored\n ? !outreachComplianceSettingsEqual(stored, normalized)\n : false\n const footer = composeOutreachFooter(normalized)\n const set = (field: keyof OutreachComplianceSettings) => (value: string) => {\n setServerIssues({})\n setForm((previous) => ({ ...previous, [field]: value }))\n }\n\n const save = async () => {\n setSaving(true)\n try {\n const answer = await api.saveSettings(normalized)\n setServerIssues({})\n setForm({ ...answer.settings })\n loaded.reload()\n enqueueSnackbar(\n answer.changed ? 'Compliance settings saved.' : 'Nothing changed.',\n {\n variant: answer.changed ? 'success' : 'info',\n },\n )\n } catch (error) {\n if (error instanceof OutreachRouteError && error.issues.length) {\n setServerIssues(\n Object.fromEntries(\n error.issues\n .filter(\n (\n issue,\n ): issue is {\n field: OutreachComplianceField\n message: string\n } => 'field' in issue,\n )\n .map((issue) => [issue.field, issue.message]),\n ),\n )\n }\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setSaving(false)\n }\n }\n\n if (loaded.status === 'loading' || (!orgId && !stored)) {\n return <OutreachLoading label=\"Loading compliance settings…\" />\n }\n if (loaded.status === 'error' || loaded.status === 'refused') {\n return (\n <OutreachLoadProblem\n status={loaded.status}\n what=\"compliance settings\"\n message={loaded.message}\n onRetry={loaded.reload}\n />\n )\n }\n\n const selectedCountries = form.allowedCountries.map(\n (code) =>\n countries.find((option) => option.code === code) ?? {\n code,\n name: outreachCountryLabel(code),\n },\n )\n\n return (\n <Stack spacing={2}>\n <CardDisplay\n header=\"Sender identity\"\n help={pluginDocsHelp('sequences', { anchor: '#compliance-settings' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Every email a sequence sends ends with a footer naming your organization, its postal ' +\n 'address, that the email is a business solicitation, and how to stop more of them. The law ' +\n 'requires these on commercial email, so every email gets the footer automatically.'}\n </Typography>\n <TextField\n label=\"Legal name\"\n value={form.legalName}\n onChange={(event) => set('legalName')(event.target.value)}\n error={Boolean(fieldIssue('legalName'))}\n helperText={\n fieldIssue('legalName') ??\n 'Your organization’s legal name, as the footer prints it.'\n }\n slotProps={{\n htmlInput: { maxLength: OUTREACH_LEGAL_NAME_MAX + 20 },\n }}\n fullWidth\n />\n <TextField\n label=\"Brand name\"\n value={form.brandName}\n onChange={(event) => set('brandName')(event.target.value)}\n error={Boolean(fieldIssue('brandName'))}\n helperText={\n fieldIssue('brandName') ??\n 'The name the solicitation sentence uses. Leave empty to use the legal name.'\n }\n slotProps={{\n htmlInput: { maxLength: OUTREACH_BRAND_NAME_MAX + 20 },\n }}\n fullWidth\n />\n <TextField\n label=\"Postal address\"\n value={form.postalAddress}\n onChange={(event) => set('postalAddress')(event.target.value)}\n error={Boolean(fieldIssue('postalAddress'))}\n helperText={\n fieldIssue('postalAddress') ?? OUTREACH_POSTAL_ADDRESS_HELP\n }\n slotProps={{\n htmlInput: { maxLength: OUTREACH_POSTAL_ADDRESS_MAX + 40 },\n }}\n multiline\n minRows={3}\n fullWidth\n />\n {footer.footer ? (\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">Every email ends with</Typography>\n <Paper variant=\"outlined\" sx={{ p: 1.5 }}>\n <Typography\n variant=\"body2\"\n sx={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}\n data-testid=\"outreach-footer-preview\"\n >\n {footer.footer}\n </Typography>\n </Paper>\n </Stack>\n ) : (\n <Alert severity=\"warning\">\n {'No sequence can be activated, and no email sent, until you add your organization’s legal ' +\n 'name and postal address.'}\n </Alert>\n )}\n </Stack>\n </CardDisplay>\n\n <CardDisplay\n header=\"Allowed countries\"\n help={pluginDocsHelp('sequences', { anchor: '#allowed-countries' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Sequences may send only to people in these countries, and every sequence only to the ones ' +\n 'it and this list both allow. The United States is the default: Canada, the United Kingdom and most ' +\n 'of the European Union require a consent basis that an email to someone who never contacted ' +\n 'you does not have, so a cold email never goes outside the United States whatever this list ' +\n 'says. Add another country only for people who came to you first.'}\n </Typography>\n <Autocomplete<OutreachCountryOption, true>\n multiple\n options={countries}\n value={selectedCountries}\n getOptionLabel={(option) => option.name}\n isOptionEqualToValue={(option, value) => option.code === value.code}\n onChange={(_event, next) => {\n setServerIssues({})\n setForm((previous) => ({\n ...previous,\n allowedCountries: next.map((option) => option.code),\n }))\n }}\n renderValue={(value, getItemProps) =>\n value.map((option, index) => {\n const { key, ...item } = getItemProps({ index })\n return (\n <Chip key={key} size=\"small\" label={option.name} {...item} />\n )\n })\n }\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"Countries\"\n error={Boolean(fieldIssue('allowedCountries'))}\n helperText={fieldIssue('allowedCountries')}\n />\n )}\n />\n </Stack>\n </CardDisplay>\n\n <Stack\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ justifyContent: 'flex-end' }}\n >\n <Button\n variant=\"text\"\n disabled={!dirty || saving}\n onClick={() => {\n setServerIssues({})\n if (stored) setForm({ ...stored })\n }}\n >\n Discard changes\n </Button>\n <Button\n variant=\"contained\"\n disabled={!dirty || saving || issues.length > 0}\n onClick={() => void save()}\n >\n {saving ? 'Saving…' : 'Save'}\n </Button>\n </Stack>\n </Stack>\n )\n}\nOutreachComplianceSection.displayName = 'OutreachComplianceSection'\n\nexport default OutreachComplianceSection\n"],"names":["pluginDocsHelp","CardDisplay","useSnackbar","Alert","Autocomplete","Button","Chip","Paper","Stack","TextField","Typography","useEffect","useMemo","useState","composeOutreachFooter","outreachComplianceSettingsEqual","outreachCountryLabel","outreachCountryOptions","OUTREACH_BRAND_NAME_MAX","OUTREACH_LEGAL_NAME_MAX","OUTREACH_POSTAL_ADDRESS_MAX","validateOutreachComplianceSettings","OutreachLoading","OutreachLoadProblem","OutreachRouteError","useOutreachApi","useOutreachComplianceSettings","OUTREACH_POSTAL_ADDRESS_HELP","EMPTY","legalName","brandName","postalAddress","allowedCountries","OutreachComplianceSection","props","fieldIssue","orgId","api","loaded","enqueueSnackbar","form","setForm","saving","setSaving","serverIssues","setServerIssues","countries","stored","settings","normalized","issues","field","find","issue","message","dirty","footer","set","value","previous","save","answer","saveSettings","reload","changed","variant","error","length","Object","fromEntries","filter","map","allowDuplicate","status","label","what","onRetry","selectedCountries","code","option","name","spacing","header","help","anchor","contentGutterX","contentGutterY","color","onChange","event","target","Boolean","helperText","slotProps","htmlInput","maxLength","fullWidth","multiline","minRows","sx","p","whiteSpace","wordBreak","data-testid","severity","multiple","options","getOptionLabel","isOptionEqualToValue","_event","next","renderValue","getItemProps","index","key","item","size","renderInput","params","direction","xs","sm","justifyContent","disabled","onClick","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/compliance-section.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 } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Autocomplete,\n Button,\n Chip,\n Paper,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport { composeOutreachFooter } from '../engine/compose'\nimport {\n outreachComplianceSettingsEqual,\n outreachCountryLabel,\n outreachCountryOptions,\n OUTREACH_BRAND_NAME_MAX,\n OUTREACH_LEGAL_NAME_MAX,\n OUTREACH_POSTAL_ADDRESS_MAX,\n validateOutreachComplianceSettings,\n type OutreachComplianceField,\n type OutreachCountryOption,\n} from '../model/compliance-settings'\nimport type { OutreachComplianceSettings } from '../model/outreach.types'\nimport { OutreachLoading, OutreachLoadProblem } from './outreach-ui'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport { useOutreachComplianceSettings } from './use-outreach-settings'\n\nexport interface OutreachComplianceSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n}\n\n/** What CAN-SPAM accepts as the postal address, as the field says it. */\nexport const OUTREACH_POSTAL_ADDRESS_HELP =\n 'A street address, a post office box registered with the US Postal Service, or a private mailbox ' +\n 'registered with the US Postal Service. Every email prints it in its footer.'\n\nconst EMPTY: OutreachComplianceSettings = {\n legalName: '',\n brandName: '',\n postalAddress: '',\n allowedCountries: ['US'],\n}\n\n/**\n * Sequences → Compliance (AGL-2980): who every email says sent it, and the\n * countries a sequence may send to at all.\n *\n * The footer is previewed from what is typed, by the engine's own\n * `composeOutreachFooter`, so the page shows the lines every email will end\n * with — or, while the legal name or the address is empty, the sentence\n * that explains why nothing can be sent and no sequence activated.\n */\nexport function OutreachComplianceSection(\n props: OutreachComplianceSectionProps,\n) {\n const { orgId } = props\n const api = useOutreachApi(orgId)\n const loaded = useOutreachComplianceSettings(api, orgId)\n const { enqueueSnackbar } = useSnackbar()\n const [form, setForm] = useState<OutreachComplianceSettings>(EMPTY)\n const [saving, setSaving] = useState(false)\n const [serverIssues, setServerIssues] = useState<\n Partial<Record<OutreachComplianceField, string>>\n >({})\n const countries = useMemo(() => outreachCountryOptions(), [])\n\n const stored = loaded.settings\n useEffect(() => {\n if (stored) setForm({ ...stored })\n }, [stored])\n\n const { settings: normalized, issues } =\n validateOutreachComplianceSettings(form)\n const fieldIssue = (field: OutreachComplianceField) =>\n serverIssues[field] ??\n issues.find((issue) => issue.field === field)?.message\n const dirty = stored\n ? !outreachComplianceSettingsEqual(stored, normalized)\n : false\n const footer = composeOutreachFooter(normalized)\n const set = (field: keyof OutreachComplianceSettings) => (value: string) => {\n setServerIssues({})\n setForm((previous) => ({ ...previous, [field]: value }))\n }\n\n const save = async () => {\n setSaving(true)\n try {\n const answer = await api.saveSettings(normalized)\n setServerIssues({})\n setForm({ ...answer.settings })\n loaded.reload()\n enqueueSnackbar(\n answer.changed ? 'Compliance settings saved.' : 'Nothing changed.',\n {\n variant: answer.changed ? 'success' : 'info',\n },\n )\n } catch (error) {\n if (error instanceof OutreachRouteError && error.issues.length) {\n setServerIssues(\n Object.fromEntries(\n error.issues\n .filter(\n (\n issue,\n ): issue is {\n field: OutreachComplianceField\n message: string\n } => 'field' in issue,\n )\n .map((issue) => [issue.field, issue.message]),\n ),\n )\n }\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setSaving(false)\n }\n }\n\n if (loaded.status === 'loading' || (!orgId && !stored)) {\n return <OutreachLoading label=\"Loading compliance settings…\" />\n }\n if (loaded.status === 'error' || loaded.status === 'refused') {\n return (\n <OutreachLoadProblem\n status={loaded.status}\n what=\"compliance settings\"\n message={loaded.message}\n onRetry={loaded.reload}\n />\n )\n }\n\n const selectedCountries = form.allowedCountries.map(\n (code) =>\n countries.find((option) => option.code === code) ?? {\n code,\n name: outreachCountryLabel(code),\n },\n )\n\n return (\n <Stack spacing={2}>\n <CardDisplay\n header=\"Sender identity\"\n help={pluginDocsHelp('sequences', { anchor: '#compliance-settings' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Every email a sequence sends ends with a footer naming your organization, its postal ' +\n 'address, that the email is a sales email, and how to stop more of them. The law ' +\n 'requires these on commercial email, so every email gets the footer automatically.'}\n </Typography>\n <TextField\n label=\"Legal name\"\n value={form.legalName}\n onChange={(event) => set('legalName')(event.target.value)}\n error={Boolean(fieldIssue('legalName'))}\n helperText={\n fieldIssue('legalName') ??\n 'Your organization’s legal name, as the footer prints it.'\n }\n slotProps={{\n htmlInput: { maxLength: OUTREACH_LEGAL_NAME_MAX + 20 },\n }}\n fullWidth\n />\n <TextField\n label=\"Brand name\"\n value={form.brandName}\n onChange={(event) => set('brandName')(event.target.value)}\n error={Boolean(fieldIssue('brandName'))}\n helperText={\n fieldIssue('brandName') ??\n 'The name the solicitation sentence uses. Leave empty to use the legal name.'\n }\n slotProps={{\n htmlInput: { maxLength: OUTREACH_BRAND_NAME_MAX + 20 },\n }}\n fullWidth\n />\n <TextField\n label=\"Postal address\"\n value={form.postalAddress}\n onChange={(event) => set('postalAddress')(event.target.value)}\n error={Boolean(fieldIssue('postalAddress'))}\n helperText={\n fieldIssue('postalAddress') ?? OUTREACH_POSTAL_ADDRESS_HELP\n }\n slotProps={{\n htmlInput: { maxLength: OUTREACH_POSTAL_ADDRESS_MAX + 40 },\n }}\n multiline\n minRows={3}\n fullWidth\n />\n {footer.footer ? (\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">Every email ends with</Typography>\n <Paper variant=\"outlined\" sx={{ p: 1.5 }}>\n <Typography\n variant=\"body2\"\n sx={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}\n data-testid=\"outreach-footer-preview\"\n >\n {footer.footer}\n </Typography>\n </Paper>\n </Stack>\n ) : (\n <Alert severity=\"warning\">\n {'No sequence can be activated, and no email sent, until you add your organization’s legal ' +\n 'name and postal address.'}\n </Alert>\n )}\n </Stack>\n </CardDisplay>\n\n <CardDisplay\n header=\"Allowed countries\"\n help={pluginDocsHelp('sequences', { anchor: '#allowed-countries' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Sequences may send only to people in these countries, and every sequence only to the ones ' +\n 'it and this list both allow. The United States is the default: Canada, the United Kingdom and most ' +\n 'of the European Union require a consent basis that an email to someone who never contacted ' +\n 'you does not have, so a cold email never goes outside the United States whatever this list ' +\n 'says. Add another country only for people who came to you first.'}\n </Typography>\n <Autocomplete<OutreachCountryOption, true>\n multiple\n options={countries}\n value={selectedCountries}\n getOptionLabel={(option) => option.name}\n isOptionEqualToValue={(option, value) => option.code === value.code}\n onChange={(_event, next) => {\n setServerIssues({})\n setForm((previous) => ({\n ...previous,\n allowedCountries: next.map((option) => option.code),\n }))\n }}\n renderValue={(value, getItemProps) =>\n value.map((option, index) => {\n const { key, ...item } = getItemProps({ index })\n return (\n <Chip key={key} size=\"small\" label={option.name} {...item} />\n )\n })\n }\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"Countries\"\n error={Boolean(fieldIssue('allowedCountries'))}\n helperText={fieldIssue('allowedCountries')}\n />\n )}\n />\n </Stack>\n </CardDisplay>\n\n <Stack\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ justifyContent: 'flex-end' }}\n >\n <Button\n variant=\"text\"\n disabled={!dirty || saving}\n onClick={() => {\n setServerIssues({})\n if (stored) setForm({ ...stored })\n }}\n >\n Discard changes\n </Button>\n <Button\n variant=\"contained\"\n disabled={!dirty || saving || issues.length > 0}\n onClick={() => void save()}\n >\n {saving ? 'Saving…' : 'Save'}\n </Button>\n </Stack>\n </Stack>\n )\n}\nOutreachComplianceSection.displayName = 'OutreachComplianceSection'\n\nexport default OutreachComplianceSection\n"],"names":["pluginDocsHelp","CardDisplay","useSnackbar","Alert","Autocomplete","Button","Chip","Paper","Stack","TextField","Typography","useEffect","useMemo","useState","composeOutreachFooter","outreachComplianceSettingsEqual","outreachCountryLabel","outreachCountryOptions","OUTREACH_BRAND_NAME_MAX","OUTREACH_LEGAL_NAME_MAX","OUTREACH_POSTAL_ADDRESS_MAX","validateOutreachComplianceSettings","OutreachLoading","OutreachLoadProblem","OutreachRouteError","useOutreachApi","useOutreachComplianceSettings","OUTREACH_POSTAL_ADDRESS_HELP","EMPTY","legalName","brandName","postalAddress","allowedCountries","OutreachComplianceSection","props","fieldIssue","orgId","api","loaded","enqueueSnackbar","form","setForm","saving","setSaving","serverIssues","setServerIssues","countries","stored","settings","normalized","issues","field","find","issue","message","dirty","footer","set","value","previous","save","answer","saveSettings","reload","changed","variant","error","length","Object","fromEntries","filter","map","allowDuplicate","status","label","what","onRetry","selectedCountries","code","option","name","spacing","header","help","anchor","contentGutterX","contentGutterY","color","onChange","event","target","Boolean","helperText","slotProps","htmlInput","maxLength","fullWidth","multiline","minRows","sx","p","whiteSpace","wordBreak","data-testid","severity","multiple","options","getOptionLabel","isOptionEqualToValue","_event","next","renderValue","getItemProps","index","key","item","size","renderInput","params","direction","xs","sm","justifyContent","disabled","onClick","displayName"],"mappings":"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"}
@@ -27,6 +27,8 @@ export declare const MAILBOX_ACTION_LABELS: {
27
27
  readonly pause: "Pause";
28
28
  readonly resume: "Resume";
29
29
  readonly test: "Send a test to myself";
30
+ readonly testElsewhere: "Send a test";
31
+ readonly testAddress: "Test address";
30
32
  readonly disconnect: "Disconnect";
31
33
  readonly reconnect: "Reconnect";
32
34
  };
@@ -51,6 +51,8 @@ import { formatMinuteOfDay, isValidTimezone, OUTREACH_DEFAULT_WINDOW, OUTREACH_M
51
51
  pause: 'Pause',
52
52
  resume: 'Resume',
53
53
  test: 'Send a test to myself',
54
+ testElsewhere: 'Send a test',
55
+ testAddress: 'Test address',
54
56
  disconnect: 'Disconnect',
55
57
  reconnect: 'Reconnect'
56
58
  };
@@ -112,6 +114,9 @@ const draftOf = (mailbox)=>{
112
114
  const [saving, setSaving] = useState(false);
113
115
  const [saveError, setSaveError] = useState(null);
114
116
  const [busy, setBusy] = useState(null);
117
+ // Where a test goes when not to the member themself (AGL-3228): a test to
118
+ // one's own address never leaves Google and shows no authentication result.
119
+ const [testAddress, setTestAddress] = useState('');
115
120
  // A save here, a change made from another tab, or a reconnect rewrites the
116
121
  // stored settings; the form follows them, unless the member has unsaved
117
122
  // edits of their own, which are never thrown away.
@@ -188,8 +193,9 @@ const draftOf = (mailbox)=>{
188
193
  const sendTest = async ()=>{
189
194
  setBusy('test');
190
195
  try {
191
- const sent = await api.sendTest(mailbox.id);
192
- enqueueSnackbar(`Test sent to ${sent.sentTo}. Check your inbox.`, {
196
+ const to = testAddress.trim();
197
+ const sent = await api.sendTest(mailbox.id, to || undefined);
198
+ enqueueSnackbar(to ? `Test sent to ${sent.sentTo}. Its original source shows whether the sender authenticated.` : `Test sent to ${sent.sentTo}. Check your inbox.`, {
193
199
  variant: 'success'
194
200
  });
195
201
  } catch (error) {
@@ -479,6 +485,16 @@ const draftOf = (mailbox)=>{
479
485
  color: "text.secondary",
480
486
  children: `Sends as ${sendAsLabel(stored.sendAs)}, at most ${stored.dailyCap} a day, ` + `${stored.window.days.map((day)=>OUTREACH_WEEKDAY_LABELS[day]).join(', ')} ` + `${formatMinuteOfDay(stored.window.startMinute)}–${formatMinuteOfDay(stored.window.endMinute)} ` + `(${stored.timezone}).`
481
487
  }),
488
+ isMine && !needsReconnect ? /*#__PURE__*/ _jsx(TextField, {
489
+ label: MAILBOX_ACTION_LABELS.testAddress,
490
+ value: testAddress,
491
+ onChange: (event)=>setTestAddress(event.target.value),
492
+ disabled: busy !== null,
493
+ size: "small",
494
+ type: "email",
495
+ helperText: 'Leave empty to send the test to yourself. A test to an outside mailbox you can ' + 'read is the only one whose original source shows the sender authentication results.',
496
+ fullWidth: true
497
+ }) : null,
482
498
  canManage || isMine ? /*#__PURE__*/ _jsxs(Stack, {
483
499
  direction: "row",
484
500
  spacing: 1,
@@ -497,7 +513,7 @@ const draftOf = (mailbox)=>{
497
513
  variant: "outlined",
498
514
  disabled: busy !== null,
499
515
  onClick: ()=>void sendTest(),
500
- children: MAILBOX_ACTION_LABELS.test
516
+ children: testAddress.trim() ? MAILBOX_ACTION_LABELS.testElsewhere : MAILBOX_ACTION_LABELS.test
501
517
  }) : null,
502
518
  canManage ? /*#__PURE__*/ _jsx(Button, {
503
519
  color: "error",
@@ -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 FormHelperText,\n MenuItem,\n Stack,\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 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 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}\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})\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\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 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 rampStartedAtMs: mailbox.rampStartedAtMs,\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 })\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 sent = await api.sendTest(mailbox.id)\n enqueueSnackbar(`Test sent to ${sent.sentTo}. Check your inbox.`, { 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 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 </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 : `${RAMP_SENTENCE} Today’s limit: ${todayLimit}.`\n }\n />\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 {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 {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","FormHelperText","MenuItem","Stack","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","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","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","MailboxCard","props","isMine","canManage","api","onReconnect","reconnecting","nowMs","Date","now","enqueueSnackbar","confirm","draft","setDraft","saving","setSaving","saveError","setSaveError","busy","setBusy","stored","storedKey","JSON","stringify","seededFrom","dirty","previous","health","needsReconnect","status","capCheck","Number","NaN","windowCheck","nameCheck","timezoneValid","invalid","todayLimit","rampStartedAtMs","saveSettings","mailboxId","id","variant","error","togglePaused","setPaused","allowDuplicate","sendTest","sent","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","undefined","role","hasSent","direction","sx","mt","bounces","replies","value","aria-label","component","onSubmit","event","preventDefault","select","onChange","target","helperText","type","slotProps","htmlInput","min","max","gutterBottom","days","_event","sort","a","b","xs","sm","startMinute","slice","minute","endMinute","disableClearable","options","minWidth","renderInput","params","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 {\n Alert,\n Autocomplete,\n Box,\n Button,\n Chip,\n FormHelperText,\n MenuItem,\n Stack,\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 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 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}\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})\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 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 rampStartedAtMs: mailbox.rampStartedAtMs,\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 })\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 </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 : `${RAMP_SENTENCE} Today’s limit: ${todayLimit}.`\n }\n />\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","FormHelperText","MenuItem","Stack","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","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","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","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","needsReconnect","status","capCheck","Number","NaN","windowCheck","nameCheck","timezoneValid","invalid","todayLimit","rampStartedAtMs","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","max","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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"}
@@ -17,7 +17,8 @@ export interface OutreachMailboxApi {
17
17
  }): Promise<OutreachConnectCompleteResponse>;
18
18
  saveSettings(input: Omit<OutreachMailboxSettingsRequest, 'orgId'>): Promise<OutreachMailboxResponse>;
19
19
  setPaused(mailboxId: string, paused: boolean): Promise<OutreachMailboxResponse>;
20
- sendTest(mailboxId: string): Promise<OutreachMailboxTestResponse>;
20
+ /** A test from the mailbox — to `to`, or to the account's own address. */
21
+ sendTest(mailboxId: string, to?: string): Promise<OutreachMailboxTestResponse>;
21
22
  disconnect(mailboxId: string): Promise<OutreachMailboxDisconnectResponse>;
22
23
  }
23
24
  /**
@@ -81,11 +81,13 @@ import { OUTREACH_API_ROUTES } from "../constants/api-routes.js";
81
81
  paused
82
82
  }
83
83
  }),
84
- sendTest: (mailboxId)=>call(OUTREACH_API_ROUTES.mailboxesTest, {
84
+ sendTest: (mailboxId, to)=>call(OUTREACH_API_ROUTES.mailboxesTest, {
85
85
  method: 'POST',
86
- body: {
86
+ body: _extends({
87
87
  mailboxId
88
- }
88
+ }, to ? {
89
+ to
90
+ } : {})
89
91
  }),
90
92
  disconnect: (mailboxId)=>call(OUTREACH_API_ROUTES.mailboxesDisconnect, {
91
93
  method: 'POST',
@@ -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 sendTest(mailboxId: 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) =>\n call<OutreachMailboxTestResponse>(OUTREACH_API_ROUTES.mailboxesTest, {\n method: 'POST',\n body: { mailboxId },\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","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;AAgBA;;;;;;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,YACT5B,KAAkCX,oBAAoB2C,aAAa,EAAE;oBACnE5B,QAAQ;oBACRI,MAAM;wBAAEoB;oBAAU;gBACpB;YACFK,YAAY,CAACL,YACX5B,KAAwCX,oBAAoB6C,mBAAmB,EAAE;oBAC/E9B,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 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"}
@@ -17,8 +17,13 @@
17
17
  import { type CrmMergeContext } from '@aglyn/aglyn/app-utils/crm-email-templates';
18
18
  import type { OutreachEnrollment, OutreachOrgSettings, OutreachSequence } from '../model/outreach.types';
19
19
  /** The way out every email offers, word for word. */
20
- export declare const OUTREACH_OPT_OUT_LINE = "Not relevant? Reply \"no\" and I won't email again.";
21
- /** The sentence that says what the email is. */
20
+ export declare const OUTREACH_OPT_OUT_LINE = "Not interested? Reply \"no\" and I won't email again.";
21
+ /**
22
+ * The sentence that says what the email is. "Sales email", not "business
23
+ * solicitation": an inbox-placement test on 2026-09-19 named the latter
24
+ * phrase as a spam trigger, and the law asks that the email be identifiable
25
+ * as a solicitation, not for any particular words (AGL-3228).
26
+ */
22
27
  export declare function outreachSolicitationStatement(senderName: string): string;
23
28
  /**
24
29
  * The message the transport sends. Header values are single lines already;
@@ -25,8 +25,9 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
25
25
  * ## The footer is not optional
26
26
  *
27
27
  * Every email ends with the organization's legal name, its postal address,
28
- * a sentence saying the email is a business solicitation, and the way out —
29
- * reply "no". Those are what CAN-SPAM requires of a commercial email, and
28
+ * a sentence saying the email is a sales email, and the way out — reply
29
+ * "no". Those are what CAN-SPAM requires of a commercial email (16 CFR 316:
30
+ * identifiable as a solicitation, no fixed phrase), and
30
31
  * the composer appends them itself, after the merge, as literal text: no
31
32
  * template can leave them off, and no merge field can rewrite them. With no
32
33
  * postal address or no legal name, it refuses to compose at all.
@@ -54,9 +55,14 @@ import { resolveCrmMergeFields } from "@aglyn/aglyn/app-utils/crm-email-template
54
55
  import { crmThreadSubject } from "@aglyn/aglyn/app-utils/crm-inbound";
55
56
  import { normalizeOutreachPersonalLine } from "./gates.js";
56
57
  import { isInThreadEmailStep, OUTREACH_PERSONAL_LINE_FIELD, outreachSubjectHasReplyPrefix } from "./sequence-validation.js";
57
- /** The way out every email offers, word for word. */ export const OUTREACH_OPT_OUT_LINE = 'Not relevant? Reply "no" and I won\'t email again.';
58
- /** The sentence that says what the email is. */ export function outreachSolicitationStatement(senderName) {
59
- return `This is a business solicitation from ${senderName}.`;
58
+ /** The way out every email offers, word for word. */ export const OUTREACH_OPT_OUT_LINE = 'Not interested? Reply "no" and I won\'t email again.';
59
+ /**
60
+ * The sentence that says what the email is. "Sales email", not "business
61
+ * solicitation": an inbox-placement test on 2026-09-19 named the latter
62
+ * phrase as a spam trigger, and the law asks that the email be identifiable
63
+ * as a solicitation, not for any particular words (AGL-3228).
64
+ */ export function outreachSolicitationStatement(senderName) {
65
+ return `This is a sales email from ${senderName}.`;
60
66
  }
61
67
  const oneLine = (value)=>typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
62
68
  /**