@aglyn/plugins-marketing 1.0.0-beta.218 → 1.0.0-beta.220

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +5 -1
  2. package/package.json +16 -16
  3. package/src/lib/analytics-provider.d.ts +177 -0
  4. package/src/lib/analytics-provider.js +370 -0
  5. package/src/lib/analytics-provider.js.map +1 -0
  6. package/src/lib/components/besigner-section-experiments.d.ts +20 -0
  7. package/src/lib/components/besigner-section-experiments.js +128 -0
  8. package/src/lib/components/besigner-section-experiments.js.map +1 -0
  9. package/src/lib/components/campaign-conversions-card.js +1 -1
  10. package/src/lib/components/campaign-conversions-card.js.map +1 -1
  11. package/src/lib/components/campaign-detail-card.js +16 -16
  12. package/src/lib/components/campaign-detail-card.js.map +1 -1
  13. package/src/lib/components/campaign-members-section.js +15 -14
  14. package/src/lib/components/campaign-members-section.js.map +1 -1
  15. package/src/lib/components/campaign-reach-sections.js +4 -1
  16. package/src/lib/components/campaign-reach-sections.js.map +1 -1
  17. package/src/lib/components/campaign-report-card.js +8 -6
  18. package/src/lib/components/campaign-report-card.js.map +1 -1
  19. package/src/lib/components/campaigns-card.js +1 -1
  20. package/src/lib/components/campaigns-card.js.map +1 -1
  21. package/src/lib/components/conversion-attribution.component.d.ts +1 -1
  22. package/src/lib/components/conversion-attribution.component.js +2 -2
  23. package/src/lib/components/conversion-attribution.component.js.map +1 -1
  24. package/src/lib/components/email-detail.js +12 -7
  25. package/src/lib/components/email-detail.js.map +1 -1
  26. package/src/lib/components/email-messages-widget.js +1 -1
  27. package/src/lib/components/email-messages-widget.js.map +1 -1
  28. package/src/lib/components/emails-list-card.js +11 -5
  29. package/src/lib/components/emails-list-card.js.map +1 -1
  30. package/src/lib/components/host-experiments-card.component.js +12 -7
  31. package/src/lib/components/host-experiments-card.component.js.map +1 -1
  32. package/src/lib/components/org-experiments-card.js +1 -1
  33. package/src/lib/components/org-experiments-card.js.map +1 -1
  34. package/src/lib/components/record-attribution-widget.js +1 -1
  35. package/src/lib/components/record-attribution-widget.js.map +1 -1
  36. package/src/lib/components/record-route-context.d.ts +41 -0
  37. package/src/lib/components/record-route-context.js +63 -0
  38. package/src/lib/components/record-route-context.js.map +1 -0
  39. package/src/lib/components/site-runtime.js +2 -2
  40. package/src/lib/components/site-runtime.js.map +1 -1
  41. package/src/lib/components/use-marketing-hub-path.d.ts +18 -5
  42. package/src/lib/components/use-marketing-hub-path.js +36 -6
  43. package/src/lib/components/use-marketing-hub-path.js.map +1 -1
  44. package/src/lib/constants/operator-alerts.js +4 -1
  45. package/src/lib/constants/operator-alerts.js.map +1 -1
  46. package/src/lib/declarations.server.d.ts +3 -1
  47. package/src/lib/declarations.server.js +17 -1
  48. package/src/lib/declarations.server.js.map +1 -1
  49. package/src/lib/model/campaign-conversions.d.ts +301 -0
  50. package/src/lib/model/campaign-conversions.js +252 -0
  51. package/src/lib/model/campaign-conversions.js.map +1 -0
  52. package/src/lib/model/campaign-kind.d.ts +50 -0
  53. package/src/lib/model/campaign-kind.js +52 -0
  54. package/src/lib/model/campaign-kind.js.map +1 -0
  55. package/src/lib/model/campaign-members-query.js +1 -1
  56. package/src/lib/model/campaign-members-query.js.map +1 -1
  57. package/src/lib/model/campaign-revenue.d.ts +327 -0
  58. package/src/lib/model/campaign-revenue.js +332 -0
  59. package/src/lib/model/campaign-revenue.js.map +1 -0
  60. package/src/lib/model/campaign-send-time.d.ts +74 -0
  61. package/src/lib/model/campaign-send-time.js +120 -0
  62. package/src/lib/model/campaign-send-time.js.map +1 -0
  63. package/src/lib/model/experiments.js +1 -1
  64. package/src/lib/model/experiments.js.map +1 -1
  65. package/src/lib/model/index.d.ts +3 -0
  66. package/src/lib/model/index.js +4 -1
  67. package/src/lib/model/index.js.map +1 -1
  68. package/src/lib/model/overlay-record-list.d.ts +33 -0
  69. package/src/lib/model/overlay-record-list.js +53 -0
  70. package/src/lib/model/overlay-record-list.js.map +1 -0
  71. package/src/lib/plugin.js +12 -0
  72. package/src/lib/plugin.js.map +1 -1
  73. package/src/lib/server/campaign-batch-plan.d.ts +73 -0
  74. package/src/lib/server/campaign-batch-plan.js +79 -0
  75. package/src/lib/server/campaign-batch-plan.js.map +1 -0
  76. package/src/lib/server/campaign-manage.js +19 -60
  77. package/src/lib/server/campaign-manage.js.map +1 -1
  78. package/src/lib/server/campaign-send.d.ts +1 -1
  79. package/src/lib/server/campaign-send.js +32 -11
  80. package/src/lib/server/campaign-send.js.map +1 -1
  81. package/src/lib/server/email-events.js +16 -16
  82. package/src/lib/server/email-events.js.map +1 -1
  83. package/src/lib/server/list-send-time-facts.d.ts +60 -0
  84. package/src/lib/server/list-send-time-facts.js +108 -0
  85. package/src/lib/server/list-send-time-facts.js.map +1 -0
  86. package/src/lib/server/overlay-beacon.d.ts +46 -0
  87. package/src/lib/server/overlay-beacon.js +76 -0
  88. package/src/lib/server/overlay-beacon.js.map +1 -0
  89. package/src/lib/server.js +4 -0
  90. package/src/lib/server.js.map +1 -1
  91. package/src/lib/tenant-emails.d.ts +29 -0
  92. package/src/lib/tenant-emails.js +41 -0
  93. package/src/lib/tenant-emails.js.map +1 -0
  94. package/src/lib/testing/stand-in-emails-page-routes.d.ts +27 -0
  95. package/src/lib/testing/stand-in-emails-page-routes.js +42 -0
  96. package/src/lib/testing/stand-in-emails-page-routes.js.map +1 -0
  97. package/src/lib/components/use-emails-hub-path.d.ts +0 -58
  98. package/src/lib/components/use-emails-hub-path.js +0 -109
  99. package/src/lib/components/use-emails-hub-path.js.map +0 -1
