@aglyn/plugins-marketing 1.0.0-beta.205 → 1.0.0-beta.207

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.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ // The seam from its own module, not the plugin-manager barrel: boot needs the
17
+ // registry and nothing else, and the barrel reaches the client contexts.
18
+ import { registerOperatorAlerts } from "@aglyn/aglyn/plugin-manager/operator-alerts";
19
+ import { BUNDLE_ID } from "./constants/bundle-common.js";
20
+ import { MARKETING_OPERATOR_ALERTS } from "./constants/operator-alerts.js";
21
+ /**
22
+ * The marketing plugin's server declarations: the light registrations core
23
+ * reads at boot, before any surface loads — its operator alerts (AGL-3377),
24
+ * so Staff → Operator alerts lists them before the first one is raised.
25
+ */ export function registerMarketingServerDeclarations() {
26
+ registerOperatorAlerts(MARKETING_OPERATOR_ALERTS, {
27
+ pluginId: BUNDLE_ID
28
+ });
29
+ }
30
+
31
+ //# sourceMappingURL=declarations.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/plugins/marketing/src/lib/declarations.server.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// The seam from its own module, not the plugin-manager barrel: boot needs the\n// registry and nothing else, and the barrel reaches the client contexts.\nimport { registerOperatorAlerts } from '@aglyn/aglyn/plugin-manager/operator-alerts'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { MARKETING_OPERATOR_ALERTS } from './constants/operator-alerts'\n\n/**\n * The marketing plugin's server declarations: the light registrations core\n * reads at boot, before any surface loads — its operator alerts (AGL-3377),\n * so Staff → Operator alerts lists them before the first one is raised.\n */\nexport function registerMarketingServerDeclarations(): void {\n registerOperatorAlerts(MARKETING_OPERATOR_ALERTS, { pluginId: BUNDLE_ID })\n}\n"],"names":["registerOperatorAlerts","BUNDLE_ID","MARKETING_OPERATOR_ALERTS","registerMarketingServerDeclarations","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,8EAA8E;AAC9E,yEAAyE;AACzE,SAASA,sBAAsB,QAAQ,8CAA6C;AACpF,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,yBAAyB,QAAQ,iCAA6B;AAEvE;;;;CAIC,GACD,OAAO,SAASC;IACdH,uBAAuBE,2BAA2B;QAAEE,UAAUH;IAAU;AAC1E"}
package/src/lib/plugin.js CHANGED
@@ -21,6 +21,7 @@ import { CAMPAIGN_DESIGN_CREATE_ZONE, CAMPAIGN_DESIGN_PREVIEW_ZONE, CAMPAIGN_SEN
21
21
  import { EXPERIMENT_RESULT_ZONE, EXPERIMENT_VARIANTS_ZONE } from "./components/experiment-zones.js";
22
22
  import { MARKETING_CONSOLE_SECTIONS, MARKETING_ORG_CONSOLE_SECTIONS } from "./components/marketing-console-sections.js";
23
23
  import { BUNDLE_ID } from "./constants/bundle-common.js";
24
+ import { StaffOrgEmailCard } from "./components/staff-org-email-card.component.js";
24
25
  import { registerMarketingRecordRoutes } from "./model/marketing-record-routes.js";
25
26
  /** Code-split: the Marketing console page only loads when opened. */ const MarketingConsolePage = lazy(()=>import("./components/marketing-console-page.js"));
26
27
  /** Dashboard glance card, loaded only where the shell renders the slot. */ const CampaignGlanceCard = lazy(()=>import("./components/campaign-glance-card.component.js"));
@@ -141,6 +142,14 @@ import { registerMarketingRecordRoutes } from "./model/marketing-record-routes.j
141
142
  widgetId: 'marketing-inbox-record-attribution',
142
143
  title: 'Campaign attribution',
143
144
  Component: RecordAttributionWidget
145
+ },
146
+ // The staff organization page's sends and campaigns (AGL-3380), read
147
+ // only: staff see what the organization sent without joining it.
148
+ {
149
+ slot: Aglyn.CONSOLE_WIDGET_SLOTS.adminOrgDetail,
150
+ widgetId: 'marketing-staff-org-email',
151
+ title: 'Email campaigns',
152
+ Component: StaffOrgEmailCard
144
153
  }
