@aglyn/aglyn 1.0.0-beta.219 → 1.0.0-beta.221
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 +12 -12
- package/src/lib/app-utils/actions.d.ts +1 -59
- package/src/lib/app-utils/actions.js +7 -72
- package/src/lib/app-utils/actions.js.map +1 -1
- package/src/lib/app-utils/consent-groups.d.ts +1 -1
- package/src/lib/app-utils/consent-groups.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +0 -36
- package/src/lib/app-utils/console-routes.js +2 -37
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/contacts.d.ts +1 -1
- package/src/lib/app-utils/contacts.js.map +1 -1
- package/src/lib/app-utils/container-membership.d.ts +93 -0
- package/src/lib/app-utils/container-membership.js +165 -0
- package/src/lib/app-utils/container-membership.js.map +1 -0
- package/src/lib/app-utils/crm-task-reminders.d.ts +23 -6
- package/src/lib/app-utils/crm-task-reminders.js +32 -11
- package/src/lib/app-utils/crm-task-reminders.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +24 -4
- package/src/lib/app-utils/crm.js +23 -0
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/csv-upload.d.ts +1 -1
- package/src/lib/app-utils/csv-upload.js +1 -1
- package/src/lib/app-utils/csv-upload.js.map +1 -1
- package/src/lib/app-utils/{dataset-csv.d.ts → csv.d.ts} +10 -43
- package/src/lib/app-utils/{dataset-csv.js → csv.js} +10 -81
- package/src/lib/app-utils/csv.js.map +1 -0
- package/src/lib/app-utils/datasets.d.ts +0 -19
- package/src/lib/app-utils/datasets.js +0 -13
- package/src/lib/app-utils/datasets.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +4 -4
- package/src/lib/app-utils/docs-help.generated.js +5 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +71 -36
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/dynamic-list-rule.js +3 -3
- package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
- package/src/lib/app-utils/email-text.d.ts +80 -0
- package/src/lib/app-utils/email-text.js +189 -0
- package/src/lib/app-utils/email-text.js.map +1 -0
- package/src/lib/app-utils/{list-assignment-policy.d.ts → enrollment-basis.d.ts} +4 -0
- package/src/lib/app-utils/{list-assignment-policy.js → enrollment-basis.js} +5 -1
- package/src/lib/app-utils/enrollment-basis.js.map +1 -0
- package/src/lib/app-utils/expand-repeatables.js +4 -4
- package/src/lib/app-utils/expand-repeatables.js.map +1 -1
- package/src/lib/app-utils/forms.js +2 -2
- package/src/lib/app-utils/forms.js.map +1 -1
- package/src/lib/app-utils/health-report.d.ts +2 -3
- package/src/lib/app-utils/health-report.js +30 -6
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/inbound-mail-domain.d.ts +27 -0
- package/src/lib/app-utils/inbound-mail-domain.js +31 -0
- package/src/lib/app-utils/inbound-mail-domain.js.map +1 -0
- package/src/lib/app-utils/leaving-notice.d.ts +96 -0
- package/src/lib/app-utils/leaving-notice.js +157 -0
- package/src/lib/app-utils/leaving-notice.js.map +1 -0
- package/src/lib/app-utils/lockdown.d.ts +11 -0
- package/src/lib/app-utils/lockdown.js +33 -3
- package/src/lib/app-utils/lockdown.js.map +1 -1
- package/src/lib/app-utils/member-email-aliases.d.ts +25 -0
- package/src/lib/app-utils/member-email-aliases.js +29 -0
- package/src/lib/app-utils/member-email-aliases.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +69 -0
- package/src/lib/app-utils/notifications.js +119 -0
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/operator-alerts.d.ts +15 -1
- package/src/lib/app-utils/operator-alerts.js +64 -10
- package/src/lib/app-utils/operator-alerts.js.map +1 -1
- package/src/lib/app-utils/plan-entitlements.d.ts +164 -178
- package/src/lib/app-utils/plan-entitlements.js +200 -224
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/platform-marketing-consent.d.ts +56 -2
- package/src/lib/app-utils/platform-marketing-consent.js +6 -5
- package/src/lib/app-utils/platform-marketing-consent.js.map +1 -1
- package/src/lib/app-utils/plugin-starter-templates.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-starter-templates.generated.js +711 -0
- package/src/lib/app-utils/plugin-starter-templates.generated.js.map +1 -0
- package/src/lib/app-utils/repeat-query.d.ts +56 -0
- package/src/lib/app-utils/{dataset-query.js → repeat-query.js} +16 -16
- package/src/lib/app-utils/repeat-query.js.map +1 -0
- package/src/lib/app-utils/screen-link-context-value.d.ts +7 -0
- package/src/lib/app-utils/screen-link-context-value.js.map +1 -1
- package/src/lib/app-utils/screen-link-context.js +14 -2
- package/src/lib/app-utils/screen-link-context.js.map +1 -1
- package/src/lib/app-utils/screen-route.d.ts +3 -0
- package/src/lib/app-utils/screen-route.js +6 -1
- package/src/lib/app-utils/screen-route.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +7 -9
- package/src/lib/app-utils/server.js +18 -26
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/starter-template-nodes.d.ts +137 -0
- package/src/lib/app-utils/starter-template-nodes.js +129 -0
- package/src/lib/app-utils/starter-template-nodes.js.map +1 -0
- package/src/lib/app-utils/starter-templates.d.ts +11 -34
- package/src/lib/app-utils/starter-templates.js +19 -230
- package/src/lib/app-utils/starter-templates.js.map +1 -1
- package/src/lib/app-utils/{email-topics.d.ts → subscription-topics.d.ts} +64 -48
- package/src/lib/app-utils/{email-topics.js → subscription-topics.js} +39 -109
- package/src/lib/app-utils/subscription-topics.js.map +1 -0
- package/src/lib/app-utils/usage-budget.d.ts +73 -188
- package/src/lib/app-utils/usage-budget.js +58 -194
- package/src/lib/app-utils/usage-budget.js.map +1 -1
- package/src/lib/app-utils/utm-attribution.d.ts +1 -1
- package/src/lib/app-utils/utm-attribution.js +1 -1
- package/src/lib/app-utils/utm-attribution.js.map +1 -1
- package/src/lib/app-utils/{campaign-touch.d.ts → utm-touch.d.ts} +21 -13
- package/src/lib/app-utils/{campaign-touch.js → utm-touch.js} +30 -22
- package/src/lib/app-utils/utm-touch.js.map +1 -0
- package/src/lib/app-utils/variables.d.ts +9 -9
- package/src/lib/app-utils/variables.js.map +1 -1
- package/src/lib/app-utils/visitor-record-ceiling.d.ts +32 -0
- package/src/lib/app-utils/visitor-record-ceiling.js +39 -0
- package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
- package/src/lib/foundation/definitions/org-billing.types.d.ts +2 -2
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +80 -79
- package/src/lib/plugin-manager/feature-plugins.js +22 -35
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +18 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js +62 -2
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/index.d.ts +0 -1
- package/src/lib/plugin-manager/index.js +0 -1
- package/src/lib/plugin-manager/index.js.map +1 -1
- package/src/lib/plugin-manager/plugin-contact-capture.d.ts +9 -4
- package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
- package/src/lib/plugin-manager/plugin-containers.d.ts +68 -0
- package/src/lib/plugin-manager/plugin-containers.generated.d.ts +10 -0
- package/src/lib/plugin-manager/plugin-containers.generated.js +20 -0
- package/src/lib/plugin-manager/plugin-containers.generated.js.map +1 -0
- package/src/lib/plugin-manager/plugin-containers.js +27 -0
- package/src/lib/plugin-manager/plugin-containers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-entitlements.d.ts +8 -0
- package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
- package/src/lib/plugin-manager/plugin-membership-detach.js +1 -1
- package/src/lib/plugin-manager/plugin-membership-detach.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-email-state.d.ts +69 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js +32 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-lists.d.ts +115 -0
- package/src/lib/plugin-manager/plugin-record-lists.js +75 -0
- package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +42 -8
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
- package/src/lib/plugin-manager/plugin-server-steps.d.ts +170 -0
- package/src/lib/plugin-manager/plugin-server-steps.js +116 -0
- package/src/lib/plugin-manager/plugin-server-steps.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-beacons.d.ts +71 -0
- package/src/lib/plugin-manager/plugin-site-beacons.js +54 -0
- package/src/lib/plugin-manager/plugin-site-beacons.js.map +1 -0
- package/src/lib/plugin-manager/plugin-subscription-topics.generated.d.ts +11 -0
- package/src/lib/plugin-manager/plugin-subscription-topics.generated.js +41 -0
- package/src/lib/plugin-manager/plugin-subscription-topics.generated.js.map +1 -0
- package/src/lib/plugin-manager/plugin-usage-axes.d.ts +212 -0
- package/src/lib/plugin-manager/plugin-usage-axes.js +82 -3
- package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -1
- package/src/lib/plugin-manager/plugin-usage-meters.d.ts +108 -0
- package/src/lib/plugin-manager/plugin-usage-meters.js +105 -0
- package/src/lib/plugin-manager/plugin-usage-meters.js.map +1 -0
- package/src/lib/plugin-manager/usage-alert-contributors.d.ts +76 -6
- package/src/lib/plugin-manager/usage-alert-contributors.js +23 -10
- package/src/lib/plugin-manager/usage-alert-contributors.js.map +1 -1
- package/src/lib/app-utils/assist-credits.d.ts +0 -482
- package/src/lib/app-utils/assist-credits.js +0 -510
- package/src/lib/app-utils/assist-credits.js.map +0 -1
- package/src/lib/app-utils/campaign-membership.d.ts +0 -121
- package/src/lib/app-utils/campaign-membership.js +0 -189
- package/src/lib/app-utils/campaign-membership.js.map +0 -1
- package/src/lib/app-utils/campaign-touch.js.map +0 -1
- package/src/lib/app-utils/console-record-links.d.ts +0 -84
- package/src/lib/app-utils/console-record-links.js +0 -76
- package/src/lib/app-utils/console-record-links.js.map +0 -1
- package/src/lib/app-utils/crm-booking.d.ts +0 -75
- package/src/lib/app-utils/crm-booking.js +0 -133
- package/src/lib/app-utils/crm-booking.js.map +0 -1
- package/src/lib/app-utils/crm-inbound.d.ts +0 -245
- package/src/lib/app-utils/crm-inbound.js +0 -407
- package/src/lib/app-utils/crm-inbound.js.map +0 -1
- package/src/lib/app-utils/crm-reports.d.ts +0 -479
- package/src/lib/app-utils/crm-reports.js +0 -680
- package/src/lib/app-utils/crm-reports.js.map +0 -1
- package/src/lib/app-utils/dataset-csv.js.map +0 -1
- package/src/lib/app-utils/dataset-query.d.ts +0 -57
- package/src/lib/app-utils/dataset-query.js.map +0 -1
- package/src/lib/app-utils/email-topics.js.map +0 -1
- package/src/lib/app-utils/list-assignment-policy.js.map +0 -1
- package/src/lib/plugin-manager/plugin-metered-lines.d.ts +0 -97
- package/src/lib/plugin-manager/plugin-metered-lines.js +0 -123
- package/src/lib/plugin-manager/plugin-metered-lines.js.map +0 -1
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/ /**
|
|
17
|
-
* THE BOOKING DOOR BETWEEN THE CRM AND BOOKINGS (AGL-2660) — the pure half.
|
|
18
|
-
*
|
|
19
|
-
* A rep drops a booking link into an email from a record; the visitor books
|
|
20
|
-
* through it; the booking comes back to the record as a meeting on its
|
|
21
|
-
* timeline and, when the service asks for one, a follow-up task. The two
|
|
22
|
-
* plugins never import each other's server code, so everything both sides
|
|
23
|
-
* must agree on — the query key the link carries, the shape of the record
|
|
24
|
-
* reference inside it, the wording of the meeting and the task, the day the
|
|
25
|
-
* task falls due — lives here, in the library both already read.
|
|
26
|
-
*/ import { isValidTimeZone, zonedDateTime } from "@aglyn/shared-util-timestamp/zoned-time";
|
|
27
|
-
/**
|
|
28
|
-
* The query key a booking link carries the record on: `?crm=contact:abc`.
|
|
29
|
-
*
|
|
30
|
-
* The reference rides the LINK rather than relying on the booker's address
|
|
31
|
-
* because the two routinely differ — a rep emails somebody's work address
|
|
32
|
-
* and they book with a personal one — and a booking that could only be
|
|
33
|
-
* matched by address would then land on nobody. The widget copies the value
|
|
34
|
-
* onto the booking request, the booking row keeps it as `crmRef`, and the
|
|
35
|
-
* server files the meeting under the record it names.
|
|
36
|
-
*/ export const CRM_BOOKING_REF_PARAM = 'crm';
|
|
37
|
-
/**
|
|
38
|
-
* The query key that preselects a service in the booking widget:
|
|
39
|
-
* `?service={serviceId}`. The widget lists every active service and this
|
|
40
|
-
* opens it on the one the link was about.
|
|
41
|
-
*/ export const BOOKING_SERVICE_PARAM = 'service';
|
|
42
|
-
/** The three records a booking link can be dropped from. */ export const CRM_BOOKING_REF_KINDS = [
|
|
43
|
-
'contact',
|
|
44
|
-
'lead',
|
|
45
|
-
'deal'
|
|
46
|
-
];
|
|
47
|
-
/**
|
|
48
|
-
* What a record id may look like inside the reference. A contact's and a
|
|
49
|
-
* deal's ids are Firestore-minted; a lead's is a person key (hex). Nothing
|
|
50
|
-
* legitimate carries a slash or a space, and a value that does is not a
|
|
51
|
-
* reference the server should go looking for.
|
|
52
|
-
*/ const REF_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
53
|
-
/** `contact:abc` — the wire form of a reference. */ export function formatCrmBookingRef(ref) {
|
|
54
|
-
return `${ref.kind}:${ref.id}`;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* The reference a query value names, or `null` for anything that is not one.
|
|
58
|
-
*
|
|
59
|
-
* Strict on purpose: the value arrives on a public, unauthenticated booking
|
|
60
|
-
* request, so an unknown kind or a malformed id is dropped rather than
|
|
61
|
-
* stored — a booking still lands, it simply matches by address instead.
|
|
62
|
-
*/ export function parseCrmBookingRef(raw) {
|
|
63
|
-
if (typeof raw !== 'string') return null;
|
|
64
|
-
const separator = raw.indexOf(':');
|
|
65
|
-
if (separator <= 0) return null;
|
|
66
|
-
const kind = raw.slice(0, separator);
|
|
67
|
-
const id = raw.slice(separator + 1);
|
|
68
|
-
if (!CRM_BOOKING_REF_KINDS.includes(kind)) return null;
|
|
69
|
-
if (!REF_ID_PATTERN.test(id)) return null;
|
|
70
|
-
return {
|
|
71
|
-
kind: kind,
|
|
72
|
-
id
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
/** The slot, as the meeting entry and the task print it. */ function formatSlot(startsAtMs, timezone) {
|
|
76
|
-
try {
|
|
77
|
-
return new Intl.DateTimeFormat('en-US', {
|
|
78
|
-
timeZone: timezone,
|
|
79
|
-
dateStyle: 'full',
|
|
80
|
-
timeStyle: 'short'
|
|
81
|
-
}).format(new Date(startsAtMs));
|
|
82
|
-
} catch (unused) {
|
|
83
|
-
// An unknown zone name on the service falls back to UTC rather than
|
|
84
|
-
// failing the write: a meeting filed with a slightly wrong clock is
|
|
85
|
-
// better than no meeting at all.
|
|
86
|
-
return new Intl.DateTimeFormat('en-US', {
|
|
87
|
-
timeZone: 'UTC',
|
|
88
|
-
dateStyle: 'full',
|
|
89
|
-
timeStyle: 'short'
|
|
90
|
-
}).format(new Date(startsAtMs));
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* What the meeting entry on the timeline says — the service and the slot,
|
|
95
|
-
* in the service's own timezone, with the zone named so a rep in another
|
|
96
|
-
* one is not misled. The body, not the subject: `subject` is the field a
|
|
97
|
-
* sent email carries, and the timeline draws it as one.
|
|
98
|
-
*/ export function crmBookingMeetingBody(input) {
|
|
99
|
-
var _input_serviceName;
|
|
100
|
-
const timezone = input.timezone || 'UTC';
|
|
101
|
-
const service = String((_input_serviceName = input.serviceName) != null ? _input_serviceName : '').trim() || 'Booking';
|
|
102
|
-
return `${service} — ${formatSlot(input.startsAtMs, timezone)} (${timezone})`;
|
|
103
|
-
}
|
|
104
|
-
/** The title of the task a service files when it asks for a follow-up. */ export function crmBookingFollowUpTitle(serviceName) {
|
|
105
|
-
const service = String(serviceName != null ? serviceName : '').trim() || 'the booking';
|
|
106
|
-
return `Follow up after ${service}`;
|
|
107
|
-
}
|
|
108
|
-
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
109
|
-
/**
|
|
110
|
-
* Weekday (0 = Sunday … 6 = Saturday) of an instant in a timezone, read in
|
|
111
|
-
* UTC when the zone name is one `Intl` does not know — for the reason
|
|
112
|
-
* `formatSlot` gives.
|
|
113
|
-
*/ function weekdayIn(atMs, timezone) {
|
|
114
|
-
return zonedDateTime(atMs, isValidTimeZone(timezone) ? timezone : 'UTC').weekday;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* When the follow-up falls due: ONE BUSINESS DAY after the slot, at the
|
|
118
|
-
* slot's own clock time.
|
|
119
|
-
*
|
|
120
|
-
* A meeting on a Friday is followed up on Monday, one on a Saturday or a
|
|
121
|
-
* Sunday on Monday too; any other day is followed up the next. Whole days
|
|
122
|
-
* are added to the instant rather than a calendar walked, so the arithmetic
|
|
123
|
-
* cannot land on a clock time that does not exist across a DST change —
|
|
124
|
-
* the task is due "about this time tomorrow", and an hour either way is not
|
|
125
|
-
* a fact the task keeps.
|
|
126
|
-
*/ export function crmBookingFollowUpDueMs(endsAtMs, timezone) {
|
|
127
|
-
const weekday = weekdayIn(endsAtMs, timezone || 'UTC');
|
|
128
|
-
// Friday (5) skips the weekend; Saturday (6) reaches Monday in two days.
|
|
129
|
-
const daysAhead = weekday === 5 ? 3 : weekday === 6 ? 2 : 1;
|
|
130
|
-
return endsAtMs + daysAhead * DAY_MS;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
//# sourceMappingURL=crm-booking.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/crm-booking.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * THE BOOKING DOOR BETWEEN THE CRM AND BOOKINGS (AGL-2660) — the pure half.\n *\n * A rep drops a booking link into an email from a record; the visitor books\n * through it; the booking comes back to the record as a meeting on its\n * timeline and, when the service asks for one, a follow-up task. The two\n * plugins never import each other's server code, so everything both sides\n * must agree on — the query key the link carries, the shape of the record\n * reference inside it, the wording of the meeting and the task, the day the\n * task falls due — lives here, in the library both already read.\n */\n\nimport {\n isValidTimeZone,\n zonedDateTime,\n} from '@aglyn/shared-util-timestamp/zoned-time'\n\n/**\n * The query key a booking link carries the record on: `?crm=contact:abc`.\n *\n * The reference rides the LINK rather than relying on the booker's address\n * because the two routinely differ — a rep emails somebody's work address\n * and they book with a personal one — and a booking that could only be\n * matched by address would then land on nobody. The widget copies the value\n * onto the booking request, the booking row keeps it as `crmRef`, and the\n * server files the meeting under the record it names.\n */\nexport const CRM_BOOKING_REF_PARAM = 'crm'\n\n/**\n * The query key that preselects a service in the booking widget:\n * `?service={serviceId}`. The widget lists every active service and this\n * opens it on the one the link was about.\n */\nexport const BOOKING_SERVICE_PARAM = 'service'\n\n/** The three records a booking link can be dropped from. */\nexport const CRM_BOOKING_REF_KINDS = ['contact', 'lead', 'deal'] as const\n\nexport type CrmBookingRefKind = (typeof CRM_BOOKING_REF_KINDS)[number]\n\nexport interface CrmBookingRef {\n kind: CrmBookingRefKind\n id: string\n}\n\n/**\n * What a record id may look like inside the reference. A contact's and a\n * deal's ids are Firestore-minted; a lead's is a person key (hex). Nothing\n * legitimate carries a slash or a space, and a value that does is not a\n * reference the server should go looking for.\n */\nconst REF_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/\n\n/** `contact:abc` — the wire form of a reference. */\nexport function formatCrmBookingRef(ref: CrmBookingRef): string {\n return `${ref.kind}:${ref.id}`\n}\n\n/**\n * The reference a query value names, or `null` for anything that is not one.\n *\n * Strict on purpose: the value arrives on a public, unauthenticated booking\n * request, so an unknown kind or a malformed id is dropped rather than\n * stored — a booking still lands, it simply matches by address instead.\n */\nexport function parseCrmBookingRef(raw: unknown): CrmBookingRef | null {\n if (typeof raw !== 'string') return null\n const separator = raw.indexOf(':')\n if (separator <= 0) return null\n const kind = raw.slice(0, separator)\n const id = raw.slice(separator + 1)\n if (!(CRM_BOOKING_REF_KINDS as readonly string[]).includes(kind)) return null\n if (!REF_ID_PATTERN.test(id)) return null\n return { kind: kind as CrmBookingRefKind, id }\n}\n\n/** The slot, as the meeting entry and the task print it. */\nfunction formatSlot(startsAtMs: number, timezone: string): string {\n try {\n return new Intl.DateTimeFormat('en-US', {\n timeZone: timezone,\n dateStyle: 'full',\n timeStyle: 'short',\n }).format(new Date(startsAtMs))\n } catch {\n // An unknown zone name on the service falls back to UTC rather than\n // failing the write: a meeting filed with a slightly wrong clock is\n // better than no meeting at all.\n return new Intl.DateTimeFormat('en-US', {\n timeZone: 'UTC',\n dateStyle: 'full',\n timeStyle: 'short',\n }).format(new Date(startsAtMs))\n }\n}\n\n/**\n * What the meeting entry on the timeline says — the service and the slot,\n * in the service's own timezone, with the zone named so a rep in another\n * one is not misled. The body, not the subject: `subject` is the field a\n * sent email carries, and the timeline draws it as one.\n */\nexport function crmBookingMeetingBody(input: {\n serviceName: string\n startsAtMs: number\n timezone?: string | null\n}): string {\n const timezone = input.timezone || 'UTC'\n const service = String(input.serviceName ?? '').trim() || 'Booking'\n return `${service} — ${formatSlot(input.startsAtMs, timezone)} (${timezone})`\n}\n\n/** The title of the task a service files when it asks for a follow-up. */\nexport function crmBookingFollowUpTitle(serviceName: string): string {\n const service = String(serviceName ?? '').trim() || 'the booking'\n return `Follow up after ${service}`\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\n/**\n * Weekday (0 = Sunday … 6 = Saturday) of an instant in a timezone, read in\n * UTC when the zone name is one `Intl` does not know — for the reason\n * `formatSlot` gives.\n */\nfunction weekdayIn(atMs: number, timezone: string): number {\n return zonedDateTime(atMs, isValidTimeZone(timezone) ? timezone : 'UTC').weekday\n}\n\n/**\n * When the follow-up falls due: ONE BUSINESS DAY after the slot, at the\n * slot's own clock time.\n *\n * A meeting on a Friday is followed up on Monday, one on a Saturday or a\n * Sunday on Monday too; any other day is followed up the next. Whole days\n * are added to the instant rather than a calendar walked, so the arithmetic\n * cannot land on a clock time that does not exist across a DST change —\n * the task is due \"about this time tomorrow\", and an hour either way is not\n * a fact the task keeps.\n */\nexport function crmBookingFollowUpDueMs(\n endsAtMs: number,\n timezone?: string | null,\n): number {\n const weekday = weekdayIn(endsAtMs, timezone || 'UTC')\n // Friday (5) skips the weekend; Saturday (6) reaches Monday in two days.\n const daysAhead = weekday === 5 ? 3 : weekday === 6 ? 2 : 1\n return endsAtMs + daysAhead * DAY_MS\n}\n"],"names":["isValidTimeZone","zonedDateTime","CRM_BOOKING_REF_PARAM","BOOKING_SERVICE_PARAM","CRM_BOOKING_REF_KINDS","REF_ID_PATTERN","formatCrmBookingRef","ref","kind","id","parseCrmBookingRef","raw","separator","indexOf","slice","includes","test","formatSlot","startsAtMs","timezone","Intl","DateTimeFormat","timeZone","dateStyle","timeStyle","format","Date","crmBookingMeetingBody","input","service","String","serviceName","trim","crmBookingFollowUpTitle","DAY_MS","weekdayIn","atMs","weekday","crmBookingFollowUpDueMs","endsAtMs","daysAhead"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;CAUC,GAED,SACEA,eAAe,EACfC,aAAa,QACR,0CAAyC;AAEhD;;;;;;;;;CASC,GACD,OAAO,MAAMC,wBAAwB,MAAK;AAE1C;;;;CAIC,GACD,OAAO,MAAMC,wBAAwB,UAAS;AAE9C,0DAA0D,GAC1D,OAAO,MAAMC,wBAAwB;IAAC;IAAW;IAAQ;CAAO,CAAS;AASzE;;;;;CAKC,GACD,MAAMC,iBAAiB;AAEvB,kDAAkD,GAClD,OAAO,SAASC,oBAAoBC,GAAkB;IACpD,OAAO,GAAGA,IAAIC,IAAI,CAAC,CAAC,EAAED,IAAIE,EAAE,EAAE;AAChC;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,mBAAmBC,GAAY;IAC7C,IAAI,OAAOA,QAAQ,UAAU,OAAO;IACpC,MAAMC,YAAYD,IAAIE,OAAO,CAAC;IAC9B,IAAID,aAAa,GAAG,OAAO;IAC3B,MAAMJ,OAAOG,IAAIG,KAAK,CAAC,GAAGF;IAC1B,MAAMH,KAAKE,IAAIG,KAAK,CAACF,YAAY;IACjC,IAAI,CAAC,AAACR,sBAA4CW,QAAQ,CAACP,OAAO,OAAO;IACzE,IAAI,CAACH,eAAeW,IAAI,CAACP,KAAK,OAAO;IACrC,OAAO;QAAED,MAAMA;QAA2BC;IAAG;AAC/C;AAEA,0DAA0D,GAC1D,SAASQ,WAAWC,UAAkB,EAAEC,QAAgB;IACtD,IAAI;QACF,OAAO,IAAIC,KAAKC,cAAc,CAAC,SAAS;YACtCC,UAAUH;YACVI,WAAW;YACXC,WAAW;QACb,GAAGC,MAAM,CAAC,IAAIC,KAAKR;IACrB,EAAE,eAAM;QACN,oEAAoE;QACpE,oEAAoE;QACpE,iCAAiC;QACjC,OAAO,IAAIE,KAAKC,cAAc,CAAC,SAAS;YACtCC,UAAU;YACVC,WAAW;YACXC,WAAW;QACb,GAAGC,MAAM,CAAC,IAAIC,KAAKR;IACrB;AACF;AAEA;;;;;CAKC,GACD,OAAO,SAASS,sBAAsBC,KAIrC;QAEwBA;IADvB,MAAMT,WAAWS,MAAMT,QAAQ,IAAI;IACnC,MAAMU,UAAUC,QAAOF,qBAAAA,MAAMG,WAAW,YAAjBH,qBAAqB,IAAII,IAAI,MAAM;IAC1D,OAAO,GAAGH,QAAQ,GAAG,EAAEZ,WAAWW,MAAMV,UAAU,EAAEC,UAAU,EAAE,EAAEA,SAAS,CAAC,CAAC;AAC/E;AAEA,wEAAwE,GACxE,OAAO,SAASc,wBAAwBF,WAAmB;IACzD,MAAMF,UAAUC,OAAOC,sBAAAA,cAAe,IAAIC,IAAI,MAAM;IACpD,OAAO,CAAC,gBAAgB,EAAEH,SAAS;AACrC;AAEA,MAAMK,SAAS,KAAK,KAAK,KAAK;AAE9B;;;;CAIC,GACD,SAASC,UAAUC,IAAY,EAAEjB,QAAgB;IAC/C,OAAOlB,cAAcmC,MAAMpC,gBAAgBmB,YAAYA,WAAW,OAAOkB,OAAO;AAClF;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASC,wBACdC,QAAgB,EAChBpB,QAAwB;IAExB,MAAMkB,UAAUF,UAAUI,UAAUpB,YAAY;IAChD,yEAAyE;IACzE,MAAMqB,YAAYH,YAAY,IAAI,IAAIA,YAAY,IAAI,IAAI;IAC1D,OAAOE,WAAWC,YAAYN;AAChC"}
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
import { type CrmActivity, type CrmActivityLink, type CrmEmailDirection } from './crm';
|
|
18
|
-
import { type MemberAddresses } from './member-email-aliases';
|
|
19
|
-
/**
|
|
20
|
-
* EMAIL CAPTURE (AGL-2657): the pure half.
|
|
21
|
-
*
|
|
22
|
-
* A workspace gets one address — `crm+<token>@<capture domain>` — and a
|
|
23
|
-
* reply forwarded to it, or a message a teammate copies to it from their
|
|
24
|
-
* own mailbox, is filed on the record of whoever the message was with.
|
|
25
|
-
* This module is everything about that which needs no Firestore and no
|
|
26
|
-
* provider: the shape of the address, the token in it, which of a
|
|
27
|
-
* message's addresses is the correspondent, what of the body is worth
|
|
28
|
-
* keeping, and the activity row the whole thing becomes. The webhook route
|
|
29
|
-
* and the address route read it; the dialog and the settings card read the
|
|
30
|
-
* address helpers; the specs read all of it.
|
|
31
|
-
*
|
|
32
|
-
* ## The token is the whole secret
|
|
33
|
-
*
|
|
34
|
-
* The address is public the moment a member pastes it into a mailbox rule,
|
|
35
|
-
* and the domain is the platform's, so the token is the only thing that
|
|
36
|
-
* says which workspace a message is for. Thirty-two characters from a
|
|
37
|
-
* lowercase alphabet — mail servers may fold the local part's case, and a
|
|
38
|
-
* token that survived folding is one that never depended on case — drawn
|
|
39
|
-
* from the platform's random source, which is the same entropy an API key
|
|
40
|
-
* carries. Rotation replaces it; the old address then files nothing.
|
|
41
|
-
*
|
|
42
|
-
* ## Who the message was with
|
|
43
|
-
*
|
|
44
|
-
* A message names several addresses and exactly one of them is the person
|
|
45
|
-
* the record is about. The rule: the first address among From, To and Cc
|
|
46
|
-
* that is not the capture address and not a member of the workspace — the
|
|
47
|
-
* workspace's own people are never the correspondent — in that order,
|
|
48
|
-
* because a reply's From is the correspondent and a copied send's To is.
|
|
49
|
-
* A member is every address that is theirs: the one they sign in with and
|
|
50
|
-
* each alias they have confirmed (`member-email-aliases.ts`), so a send
|
|
51
|
-
* from an outbound-domain alias is read as the member's, not as a stranger.
|
|
52
|
-
* A message a teammate forwarded names the correspondent nowhere in its
|
|
53
|
-
* headers, only in the forwarded block of the body, so that block's `From:`
|
|
54
|
-
* is the last candidate. Which candidate matched decides the direction:
|
|
55
|
-
* one the correspondent wrote is `inbound`, one the workspace wrote is
|
|
56
|
-
* `outbound`.
|
|
57
|
-
*/
|
|
58
|
-
/** Where captured mail arrives when the deployment names no domain. */
|
|
59
|
-
export declare const CRM_INBOUND_DEFAULT_DOMAIN = "in.aglyn.com";
|
|
60
|
-
/** The local part every capture address begins with: `crm+<token>@…`. */
|
|
61
|
-
export declare const CRM_INBOUND_LOCAL_PART = "crm";
|
|
62
|
-
/** How many characters a minted token has. */
|
|
63
|
-
export declare const CRM_INBOUND_TOKEN_LENGTH = 32;
|
|
64
|
-
/** The most a captured message's excerpt keeps: a letter, not a thread. */
|
|
65
|
-
export declare const CRM_INBOUND_EXCERPT_MAX = 4000;
|
|
66
|
-
/** What the org's feed says about a message that matched no record. */
|
|
67
|
-
export declare const CRM_INBOUND_UNMATCHED_ACTION = "Inbound email matched no record";
|
|
68
|
-
/** What the org's feed says about a message the record's ceiling refused. */
|
|
69
|
-
export declare const CRM_INBOUND_CEILING_ACTION = "Inbound email not filed: activity log full";
|
|
70
|
-
/**
|
|
71
|
-
* The domain captured mail is addressed at: `CRM_INBOUND_DOMAIN`, or the
|
|
72
|
-
* platform's default when the deployment names none or names something
|
|
73
|
-
* that is not a hostname. Lowercased, because a domain is.
|
|
74
|
-
*/
|
|
75
|
-
export declare function crmInboundDomain(env?: Record<string, string | undefined>): string;
|
|
76
|
-
/**
|
|
77
|
-
* A fresh token from the platform's random source. One byte per character
|
|
78
|
-
* folded onto a 36-symbol alphabet: the fold is very slightly uneven and
|
|
79
|
-
* the token is thirty-two symbols long, which is more than an API key
|
|
80
|
-
* carries and far more than an address anyone guesses.
|
|
81
|
-
*/
|
|
82
|
-
export declare function mintCrmInboundToken(): string;
|
|
83
|
-
export declare function isCrmInboundToken(value: unknown): value is string;
|
|
84
|
-
/** `crm+<token>@<domain>` — the address a member pastes into a mailbox. */
|
|
85
|
-
export declare function crmInboundAddress(token: string, domain: string): string;
|
|
86
|
-
/**
|
|
87
|
-
* The bare address in a header value — `Ada <ada@example.com>` and
|
|
88
|
-
* `ada@example.com` both answer `ada@example.com` — normalized as every
|
|
89
|
-
* contact address is, or `null` for anything that is not one.
|
|
90
|
-
*/
|
|
91
|
-
export declare function emailAddressOf(value: unknown): string | null;
|
|
92
|
-
/** The part after the `@`, or `null` — what an unmatched message is logged by. */
|
|
93
|
-
export declare function emailDomainOf(value: unknown): string | null;
|
|
94
|
-
/**
|
|
95
|
-
* The token a recipient address carries, or `null` when the address is not
|
|
96
|
-
* a capture address on `domain`. The local part is read case-folded — a
|
|
97
|
-
* relay may fold it and the alphabet has no case to lose — and the domain
|
|
98
|
-
* is compared the way domains compare.
|
|
99
|
-
*/
|
|
100
|
-
export declare function crmInboundTokenOf(recipient: unknown, domain: string): string | null;
|
|
101
|
-
/** Every distinct token among a message's recipients, in the order met. */
|
|
102
|
-
export declare function crmInboundTokensIn(recipients: readonly unknown[], domain: string): string[];
|
|
103
|
-
export declare function isCrmInboundAddress(address: unknown, domain: string): boolean;
|
|
104
|
-
/**
|
|
105
|
-
* The subject with its reply and forward prefixes removed, however many
|
|
106
|
-
* were stacked — `Re: Fwd: RE: Renewal` is the `Renewal` thread — and
|
|
107
|
-
* whitespace collapsed, so two rows of one conversation carry one value.
|
|
108
|
-
*/
|
|
109
|
-
export declare function crmThreadSubject(subject: unknown): string;
|
|
110
|
-
/**
|
|
111
|
-
* The words in an HTML part, for a message that carried no text part.
|
|
112
|
-
* Block boundaries become line breaks, tags go, the common entities are
|
|
113
|
-
* read back — a best effort over a bounded slice, never a renderer.
|
|
114
|
-
*/
|
|
115
|
-
export declare function htmlToPlainText(html: unknown): string;
|
|
116
|
-
/** What a forwarded message's header block says, and the words after it. */
|
|
117
|
-
export interface ForwardedSection {
|
|
118
|
-
/** The address on the block's `From:` line, or `null` when it carried none. */
|
|
119
|
-
from: string | null;
|
|
120
|
-
/** The forwarded message's own text, header block removed. */
|
|
121
|
-
body: string;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* The forwarded message inside a forward — the block a mail client puts
|
|
125
|
-
* under "Forwarded message" or, with no marker, a run of `From:` … `Subject:`
|
|
126
|
-
* header lines — or `null` when the text carries none. The `From:` line is
|
|
127
|
-
* where the correspondent of a forwarded reply is named, since the forward's
|
|
128
|
-
* own headers name only the teammate who forwarded it.
|
|
129
|
-
*/
|
|
130
|
-
export declare function forwardedSection(text: string): ForwardedSection | null;
|
|
131
|
-
/**
|
|
132
|
-
* The words above the quoted history: everything before the first `On …
|
|
133
|
-
* wrote:` line, quote mark, "Original Message" rule or reply header block.
|
|
134
|
-
* The intro may wrap onto a second line, so a line that begins `On` is
|
|
135
|
-
* read together with the one after it.
|
|
136
|
-
*/
|
|
137
|
-
export declare function stripQuotedHistory(text: string): string;
|
|
138
|
-
/**
|
|
139
|
-
* What the timeline keeps of a captured message: the text part — or the
|
|
140
|
-
* words of the HTML part when there is none — with a forward's header
|
|
141
|
-
* block and the quoted history below the reply removed, whitespace
|
|
142
|
-
* settled, bounded. Never the whole thread: the thread is in the mailbox,
|
|
143
|
-
* and the row is a note that this exchange happened.
|
|
144
|
-
*/
|
|
145
|
-
export declare function crmInboundExcerpt(text: unknown, html?: unknown): string;
|
|
146
|
-
/** One address the message might be with, in the order the rule tries them. */
|
|
147
|
-
export interface CrmInboundCandidate {
|
|
148
|
-
email: string;
|
|
149
|
-
/** What a row filed on a match with this address says about the message. */
|
|
150
|
-
direction: CrmEmailDirection;
|
|
151
|
-
/** Which header the address came from, for the spec and the log. */
|
|
152
|
-
via: 'from' | 'to' | 'cc' | 'forwarded';
|
|
153
|
-
}
|
|
154
|
-
/** One member of the workspace, as the capture filer reads them. */
|
|
155
|
-
export interface CrmInboundMember extends MemberAddresses {
|
|
156
|
-
uid: string;
|
|
157
|
-
/** The address they sign in with; `''` for a roster row that carries none. */
|
|
158
|
-
email: string;
|
|
159
|
-
name?: string | null;
|
|
160
|
-
}
|
|
161
|
-
/** A roster row as {@link crmInboundRoster} reads it — `AglynOrgMember`'s fields. */
|
|
162
|
-
export interface CrmInboundRosterRow {
|
|
163
|
-
$id: string;
|
|
164
|
-
email?: string | null;
|
|
165
|
-
displayName?: string | null;
|
|
166
|
-
orgSuspended?: boolean;
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* The roster a captured message is matched against: every roster row that
|
|
170
|
-
* does not carry the org's suspension flag, each with the address the
|
|
171
|
-
* member signs in with and the aliases they have CONFIRMED, read off their
|
|
172
|
-
* stored alias document by uid. The confirmed filter is applied here, from
|
|
173
|
-
* the stored documents, so no caller can hand the rule an address a member
|
|
174
|
-
* only typed. An alias document with no roster row beside it belongs to
|
|
175
|
-
* somebody who is no longer a member, and is never read.
|
|
176
|
-
*/
|
|
177
|
-
export declare function crmInboundRoster(rows: readonly CrmInboundRosterRow[], aliasDocuments: ReadonlyMap<string, unknown>): CrmInboundMember[];
|
|
178
|
-
export interface CrmInboundCandidatesInput {
|
|
179
|
-
from: unknown;
|
|
180
|
-
to: readonly unknown[];
|
|
181
|
-
cc?: readonly unknown[];
|
|
182
|
-
/** The forwarded block's `From:`, when the body carried one. */
|
|
183
|
-
forwardedFrom?: unknown;
|
|
184
|
-
/** The capture domain, so the capture address itself is never a candidate. */
|
|
185
|
-
domain: string;
|
|
186
|
-
/**
|
|
187
|
-
* The workspace's roster. Every address that is a member's — the one
|
|
188
|
-
* they sign in with and each alias they have confirmed — is theirs, and
|
|
189
|
-
* none of them is ever a correspondent.
|
|
190
|
-
*/
|
|
191
|
-
members: readonly MemberAddresses[];
|
|
192
|
-
}
|
|
193
|
-
export interface CrmInboundCandidates {
|
|
194
|
-
/** The normalized From, or `null` when the header carried no address. */
|
|
195
|
-
sender: string | null;
|
|
196
|
-
/** Whether a member of the workspace wrote the message. */
|
|
197
|
-
senderIsMember: boolean;
|
|
198
|
-
candidates: CrmInboundCandidate[];
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* The addresses to try against the workspace's records, in order — see the
|
|
202
|
-
* module comment — each already stamped with the direction a match would
|
|
203
|
-
* mean: a message a member wrote went OUT to whoever it matches, and one
|
|
204
|
-
* anybody else wrote came IN from them.
|
|
205
|
-
*/
|
|
206
|
-
export declare function crmInboundCandidates(input: CrmInboundCandidatesInput): CrmInboundCandidates;
|
|
207
|
-
/**
|
|
208
|
-
* The key one message is filed under, so a second delivery of it is a
|
|
209
|
-
* no-op: its `Message-ID`, which every mail client stamps and every relay
|
|
210
|
-
* preserves, else the provider's own id, which a redelivery of one
|
|
211
|
-
* webhook event repeats. `null` when the message has neither — a message
|
|
212
|
-
* that cannot be told from its own redelivery is filed by a fresh id.
|
|
213
|
-
*/
|
|
214
|
-
export declare function crmCapturedEmailKey(messageId: unknown, providerId: unknown): string | null;
|
|
215
|
-
/** What a captured message is logged from — see {@link buildCrmCapturedEmailActivity}. */
|
|
216
|
-
export interface CrmCapturedEmailInput {
|
|
217
|
-
direction: CrmEmailDirection;
|
|
218
|
-
subject: string;
|
|
219
|
-
/** The excerpt, already reduced by {@link crmInboundExcerpt}. */
|
|
220
|
-
excerpt: string;
|
|
221
|
-
/** The address on the message's From. */
|
|
222
|
-
from: string;
|
|
223
|
-
/** The address the message was addressed to, when one is worth showing. */
|
|
224
|
-
to?: string | null;
|
|
225
|
-
messageId: string;
|
|
226
|
-
inReplyTo?: string | null;
|
|
227
|
-
/** When the message was received. */
|
|
228
|
-
atMs: number;
|
|
229
|
-
/** The member who wrote it, or `''` for a message a correspondent wrote. */
|
|
230
|
-
byUid: string;
|
|
231
|
-
byName?: string | null;
|
|
232
|
-
link: CrmActivityLink;
|
|
233
|
-
hostId: string;
|
|
234
|
-
visibleTo: readonly string[];
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* The activity row a captured message is filed as (AGL-2657): `kind:
|
|
238
|
-
* 'email'`, with the direction the match decided, the excerpt for a body,
|
|
239
|
-
* and the thread facts a later reader groups by. No delivery state — the
|
|
240
|
-
* platform did not send it and can say nothing about where it got to —
|
|
241
|
-
* which is also how the timeline tells a captured send from a platform
|
|
242
|
-
* send. The same shape `buildCrmEmailActivity` writes, so the timeline
|
|
243
|
-
* draws both with one row.
|
|
244
|
-
*/
|
|
245
|
-
export declare function buildCrmCapturedEmailActivity(input: CrmCapturedEmailInput): CrmActivity;
|