@@ -14,7 +14,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */ import { checkQuota, contactMatchesSegment, isEmailTopicId, DEFAULT_CAMPAIGN_TOPIC_ID, readMarketingBasis, resolveMarketingConsentPolicy, splitByMarketingConsent, createResourceUid, decodeStoredNodes, emailStarterSendBlock, resolveBrandingProfile, siteLockdownFromDocs, visibleToHost } from "@aglyn/aglyn/server";
17
+ */ import { checkQuota, contactMatchesSegment, isSubscriptionTopicId, DEFAULT_SUBSCRIPTION_TOPIC_ID, readMarketingBasis, resolveMarketingConsentPolicy, splitByMarketingConsent, createResourceUid, decodeStoredNodes, emailStarterSendBlock, resolveBrandingProfile, seenOnlyThroughGrant, siteLockdownFromDocs, visibleToHost } from "@aglyn/aglyn/server";
18
18
  import { renderRecipientEmail } from "@aglyn/aglyn/app-utils/recipient-email-render";
19
19
  import { composeHostComponentNodes } from "@aglyn/aglyn/app-utils/load-referenced-components";
20
20
  import { assignExperimentVariant } from "../model/index.js";
@@ -70,7 +70,9 @@ import { campaignSendSearchFields } from "../model/campaign-list-query.js";
70
70
  * against a harness that cannot serve it.
71
71
  */ import { claimOrgEmailSendDay, orgAgeDays, readSenderReputation, reconcileOrgEmailSendDay, recordCampaignAccepted, resolveOrgEmailRamp } from "@aglyn/tenant-data-admin/server/email-sender-reputation";