145
154
  ],
146
155
  navItems: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/marketing/src/lib/plugin.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 * as Aglyn from '@aglyn/aglyn'\nimport {\n registerPluginZone,\n type PluginZone,\n} from '@aglyn/aglyn/plugin-manager/plugin-zones'\nimport { mdiBullhornOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport {\n CAMPAIGN_DESIGN_CREATE_ZONE,\n CAMPAIGN_DESIGN_PREVIEW_ZONE,\n CAMPAIGN_SENDER_EDITOR_ZONE,\n CAMPAIGN_TOPIC_OPTIONS_ZONE,\n CAMPAIGN_TOPIC_SELECT_ZONE,\n} from './components/campaign-email-zones'\nimport {\n EXPERIMENT_RESULT_ZONE,\n EXPERIMENT_VARIANTS_ZONE,\n} from './components/experiment-zones'\nimport {\n MARKETING_CONSOLE_SECTIONS,\n MARKETING_ORG_CONSOLE_SECTIONS,\n} from './components/marketing-console-sections'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { registerMarketingRecordRoutes } from './model/marketing-record-routes'\n\n/** Code-split: the Marketing console page only loads when opened. */\nconst MarketingConsolePage = lazy(\n () => import('./components/marketing-console-page'),\n)\n\n/** Dashboard glance card, loaded only where the shell renders the slot. */\nconst CampaignGlanceCard = lazy(\n () => import('./components/campaign-glance-card.component'),\n)\n\n/**\n * The campaigns drawn in the Inbox's Campaigns section: one site's, or every\n * site's on the organization's Inbox.\n */\nconst InboxCampaignsWidget = lazy(\n () => import('./components/inbox-campaigns-widget'),\n)\n\n/** The Emails page's Messages section: the list, one message, its composer. */\nconst EmailMessagesWidget = lazy(\n () => import('./components/email-messages-widget'),\n)\n\n/** Who received the sends built from one template, on the template's page. */\nconst EmailRecipientsCard = lazy(\n () => import('./components/email-recipients-card'),\n)\n\n/** Loaded where a record page draws one of the attribution zones. */\nconst RecordAttributionWidget = lazy(\n () => import('./components/record-attribution-widget'),\n)\n\n/**\n * Marketing feature plugin (AGL-395). Console-only — overlays and popups\n * render on published sites through the tenant runtime, not a canvas\n * element of their own, so there is no UI bundle. The console half declares\n * the Marketing nav + page through the ConsoleExtension registry (always-on;\n * the surface itself is not release-flagged — its overlays/A-B cards run\n * their own per-plan checks off the passed `org`). The popup image picker\n * uses the shell's media browser via `useMediaPicker`.\n */\nexport function registerMarketingConsole(): void {\n /*\n * The two positions the A/B testing card hosts (AGL-2914), declared before\n * the extension that draws the card. An id another plugin has already taken\n * is refused, naming both. What each zone hands a widget is carried on its\n * token, in `components/experiment-zones`, so a widget elsewhere is written\n * against the same shape without importing this plugin.\n */\n registerPluginZone(\n {\n zone: EXPERIMENT_VARIANTS_ZONE,\n label: 'A/B test variants',\n surface: 'console',\n description:\n 'In the experiment editor, beside the variants. A widget here proposes copy for the variants the editor holds; the dialog’s Save is the write, and nothing a widget returns starts or changes a test.',\n },\n // Named rather than left to the loader's marker, because this function is\n // also called directly — by a spec, and by an app that loads the plugin\n // without the loader — and a zone with no owner is refused.\n { pluginId: BUNDLE_ID },\n )\n registerPluginZone(\n {\n zone: EXPERIMENT_RESULT_ZONE,\n label: 'A/B test result',\n surface: 'console',\n description:\n 'Below the results of one test. A widget here explains what the figures show. There is nothing to apply, and a widget may not declare a winner the card has not.',\n },\n { pluginId: BUNDLE_ID },\n )\n /*\n * What a campaign email asks of whichever plugin keeps the mail itself —\n * the topic catalog, the sending identities, the design document and its\n * renderer. Each is a zone the composer or a message's page hosts; see\n * `components/campaign-email-zones` for what each hands a widget. All are\n * `bare`: a widget there is a field, a button, a drawer or nothing at all,\n * and the stack's spacing around it would be a gap in a form.\n */\n const bareZone = <Props,>(\n zone: PluginZone<Props>,\n label: string,\n description: string,\n ) =>\n registerPluginZone(\n { zone, label, description, surface: 'console', layout: 'bare' },\n { pluginId: BUNDLE_ID },\n )\n bareZone(\n CAMPAIGN_TOPIC_SELECT_ZONE,\n 'An email’s topic',\n 'In the campaign composer’s field stack. A widget here is the picker for the stream the email belongs to: it is handed the chosen id and reports a new one through `onChange`. The composer records it; the widget writes nothing.',\n )\n bareZone(\n CAMPAIGN_TOPIC_OPTIONS_ZONE,\n 'The topics a campaign can open on',\n 'Beside the drawers that create and edit a campaign. A widget here draws nothing: while `enabled` it reads the streams a recipient can still leave and answers through `onTopics`.',\n )\n bareZone(\n CAMPAIGN_SENDER_EDITOR_ZONE,\n 'Add a sender while composing',\n 'Mounted by the campaign composer once its author asks to add a sender. A widget here is that editor, open in its add mode; `onSaved` names the sender it created.',\n )\n bareZone(\n CAMPAIGN_DESIGN_CREATE_ZONE,\n 'A design for one email',\n 'In the campaign composer, beside the design picker. A widget here is the control that mints a design document and names it through `onCreated`; the composer records the choice on the campaign and opens the editor.',\n )\n bareZone(\n CAMPAIGN_DESIGN_PREVIEW_ZONE,\n 'A sent email as an inbox receives it',\n 'On one message’s page. A widget here renders the stored design, or the plain-text body, through the renderer the send path uses.',\n )\n registerMarketingRecordRoutes()\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'Marketing',\n /*\n * The host dashboard's `Last campaign` card (AGL-433). A widget rather\n * than a direct import by the dashboard page, so it appears only on\n * workspaces that have the plugin that owns the campaign history — a card\n * about campaigns on a console with no campaigns page could only ever be\n * blank, and its header links straight into `/marketing/campaigns`.\n */\n widgets: [\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n // The id a reader's dashboard preferences already name — it is what\n // `isDashboardWidgetHidden` and the customize order are keyed on, so\n // it is persisted and does not follow the plugin.\n widgetId: 'email-campaign-glance',\n title: 'Last campaign',\n Component: CampaignGlanceCard,\n },\n // Which campaign or link brought a person, drawn where the plugins that\n // keep and list people host a zone for it. They hand over what the\n // record is and its id; neither imports this plugin.\n {\n slot: 'crmRecordAttribution',\n widgetId: 'marketing-crm-record-attribution',\n title: 'Campaign attribution',\n Component: RecordAttributionWidget,\n },\n // The Emails page hosts its Messages section as a zone: a message is\n // one send of a campaign, and every action on it is this plugin's route.\n {\n slot: 'emailMessages',\n widgetId: 'marketing-email-messages',\n title: 'Messages',\n Component: EmailMessagesWidget,\n },\n {\n slot: 'emailTemplateRecipients',\n widgetId: 'marketing-email-template-recipients',\n title: 'Recipients',\n Component: EmailRecipientsCard,\n },\n // The Inbox's Campaigns tab is a zone; this is the card that fills it.\n {\n slot: 'inboxCampaigns',\n widgetId: 'marketing-inbox-campaigns',\n title: 'Campaigns',\n Component: InboxCampaignsWidget,\n },\n {\n slot: 'inboxRecordAttribution',\n widgetId: 'marketing-inbox-record-attribution',\n title: 'Campaign attribution',\n Component: RecordAttributionWidget,\n },\n ],\n navItems: [\n {\n label: 'Marketing',\n href: '/marketing',\n // Sections as ROUTES (AGL-2501): each is a real URL the shell\n // resolves and gates, so the page mounts the one being read.\n sections: MARKETING_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-marketing',\n icon: { path: mdiBullhornOutline.path },\n header: {\n title: 'Marketing',\n icon: { path: mdiBullhornOutline.path },\n docsTopic: 'marketingOverlays',\n },\n Component: MarketingConsolePage,\n },\n ],\n /*\n * The ORGANIZATION's Marketing hub, at `/[orgSlug]/marketing`: every\n * section the site hub has, over every site. Campaigns and their sequence\n * rollups belong to the organization, so this is where one campaign is\n * placed across several sites; overlays, tests and conversions stay each\n * site's and are read across them. Every site's mail is on the\n * organization's Emails page, as a site's is on its own. The page is the\n * same component; handed no site, it renders the org sections.\n *\n * It carries the SITE tab's id on purpose, the way the org CRM tab does:\n * `release_marketing` names `nav-tab-marketing`, so one flag holds both\n * halves of the surface, and the org half cannot ship while the site half\n * is switched off.\n */\n orgNavItems: [\n {\n label: 'Marketing',\n href: '/marketing',\n sections: MARKETING_ORG_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-marketing',\n icon: { path: mdiBullhornOutline.path },\n header: {\n title: 'Marketing',\n icon: { path: mdiBullhornOutline.path },\n docsTopic: 'marketingOverlays',\n },\n Component: MarketingConsolePage,\n },\n ],\n })\n}\n\nexport * from './site'\n"],"names":["Aglyn","registerPluginZone","mdiBullhornOutline","lazy","CAMPAIGN_DESIGN_CREATE_ZONE","CAMPAIGN_DESIGN_PREVIEW_ZONE","CAMPAIGN_SENDER_EDITOR_ZONE","CAMPAIGN_TOPIC_OPTIONS_ZONE","CAMPAIGN_TOPIC_SELECT_ZONE","EXPERIMENT_RESULT_ZONE","EXPERIMENT_VARIANTS_ZONE","MARKETING_CONSOLE_SECTIONS","MARKETING_ORG_CONSOLE_SECTIONS","BUNDLE_ID","registerMarketingRecordRoutes","MarketingConsolePage","CampaignGlanceCard","InboxCampaignsWidget","EmailMessagesWidget","EmailRecipientsCard","RecordAttributionWidget","registerMarketingConsole","zone","label","surface","description","pluginId","bareZone","layout","registerConsoleExtension","displayName","widgets","slot","CONSOLE_WIDGET_SLOTS","hostDashboard","widgetId","title","Component","navItems","href","sections","navTabId","icon","path","header","docsTopic","orgNavItems"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/marketing/src/lib/plugin.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 * as Aglyn from '@aglyn/aglyn'\nimport {\n registerPluginZone,\n type PluginZone,\n} from '@aglyn/aglyn/plugin-manager/plugin-zones'\nimport { mdiBullhornOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport {\n CAMPAIGN_DESIGN_CREATE_ZONE,\n CAMPAIGN_DESIGN_PREVIEW_ZONE,\n CAMPAIGN_SENDER_EDITOR_ZONE,\n CAMPAIGN_TOPIC_OPTIONS_ZONE,\n CAMPAIGN_TOPIC_SELECT_ZONE,\n} from './components/campaign-email-zones'\nimport {\n EXPERIMENT_RESULT_ZONE,\n EXPERIMENT_VARIANTS_ZONE,\n} from './components/experiment-zones'\nimport {\n MARKETING_CONSOLE_SECTIONS,\n MARKETING_ORG_CONSOLE_SECTIONS,\n} from './components/marketing-console-sections'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { StaffOrgEmailCard } from './components/staff-org-email-card.component'\nimport { registerMarketingRecordRoutes } from './model/marketing-record-routes'\n\n/** Code-split: the Marketing console page only loads when opened. */\nconst MarketingConsolePage = lazy(\n () => import('./components/marketing-console-page'),\n)\n\n/** Dashboard glance card, loaded only where the shell renders the slot. */\nconst CampaignGlanceCard = lazy(\n () => import('./components/campaign-glance-card.component'),\n)\n\n/**\n * The campaigns drawn in the Inbox's Campaigns section: one site's, or every\n * site's on the organization's Inbox.\n */\nconst InboxCampaignsWidget = lazy(\n () => import('./components/inbox-campaigns-widget'),\n)\n\n/** The Emails page's Messages section: the list, one message, its composer. */\nconst EmailMessagesWidget = lazy(\n () => import('./components/email-messages-widget'),\n)\n\n/** Who received the sends built from one template, on the template's page. */\nconst EmailRecipientsCard = lazy(\n () => import('./components/email-recipients-card'),\n)\n\n/** Loaded where a record page draws one of the attribution zones. */\nconst RecordAttributionWidget = lazy(\n () => import('./components/record-attribution-widget'),\n)\n\n/**\n * Marketing feature plugin (AGL-395). Console-only — overlays and popups\n * render on published sites through the tenant runtime, not a canvas\n * element of their own, so there is no UI bundle. The console half declares\n * the Marketing nav + page through the ConsoleExtension registry (always-on;\n * the surface itself is not release-flagged — its overlays/A-B cards run\n * their own per-plan checks off the passed `org`). The popup image picker\n * uses the shell's media browser via `useMediaPicker`.\n */\nexport function registerMarketingConsole(): void {\n /*\n * The two positions the A/B testing card hosts (AGL-2914), declared before\n * the extension that draws the card. An id another plugin has already taken\n * is refused, naming both. What each zone hands a widget is carried on its\n * token, in `components/experiment-zones`, so a widget elsewhere is written\n * against the same shape without importing this plugin.\n */\n registerPluginZone(\n {\n zone: EXPERIMENT_VARIANTS_ZONE,\n label: 'A/B test variants',\n surface: 'console',\n description:\n 'In the experiment editor, beside the variants. A widget here proposes copy for the variants the editor holds; the dialog’s Save is the write, and nothing a widget returns starts or changes a test.',\n },\n // Named rather than left to the loader's marker, because this function is\n // also called directly — by a spec, and by an app that loads the plugin\n // without the loader — and a zone with no owner is refused.\n { pluginId: BUNDLE_ID },\n )\n registerPluginZone(\n {\n zone: EXPERIMENT_RESULT_ZONE,\n label: 'A/B test result',\n surface: 'console',\n description:\n 'Below the results of one test. A widget here explains what the figures show. There is nothing to apply, and a widget may not declare a winner the card has not.',\n },\n { pluginId: BUNDLE_ID },\n )\n /*\n * What a campaign email asks of whichever plugin keeps the mail itself —\n * the topic catalog, the sending identities, the design document and its\n * renderer. Each is a zone the composer or a message's page hosts; see\n * `components/campaign-email-zones` for what each hands a widget. All are\n * `bare`: a widget there is a field, a button, a drawer or nothing at all,\n * and the stack's spacing around it would be a gap in a form.\n */\n const bareZone = <Props,>(\n zone: PluginZone<Props>,\n label: string,\n description: string,\n ) =>\n registerPluginZone(\n { zone, label, description, surface: 'console', layout: 'bare' },\n { pluginId: BUNDLE_ID },\n )\n bareZone(\n CAMPAIGN_TOPIC_SELECT_ZONE,\n 'An email’s topic',\n 'In the campaign composer’s field stack. A widget here is the picker for the stream the email belongs to: it is handed the chosen id and reports a new one through `onChange`. The composer records it; the widget writes nothing.',\n )\n bareZone(\n CAMPAIGN_TOPIC_OPTIONS_ZONE,\n 'The topics a campaign can open on',\n 'Beside the drawers that create and edit a campaign. A widget here draws nothing: while `enabled` it reads the streams a recipient can still leave and answers through `onTopics`.',\n )\n bareZone(\n CAMPAIGN_SENDER_EDITOR_ZONE,\n 'Add a sender while composing',\n 'Mounted by the campaign composer once its author asks to add a sender. A widget here is that editor, open in its add mode; `onSaved` names the sender it created.',\n )\n bareZone(\n CAMPAIGN_DESIGN_CREATE_ZONE,\n 'A design for one email',\n 'In the campaign composer, beside the design picker. A widget here is the control that mints a design document and names it through `onCreated`; the composer records the choice on the campaign and opens the editor.',\n )\n bareZone(\n CAMPAIGN_DESIGN_PREVIEW_ZONE,\n 'A sent email as an inbox receives it',\n 'On one message’s page. A widget here renders the stored design, or the plain-text body, through the renderer the send path uses.',\n )\n registerMarketingRecordRoutes()\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'Marketing',\n /*\n * The host dashboard's `Last campaign` card (AGL-433). A widget rather\n * than a direct import by the dashboard page, so it appears only on\n * workspaces that have the plugin that owns the campaign history — a card\n * about campaigns on a console with no campaigns page could only ever be\n * blank, and its header links straight into `/marketing/campaigns`.\n */\n widgets: [\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n // The id a reader's dashboard preferences already name — it is what\n // `isDashboardWidgetHidden` and the customize order are keyed on, so\n // it is persisted and does not follow the plugin.\n widgetId: 'email-campaign-glance',\n title: 'Last campaign',\n Component: CampaignGlanceCard,\n },\n // Which campaign or link brought a person, drawn where the plugins that\n // keep and list people host a zone for it. They hand over what the\n // record is and its id; neither imports this plugin.\n {\n slot: 'crmRecordAttribution',\n widgetId: 'marketing-crm-record-attribution',\n title: 'Campaign attribution',\n Component: RecordAttributionWidget,\n },\n // The Emails page hosts its Messages section as a zone: a message is\n // one send of a campaign, and every action on it is this plugin's route.\n {\n slot: 'emailMessages',\n widgetId: 'marketing-email-messages',\n title: 'Messages',\n Component: EmailMessagesWidget,\n },\n {\n slot: 'emailTemplateRecipients',\n widgetId: 'marketing-email-template-recipients',\n title: 'Recipients',\n Component: EmailRecipientsCard,\n },\n // The Inbox's Campaigns tab is a zone; this is the card that fills it.\n {\n slot: 'inboxCampaigns',\n widgetId: 'marketing-inbox-campaigns',\n title: 'Campaigns',\n Component: InboxCampaignsWidget,\n },\n {\n slot: 'inboxRecordAttribution',\n widgetId: 'marketing-inbox-record-attribution',\n title: 'Campaign attribution',\n Component: RecordAttributionWidget,\n },\n // The staff organization page's sends and campaigns (AGL-3380), read\n // only: staff see what the organization sent without joining it.\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.adminOrgDetail,\n widgetId: 'marketing-staff-org-email',\n title: 'Email campaigns',\n Component: StaffOrgEmailCard,\n },\n ],\n navItems: [\n {\n label: 'Marketing',\n href: '/marketing',\n // Sections as ROUTES (AGL-2501): each is a real URL the shell\n // resolves and gates, so the page mounts the one being read.\n sections: MARKETING_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-marketing',\n icon: { path: mdiBullhornOutline.path },\n header: {\n title: 'Marketing',\n icon: { path: mdiBullhornOutline.path },\n docsTopic: 'marketingOverlays',\n },\n Component: MarketingConsolePage,\n },\n ],\n /*\n * The ORGANIZATION's Marketing hub, at `/[orgSlug]/marketing`: every\n * section the site hub has, over every site. Campaigns and their sequence\n * rollups belong to the organization, so this is where one campaign is\n * placed across several sites; overlays, tests and conversions stay each\n * site's and are read across them. Every site's mail is on the\n * organization's Emails page, as a site's is on its own. The page is the\n * same component; handed no site, it renders the org sections.\n *\n * It carries the SITE tab's id on purpose, the way the org CRM tab does:\n * `release_marketing` names `nav-tab-marketing`, so one flag holds both\n * halves of the surface, and the org half cannot ship while the site half\n * is switched off.\n */\n orgNavItems: [\n {\n label: 'Marketing',\n href: '/marketing',\n sections: MARKETING_ORG_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-marketing',\n icon: { path: mdiBullhornOutline.path },\n header: {\n title: 'Marketing',\n icon: { path: mdiBullhornOutline.path },\n docsTopic: 'marketingOverlays',\n },\n Component: MarketingConsolePage,\n },\n ],\n })\n}\n\nexport * from './site'\n"],"names":["Aglyn","registerPluginZone","mdiBullhornOutline","lazy","CAMPAIGN_DESIGN_CREATE_ZONE","CAMPAIGN_DESIGN_PREVIEW_ZONE","CAMPAIGN_SENDER_EDITOR_ZONE","CAMPAIGN_TOPIC_OPTIONS_ZONE","CAMPAIGN_TOPIC_SELECT_ZONE","EXPERIMENT_RESULT_ZONE","EXPERIMENT_VARIANTS_ZONE","MARKETING_CONSOLE_SECTIONS","MARKETING_ORG_CONSOLE_SECTIONS","BUNDLE_ID","StaffOrgEmailCard","registerMarketingRecordRoutes","MarketingConsolePage","CampaignGlanceCard","InboxCampaignsWidget","EmailMessagesWidget","EmailRecipientsCard","RecordAttributionWidget","registerMarketingConsole","zone","label","surface","description","pluginId","bareZone","layout","registerConsoleExtension","displayName","widgets","slot","CONSOLE_WIDGET_SLOTS","hostDashboard","widgetId","title","Component","adminOrgDetail","navItems","href","sections","navTabId","icon","path","header","docsTopic","orgNavItems"],"mappings":"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"}
@@ -22,6 +22,8 @@ import { campaignHeldForReviewNotice, campaignPlacedOnHost, campaignSendHeldForR
22
22
  import { readPluginRecordCard } from "@aglyn/aglyn/plugin-manager/plugin-record-cards";
23
23
  import { hostPublicOrigin } from "@aglyn/aglyn/server";
24
24
  import { consentGroupForSite, orgDataCollectionForHost, orgDataQueryForHost, filterDeliverableRecipients, filterSendableForHost, filterTopicSendable, firebaseAdmin, getOrgForHost, meterHostEmail, claimOrgEmailSendBudget, orgCampaignEmailSendsForMonth, readEmailSendRateConfig, readEmailSendRateWindow, reconcileCampaignSendReservation, reserveCampaignEmailSends, resolveHostSendingIdentity } from "@aglyn/tenant-data-admin";
25
+ import { raiseOperatorAlert } from "@aglyn/tenant-data-admin/server/operator-alerts";
26
+ import { MARKETING_REPUTATION_BREAKER } from "../constants/operator-alerts.js";
25
27
  // The leaf, not the barrel: this plugin's specs substitute the barrel
26
28
  // wholesale, and the lookup must reach the real index logic under them.
27
29
  import { findContactByEmail } from "@aglyn/tenant-data-admin/server/contact-email-index";
@@ -1125,6 +1127,17 @@ export async function performCampaignSend(options) {
1125
1127
  * merchant who cannot send and cannot find out why will open a ticket
1126
1128
  * that says the product is broken.
1127
1129
  */ if (reputation.blocked) {
1130
+ // The merchant is told by the refusal; the operator, once a day per
1131
+ // workspace, so a tripped breaker on a shared domain is looked at
1132
+ // before it is reinstated (AGL-3377).
1133
+ await raiseOperatorAlert(MARKETING_REPUTATION_BREAKER, {
1134
+ dedupeKey: orgId,
1135
+ context: {
1136
+ orgId,
1137
+ reason: reputation.reason
1138
+ },
1139
+ orgId
1140
+ });
1128
1141
  throw new CampaignSendError(reputation.reason, 409);
1129
1142
  }
1130
1143
  /*