72
72
  import { createHash } from "crypto";
73
- import { EMAIL_MAX_AUDIENCE_PER_SEND, EMAIL_MAX_RECIPIENTS_PER_SEND, campaignBatchPlan, createProviderRequestPacer, effectiveReputationPolicy, HOST_SENDERS_COLLECTION, isEmailConfigured, rateLimitedRetryAtMs, readHostSender, sendEmail, sendingIdentityRefusal, sentAsStamp } from "@aglyn/shared-util-email";
73
+ import { EMAIL_MAX_AUDIENCE_PER_SEND, EMAIL_MAX_RECIPIENTS_PER_SEND, createProviderRequestPacer, effectiveReputationPolicy, HOST_SENDERS_COLLECTION, isEmailConfigured, rateLimitedRetryAtMs, readHostSender, sendEmail, sendingIdentityRefusal, sentAsStamp } from "@aglyn/shared-util-email";
74
+ import { campaignBatchPlan } from "./campaign-batch-plan.js";
75
+ import { stampRecordEmailReach } from "@aglyn/aglyn/plugin-manager/plugin-record-email-state";
74
76
  /*
75
77
  * The List-Unsubscribe setting sequences share (AGL-3307), from its LEAF
76
78
  * module: the specs that reach this file spread the real barrel under their
@@ -449,10 +451,10 @@ export async function performCampaignSend(options) {
449
451
  * two different parameter tuples — see `signedSubject` in the email plugin's
450
452
  * `unsubscribe-link.ts`. Refused at the point the topic ENTERS the send, so
451
453
  * the link that leaves it is unambiguous by construction.
452
- */ if (options.topicId && !isEmailTopicId(options.topicId)) {
454
+ */ if (options.topicId && !isSubscriptionTopicId(options.topicId)) {
453
455
  throw new CampaignSendError('Invalid topicId', 400);
454
456
  }
455
- const topicId = options.topicId || DEFAULT_CAMPAIGN_TOPIC_ID;
457
+ const topicId = options.topicId || DEFAULT_SUBSCRIPTION_TOPIC_ID;
456
458
  /**
457
459
  * Whether this send ADDS to an email that already exists, rather than
458
460
  * starting one.
@@ -714,7 +716,9 @@ export async function performCampaignSend(options) {
714
716
  // MAIL a sibling brand's leads under this site's campaign.
715
717
  const leads = await sweepAudience((await orgDataQueryForHost(hostId, 'leads')).query);
716
718
  audienceTruncated = leads.truncated;
717
- recipients = leads.docs.map((doc)=>{
719
+ // Held, not merely seen: a lead another site SHARED with this one
720
+ // (AGL-3336) is visible here and was never this site's audience.
721
+ recipients = leads.docs.filter((doc)=>!seenOnlyThroughGrant(doc.data(), hostId)).map((doc)=>{
718
722
  var _doc_get;
719
723
  const email = String((_doc_get = doc.get('email')) != null ? _doc_get : '');
720
724
  collectName(email, doc.get('name'));
@@ -780,7 +784,9 @@ export async function performCampaignSend(options) {
780
784
  * reports a small `audienceSize` with `audienceTruncated` set, reading as
781
785
  * "at least this many, we stopped counting" and not as a total.
782
786
  */ audienceTruncated = contacts.truncated;
783
- recipients = contacts.docs.filter((doc)=>{
787
+ recipients = contacts.docs// Held, not merely seen — a shared contact is no site's audience but
788
+ // its holders' (AGL-3336).
789
+ .filter((doc)=>!seenOnlyThroughGrant(doc.data(), hostId)).filter((doc)=>{
784
790
  var _doc_get, _doc_get1;
785
791
  return contactMatchesSegment({
786
792
  tags: (_doc_get = doc.get('tags')) != null ? _doc_get : [],
@@ -1127,14 +1133,21 @@ export async function performCampaignSend(options) {
1127
1133
  * merchant who cannot send and cannot find out why will open a ticket
1128
1134
  * that says the product is broken.
1129
1135
  */ if (reputation.blocked) {
1136
+ var _ref4;
1137
+ var _orgForHost_org4;
1130
1138
  // The merchant is told by the refusal; the operator, once a day per
1131
1139
  // workspace, so a tripped breaker on a shared domain is looked at
1132
1140
  // before it is reinstated (AGL-3377).
1133
1141
  await raiseOperatorAlert(MARKETING_REPUTATION_BREAKER, {
1134
1142
  dedupeKey: orgId,
1143
+ // The findings in numbers, and the workspace by name off the org doc
1144
+ // this send already holds. `reputation.reason` is the merchant's
1145
+ // refusal, written to them, so it is not staff's copy.
1135
1146
  context: {
1136
1147
  orgId,
1137
- reason: reputation.reason
1148
+ orgName: String((_ref4 = orgForHost == null ? void 0 : (_orgForHost_org4 = orgForHost.org) == null ? void 0 : _orgForHost_org4['name']) != null ? _ref4 : '').trim(),
1149
+ windowDays: reputation.windowDays,
1150
+ detail: reputation.findings.filter((finding)=>finding.actionable).map((finding)=>finding.detail).join(' ')
1138
1151
  },
1139
1152
  orgId
1140
1153
  });
@@ -1472,14 +1485,14 @@ export async function performCampaignSend(options) {
1472
1485
  * back on the clock and this screen finds it released — and actioning the
1473
1486
  * row cancels it.
1474
1487
  */ if (!options.followUp) {
1475
- var _ref4, _hostSnapshot_data, _options_fromName, _sendingIdentity_from, _options_replyTo, _options_preheader, _options_plainText, _experiment_variants;
1488
+ var _ref5, _hostSnapshot_data, _options_fromName, _sendingIdentity_from, _options_replyTo, _options_preheader, _options_plainText, _experiment_variants;
1476
1489
  const sendRef = sends.doc(campaignId);
1477
1490
  const screened = await screenOutboundSend({
1478
1491
  kind: 'campaign',
1479
1492
  path: sendRef.path,
1480
1493
  hostId,
1481
1494
  orgId,
1482
- org: (_ref4 = orgForHost == null ? void 0 : orgForHost.org) != null ? _ref4 : null,
1495
+ org: (_ref5 = orgForHost == null ? void 0 : orgForHost.org) != null ? _ref5 : null,
1483
1496
  host: (_hostSnapshot_data = hostSnapshot.data()) != null ? _hostSnapshot_data : null,
1484
1497
  subject,
1485
1498
  fromName: (_options_fromName = options.fromName) != null ? _options_fromName : null,
@@ -1804,7 +1817,7 @@ export async function performCampaignSend(options) {
1804
1817
  siteBase,
1805
1818
  hostId,
1806
1819
  email,
1807
- campaignId,
1820
+ sendId: campaignId,
1808
1821
  topicId,
1809
1822
  secret: unsubscribeSecret
1810
1823
  };
@@ -2066,6 +2079,14 @@ export async function performCampaignSend(options) {
2066
2079
  * follow-up, which is also safe but loses the feature over a transient.
2067
2080
  */ if (options.recordCampaign !== false) {
2068
2081
  await recordCampaignReach(sends.doc(campaignId), reached);
2082
+ // The record system hears who it reached, so a lead nobody had touched
2083
+ // moves to Nurturing (AGL-3446). A test send reached nobody's lead, so it
2084
+ // is left out with the reach record. Never throws.
2085
+ await stampRecordEmailReach({
2086
+ orgId,
2087
+ hostId,
2088
+ emails: reached
2089
+ });
2069
2090
  /*
2070
2091
  * And who it decided NOT to mail, under a field of its own.
2071
2092
  *
@@ -2860,7 +2881,7 @@ function storedSendOptionsFrom(snapshot, hostId, senderUid, followUp) {
2860
2881
  * An empty value is not an error: it means "the composer did not say", which
2861
2882
  * `performCampaignSend` resolves to the default topic.
2862
2883
  */ const topicId = String((_ref10 = (_req_body14 = req.body) == null ? void 0 : _req_body14.topicId) != null ? _ref10 : '');
2863
- if (topicId && !isEmailTopicId(topicId)) {
2884
+ if (topicId && !isSubscriptionTopicId(topicId)) {
2864
2885
  return res.status(400).json({
2865
2886
  error: 'Unknown topic'
2866
2887
  });