@aglyn/shared-util-email 1.0.0-beta.290 → 1.0.0-beta.292
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 +3 -3
- package/src/index.d.ts +1 -0
- package/src/index.js +3 -0
- package/src/index.js.map +1 -1
- package/src/lib/email-thread.d.ts +86 -0
- package/src/lib/email-thread.js +187 -0
- package/src/lib/email-thread.js.map +1 -0
- package/src/lib/tenant-emails.generated.js +321 -0
- package/src/lib/tenant-emails.generated.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/shared-util-email",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.292",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
28
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.292",
|
|
29
|
+
"@aglyn/shared-util-http": "1.0.0-beta.292",
|
|
30
30
|
"@msgpack/msgpack": "^3.1.3",
|
|
31
31
|
"@swc/helpers": "0.5.23"
|
|
32
32
|
},
|
package/src/index.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export * from './lib/host-email-render';
|
|
|
39
39
|
export * from './lib/email-render';
|
|
40
40
|
export * from './lib/text-email-html';
|
|
41
41
|
export * from './lib/marketing-send';
|
|
42
|
+
export * from './lib/email-thread';
|
|
42
43
|
export * from './lib/list-unsubscribe';
|
|
43
44
|
export * from './lib/email-delivery-events';
|
|
44
45
|
export * from './lib/received-email';
|
package/src/index.js
CHANGED
|
@@ -43,6 +43,9 @@ export * from "./lib/host-email-render.js";
|
|
|
43
43
|
export * from "./lib/email-render.js";
|
|
44
44
|
export * from "./lib/text-email-html.js";
|
|
45
45
|
export * from "./lib/marketing-send.js";
|
|
46
|
+
// One conversation in the recipient's mailbox (AGL-4097): Message-ID,
|
|
47
|
+
// In-Reply-To and References for a record's later messages. Pure.
|
|
48
|
+
export * from "./lib/email-thread.js";
|
|
46
49
|
// The List-Unsubscribe setting sequences and campaigns share (AGL-3307).
|
|
47
50
|
export * from "./lib/list-unsubscribe.js";
|
|
48
51
|
export * from "./lib/email-delivery-events.js";
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/shared/util/email/src/index.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\nexport * from './lib/send-email'\n// Who carries the mail (AGL-3080): the provider contract, and the selection\n// of the deployment's provider from the built-ins and any a plugin\n// registered. The providers themselves are imported by their own paths —\n// `@aglyn/shared-util-email/mail-provider-resend`, `…/mail-provider-webhook`\n// — because nothing outside the seam should name one.\nexport * from './lib/mail-provider'\nexport * from './lib/mail-providers'\nexport * from './lib/send-rate'\nexport * from './lib/sender-reputation'\nexport * from './lib/send-ceilings'\nexport * from './lib/sending-domain'\nexport * from './lib/tracking-host'\nexport * from './lib/sending-mailbox'\nexport * from './lib/host-sender'\nexport * from './lib/platform-sending-domain'\nexport * from './lib/email-health'\nexport * from './lib/system-email-catalog'\nexport * from './lib/retention-emails'\nexport * from './lib/site-care-emails'\nexport * from './lib/site-care-project-emails'\nexport * from './lib/domain-emails'\nexport * from './lib/pos-hardware-emails'\nexport * from './lib/pos-cellular-emails'\nexport * from './lib/welcome-email'\nexport * from './lib/tenant-email-catalog'\nexport * from './lib/host-email-render'\nexport * from './lib/email-render'\nexport * from './lib/text-email-html'\nexport * from './lib/marketing-send'\n// The List-Unsubscribe setting sequences and campaigns share (AGL-3307).\nexport * from './lib/list-unsubscribe'\nexport * from './lib/email-delivery-events'\n// Received mail in the platform's vocabulary, which the CRM's capture route\n// files from (AGL-2657); the provider reads it.\n// Its neighbor `./lib/svix-signature` is deliberately NOT re-exported here:\n// it holds a `crypto` HMAC, and this barrel is reached from the browser\n// through `@aglyn/shared-ui-email-campaigns`, so a webhook imports it by its\n// own path — `@aglyn/shared-util-email/svix-signature`.\nexport * from './lib/received-email'\nexport * from './lib/email-revenue-window'\n// Exported so the console drift guard can pin it against @aglyn/aglyn.\nexport * from './lib/email-media-src'\n// Same arrangement, same reason (AGL-1223): a copy of `decodeStoredNodes`\n// this scope:shared lib may not import, pinned by `email-nodes-drift.spec.ts`.\nexport * from './lib/stored-email-nodes'\nexport * from './lib/email-merge'\n// The deliverability engine every send path asks (AGL-3328): the gateway an\n// MX names and its ledger, the bounce reader, and the address verdicts. Pure\n// — the DNS lookup and the store are injected by `@aglyn/tenant-data-admin`,\n// so nothing here reaches `node:dns` from the browser.\nexport * from './lib/mail-gateway'\nexport * from './lib/mail-bounce'\nexport * from './lib/email-deliverability'\n// The sender's side of the same engine (AGL-3328): SPF, DKIM and DMARC for\n// the domain mail leaves from, and whether they align. Pure as well; the\n// lookups are `@aglyn/tenant-data-admin`'s.\nexport * from './lib/sender-readiness'\n// The outbound phishing screen a young workspace's campaigns and automated\n// email pass (AGL-3356). Pure; the hold it leads to is `tenant-data-admin`'s.\nexport * from './lib/outbound-phishing-screen'\n// The send seam's gate for that screen — the store a hold writes is injected\n// by `@aglyn/tenant-data-admin`, like the governor's.\nexport * from './lib/outbound-screen-gate'\n// The same screen read over a published page (AGL-3356): lookalike links and\n// embeds, author-defined credential fields, a brand's call to action.\nexport * from './lib/hosted-page-screen'\n// The reputation lookup every foreign link a page, an email or a redirect\n// points at passes (AGL-3451). Pure; the lookup is injected by\n// `@aglyn/tenant-data-admin`, which holds the credential and the cache.\nexport * from './lib/link-reputation'\n// The site's theme palette as plain data (AGL-3370), so a palette token an\n// author picked resolves in mail. Built without MUI; `shared-ui-theme`'s\n// parity spec holds it equal to the theme.\nexport * from './lib/email-palette'\nexport * from './lib/email-design-chrome'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,cAAc,sBAAkB;AAChC,4EAA4E;AAC5E,mEAAmE;AACnE,yEAAyE;AACzE,6EAA6E;AAC7E,sDAAsD;AACtD,cAAc,yBAAqB;AACnC,cAAc,0BAAsB;AACpC,cAAc,qBAAiB;AAC/B,cAAc,6BAAyB;AACvC,cAAc,yBAAqB;AACnC,cAAc,0BAAsB;AACpC,cAAc,yBAAqB;AACnC,cAAc,2BAAuB;AACrC,cAAc,uBAAmB;AACjC,cAAc,mCAA+B;AAC7C,cAAc,wBAAoB;AAClC,cAAc,gCAA4B;AAC1C,cAAc,4BAAwB;AACtC,cAAc,4BAAwB;AACtC,cAAc,oCAAgC;AAC9C,cAAc,yBAAqB;AACnC,cAAc,+BAA2B;AACzC,cAAc,+BAA2B;AACzC,cAAc,yBAAqB;AACnC,cAAc,gCAA4B;AAC1C,cAAc,6BAAyB;AACvC,cAAc,wBAAoB;AAClC,cAAc,2BAAuB;AACrC,cAAc,0BAAsB;AACpC,yEAAyE;AACzE,cAAc,4BAAwB;AACtC,cAAc,iCAA6B;AAC3C,4EAA4E;AAC5E,gDAAgD;AAChD,4EAA4E;AAC5E,wEAAwE;AACxE,6EAA6E;AAC7E,wDAAwD;AACxD,cAAc,0BAAsB;AACpC,cAAc,gCAA4B;AAC1C,uEAAuE;AACvE,cAAc,2BAAuB;AACrC,0EAA0E;AAC1E,+EAA+E;AAC/E,cAAc,8BAA0B;AACxC,cAAc,uBAAmB;AACjC,4EAA4E;AAC5E,6EAA6E;AAC7E,6EAA6E;AAC7E,uDAAuD;AACvD,cAAc,wBAAoB;AAClC,cAAc,uBAAmB;AACjC,cAAc,gCAA4B;AAC1C,2EAA2E;AAC3E,yEAAyE;AACzE,4CAA4C;AAC5C,cAAc,4BAAwB;AACtC,2EAA2E;AAC3E,8EAA8E;AAC9E,cAAc,oCAAgC;AAC9C,6EAA6E;AAC7E,sDAAsD;AACtD,cAAc,gCAA4B;AAC1C,6EAA6E;AAC7E,sEAAsE;AACtE,cAAc,8BAA0B;AACxC,0EAA0E;AAC1E,+DAA+D;AAC/D,wEAAwE;AACxE,cAAc,2BAAuB;AACrC,2EAA2E;AAC3E,yEAAyE;AACzE,2CAA2C;AAC3C,cAAc,yBAAqB;AACnC,cAAc,+BAA2B"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/shared/util/email/src/index.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\nexport * from './lib/send-email'\n// Who carries the mail (AGL-3080): the provider contract, and the selection\n// of the deployment's provider from the built-ins and any a plugin\n// registered. The providers themselves are imported by their own paths —\n// `@aglyn/shared-util-email/mail-provider-resend`, `…/mail-provider-webhook`\n// — because nothing outside the seam should name one.\nexport * from './lib/mail-provider'\nexport * from './lib/mail-providers'\nexport * from './lib/send-rate'\nexport * from './lib/sender-reputation'\nexport * from './lib/send-ceilings'\nexport * from './lib/sending-domain'\nexport * from './lib/tracking-host'\nexport * from './lib/sending-mailbox'\nexport * from './lib/host-sender'\nexport * from './lib/platform-sending-domain'\nexport * from './lib/email-health'\nexport * from './lib/system-email-catalog'\nexport * from './lib/retention-emails'\nexport * from './lib/site-care-emails'\nexport * from './lib/site-care-project-emails'\nexport * from './lib/domain-emails'\nexport * from './lib/pos-hardware-emails'\nexport * from './lib/pos-cellular-emails'\nexport * from './lib/welcome-email'\nexport * from './lib/tenant-email-catalog'\nexport * from './lib/host-email-render'\nexport * from './lib/email-render'\nexport * from './lib/text-email-html'\nexport * from './lib/marketing-send'\n// One conversation in the recipient's mailbox (AGL-4097): Message-ID,\n// In-Reply-To and References for a record's later messages. Pure.\nexport * from './lib/email-thread'\n// The List-Unsubscribe setting sequences and campaigns share (AGL-3307).\nexport * from './lib/list-unsubscribe'\nexport * from './lib/email-delivery-events'\n// Received mail in the platform's vocabulary, which the CRM's capture route\n// files from (AGL-2657); the provider reads it.\n// Its neighbor `./lib/svix-signature` is deliberately NOT re-exported here:\n// it holds a `crypto` HMAC, and this barrel is reached from the browser\n// through `@aglyn/shared-ui-email-campaigns`, so a webhook imports it by its\n// own path — `@aglyn/shared-util-email/svix-signature`.\nexport * from './lib/received-email'\nexport * from './lib/email-revenue-window'\n// Exported so the console drift guard can pin it against @aglyn/aglyn.\nexport * from './lib/email-media-src'\n// Same arrangement, same reason (AGL-1223): a copy of `decodeStoredNodes`\n// this scope:shared lib may not import, pinned by `email-nodes-drift.spec.ts`.\nexport * from './lib/stored-email-nodes'\nexport * from './lib/email-merge'\n// The deliverability engine every send path asks (AGL-3328): the gateway an\n// MX names and its ledger, the bounce reader, and the address verdicts. Pure\n// — the DNS lookup and the store are injected by `@aglyn/tenant-data-admin`,\n// so nothing here reaches `node:dns` from the browser.\nexport * from './lib/mail-gateway'\nexport * from './lib/mail-bounce'\nexport * from './lib/email-deliverability'\n// The sender's side of the same engine (AGL-3328): SPF, DKIM and DMARC for\n// the domain mail leaves from, and whether they align. Pure as well; the\n// lookups are `@aglyn/tenant-data-admin`'s.\nexport * from './lib/sender-readiness'\n// The outbound phishing screen a young workspace's campaigns and automated\n// email pass (AGL-3356). Pure; the hold it leads to is `tenant-data-admin`'s.\nexport * from './lib/outbound-phishing-screen'\n// The send seam's gate for that screen — the store a hold writes is injected\n// by `@aglyn/tenant-data-admin`, like the governor's.\nexport * from './lib/outbound-screen-gate'\n// The same screen read over a published page (AGL-3356): lookalike links and\n// embeds, author-defined credential fields, a brand's call to action.\nexport * from './lib/hosted-page-screen'\n// The reputation lookup every foreign link a page, an email or a redirect\n// points at passes (AGL-3451). Pure; the lookup is injected by\n// `@aglyn/tenant-data-admin`, which holds the credential and the cache.\nexport * from './lib/link-reputation'\n// The site's theme palette as plain data (AGL-3370), so a palette token an\n// author picked resolves in mail. Built without MUI; `shared-ui-theme`'s\n// parity spec holds it equal to the theme.\nexport * from './lib/email-palette'\nexport * from './lib/email-design-chrome'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,cAAc,sBAAkB;AAChC,4EAA4E;AAC5E,mEAAmE;AACnE,yEAAyE;AACzE,6EAA6E;AAC7E,sDAAsD;AACtD,cAAc,yBAAqB;AACnC,cAAc,0BAAsB;AACpC,cAAc,qBAAiB;AAC/B,cAAc,6BAAyB;AACvC,cAAc,yBAAqB;AACnC,cAAc,0BAAsB;AACpC,cAAc,yBAAqB;AACnC,cAAc,2BAAuB;AACrC,cAAc,uBAAmB;AACjC,cAAc,mCAA+B;AAC7C,cAAc,wBAAoB;AAClC,cAAc,gCAA4B;AAC1C,cAAc,4BAAwB;AACtC,cAAc,4BAAwB;AACtC,cAAc,oCAAgC;AAC9C,cAAc,yBAAqB;AACnC,cAAc,+BAA2B;AACzC,cAAc,+BAA2B;AACzC,cAAc,yBAAqB;AACnC,cAAc,gCAA4B;AAC1C,cAAc,6BAAyB;AACvC,cAAc,wBAAoB;AAClC,cAAc,2BAAuB;AACrC,cAAc,0BAAsB;AACpC,sEAAsE;AACtE,kEAAkE;AAClE,cAAc,wBAAoB;AAClC,yEAAyE;AACzE,cAAc,4BAAwB;AACtC,cAAc,iCAA6B;AAC3C,4EAA4E;AAC5E,gDAAgD;AAChD,4EAA4E;AAC5E,wEAAwE;AACxE,6EAA6E;AAC7E,wDAAwD;AACxD,cAAc,0BAAsB;AACpC,cAAc,gCAA4B;AAC1C,uEAAuE;AACvE,cAAc,2BAAuB;AACrC,0EAA0E;AAC1E,+EAA+E;AAC/E,cAAc,8BAA0B;AACxC,cAAc,uBAAmB;AACjC,4EAA4E;AAC5E,6EAA6E;AAC7E,6EAA6E;AAC7E,uDAAuD;AACvD,cAAc,wBAAoB;AAClC,cAAc,uBAAmB;AACjC,cAAc,gCAA4B;AAC1C,2EAA2E;AAC3E,yEAAyE;AACzE,4CAA4C;AAC5C,cAAc,4BAAwB;AACtC,2EAA2E;AAC3E,8EAA8E;AAC9E,cAAc,oCAAgC;AAC9C,6EAA6E;AAC7E,sDAAsD;AACtD,cAAc,gCAA4B;AAC1C,6EAA6E;AAC7E,sEAAsE;AACtE,cAAc,8BAA0B;AACxC,0EAA0E;AAC1E,+DAA+D;AAC/D,wEAAwE;AACxE,cAAc,2BAAuB;AACrC,2EAA2E;AAC3E,yEAAyE;AACzE,2CAA2C;AAC3C,cAAc,yBAAqB;AACnC,cAAc,+BAA2B"}
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
/** A conversation as the record it is about keeps it. */
|
|
18
|
+
export interface EmailThread {
|
|
19
|
+
/** The latest message's `Message-ID`, angle brackets included: `<a.b@example.com>`. */
|
|
20
|
+
messageId: string;
|
|
21
|
+
/**
|
|
22
|
+
* The conversation's root ({@link emailThreadRootId}; a thread stored
|
|
23
|
+
* before roots has none), then every message's `Message-ID`, oldest first;
|
|
24
|
+
* the last is {@link messageId}.
|
|
25
|
+
*/
|
|
26
|
+
references: string[];
|
|
27
|
+
/** The subject the conversation runs under, without any `Re:`. */
|
|
28
|
+
subject: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The most ids a `References` header carries. RFC 5322 sets no limit, but
|
|
32
|
+
* some servers refuse very long header lines; the first id (the conversation's
|
|
33
|
+
* root) and the latest ones are what threading reads, so the middle goes.
|
|
34
|
+
*/
|
|
35
|
+
export declare const EMAIL_THREAD_MAX_REFERENCES = 20;
|
|
36
|
+
/** Whether `value` is one well-formed `Message-ID`, brackets included. */
|
|
37
|
+
export declare function isEmailMessageId(value: unknown): value is string;
|
|
38
|
+
/**
|
|
39
|
+
* A fresh `Message-ID` on the first usable domain of `domains` (the sending
|
|
40
|
+
* domain first; an address such as a `From` value gives its domain):
|
|
41
|
+
* `<label.k3x9m1.9f2c4e1a0b7d3e65@mail.acme.com>`. The time and 64 random bits
|
|
42
|
+
* keep it unique; nothing in it identifies the customer or the record.
|
|
43
|
+
* Falls back to `localhost.localdomain` only when no domain is usable, which
|
|
44
|
+
* a send on a resolved identity never meets.
|
|
45
|
+
*/
|
|
46
|
+
export declare function mintEmailMessageId(domains: ReadonlyArray<string | null | undefined>, options?: {
|
|
47
|
+
label?: string;
|
|
48
|
+
atMs?: number;
|
|
49
|
+
random?: string;
|
|
50
|
+
}): string;
|
|
51
|
+
/**
|
|
52
|
+
* The root a conversation started by the message `messageId` runs under:
|
|
53
|
+
* `<thread.quote.k3x9m1.9f2c4e1a0b7d3e65@mail.acme.com>` for
|
|
54
|
+
* `<quote.k3x9m1.9f2c4e1a0b7d3e65@mail.acme.com>`. Derived, so the headers
|
|
55
|
+
* the first message goes out with and the thread kept after it agree without
|
|
56
|
+
* either being handed the other; distinct from every id a message carries,
|
|
57
|
+
* so a provider that replaces `Message-ID` cannot replace it. Null when
|
|
58
|
+
* `messageId` is not a `Message-ID`, or the root would be too long for one.
|
|
59
|
+
*/
|
|
60
|
+
export declare function emailThreadRootId(messageId: string): string | null;
|
|
61
|
+
/**
|
|
62
|
+
* A stored thread, checked, or null. Every id must be a well-formed
|
|
63
|
+
* `Message-ID`, because each is written back into a header: a value that
|
|
64
|
+
* could carry a line break never reaches one.
|
|
65
|
+
*/
|
|
66
|
+
export declare function readEmailThread(raw: unknown): EmailThread | null;
|
|
67
|
+
/** The subject a message in `thread` goes under: the thread's, else `fallback`. */
|
|
68
|
+
export declare function emailThreadSubject(thread: EmailThread | null | undefined, fallback: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* The headers that send a message with `Message-ID` `messageId` into
|
|
71
|
+
* `thread`: the id itself, and, when the thread has begun, `In-Reply-To` the
|
|
72
|
+
* latest message and `References` the root and the chain. With no thread, the
|
|
73
|
+
* message starts one: its own id, and the root it starts
|
|
74
|
+
* ({@link emailThreadRootId}) in `References`.
|
|
75
|
+
*/
|
|
76
|
+
export declare function nextThreadHeaders(thread: EmailThread | null | undefined, messageId: string): Record<string, string>;
|
|
77
|
+
/**
|
|
78
|
+
* The thread once a message went out in it: the message is the latest, the
|
|
79
|
+
* chain grows by it, and the subject stays the thread's. A message that
|
|
80
|
+
* started the thread gives it its root (the one {@link nextThreadHeaders}
|
|
81
|
+
* sent it with) ahead of its own id, and its subject.
|
|
82
|
+
*/
|
|
83
|
+
export declare function emailThreadAfterSend(thread: EmailThread | null | undefined, sent: {
|
|
84
|
+
messageId: string;
|
|
85
|
+
subject: string;
|
|
86
|
+
}): EmailThread;
|
|
@@ -0,0 +1,187 @@
|
|
|
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
|
+
* ONE CONVERSATION IN THE CUSTOMER'S MAILBOX (AGL-4097).
|
|
18
|
+
*
|
|
19
|
+
* A site that writes to one customer about one thing more than once (a
|
|
20
|
+
* request's confirmation, the quote, a revised quote, the invoice) keeps those
|
|
21
|
+
* messages in a single conversation by sending each one as a reply to the one
|
|
22
|
+
* before, all of them under one ROOT:
|
|
23
|
+
*
|
|
24
|
+
* - the first message carries a `Message-ID` minted here and, in
|
|
25
|
+
* `References`, the conversation's root: an id derived from that one
|
|
26
|
+
* (`<thread.…@…>`, {@link emailThreadRootId}) which no message carries as
|
|
27
|
+
* its own. The record it is about keeps both as an {@link EmailThread},
|
|
28
|
+
* the root first;
|
|
29
|
+
* - every later message carries its own `Message-ID`, `In-Reply-To` naming
|
|
30
|
+
* the last one, and `References` naming the root and the chain, and keeps
|
|
31
|
+
* the thread's subject ({@link nextThreadHeaders}, {@link emailThreadAfterSend}).
|
|
32
|
+
*
|
|
33
|
+
* Why a root no message carries: a provider may replace the `Message-ID` a
|
|
34
|
+
* caller hands it, and in production one does. The ids a later message then
|
|
35
|
+
* replies to name no message the mailbox ever received, and on those alone
|
|
36
|
+
* the first message splits off into a conversation of its own. What every
|
|
37
|
+
* message still shares is the root in `References`: clients that thread by
|
|
38
|
+
* those headers group the messages under it as a missing parent, and clients
|
|
39
|
+
* that group on a shared reference (with the subject) group them by it. When
|
|
40
|
+
* a provider does keep the caller's id, the chain links them directly too.
|
|
41
|
+
*
|
|
42
|
+
* How clients group (what is documented or widely reported, not measured
|
|
43
|
+
* here): Apple Mail and Thunderbird thread by `In-Reply-To`/`References`;
|
|
44
|
+
* Gmail groups by subject together with those headers; Outlook groups by
|
|
45
|
+
* subject (its conversation topic) and its own `Thread-Index`, which only
|
|
46
|
+
* Outlook writes. Keeping the subject the same is what every one of them
|
|
47
|
+
* honors, which is why the thread remembers it.
|
|
48
|
+
*
|
|
49
|
+
* The sender records the provider's own id beside the one minted here, so a
|
|
50
|
+
* replaced one can be traced.
|
|
51
|
+
*
|
|
52
|
+
* A thread stored before roots were added has none: its later messages keep
|
|
53
|
+
* sharing its first message's id in `References`, as they always did, and
|
|
54
|
+
* nothing is migrated.
|
|
55
|
+
*
|
|
56
|
+
* Pure, and free of Node built-ins: this barrel is reached from the browser.
|
|
57
|
+
*=========================================*/ /** A conversation as the record it is about keeps it. */ /**
|
|
58
|
+
* The most ids a `References` header carries. RFC 5322 sets no limit, but
|
|
59
|
+
* some servers refuse very long header lines; the first id (the conversation's
|
|
60
|
+
* root) and the latest ones are what threading reads, so the middle goes.
|
|
61
|
+
*/ export const EMAIL_THREAD_MAX_REFERENCES = 20;
|
|
62
|
+
/** The longest subject kept on a thread. */ const MAX_SUBJECT = 300;
|
|
63
|
+
/** `dot-atom-text` on each side of the `@`, as RFC 5322 §3.6.4 allows a `msg-id`. */ const MESSAGE_ID = /^<[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+>$/;
|
|
64
|
+
/** A host name a `Message-ID` may end in. */ const DOMAIN = /^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$/;
|
|
65
|
+
/** Whether `value` is one well-formed `Message-ID`, brackets included. */ export function isEmailMessageId(value) {
|
|
66
|
+
return typeof value === 'string' && value.length <= 250 && MESSAGE_ID.test(value);
|
|
67
|
+
}
|
|
68
|
+
/** A domain as a `Message-ID` can carry it, or null. */ function messageIdDomain(value) {
|
|
69
|
+
const raw = String(value != null ? value : '').trim().toLowerCase();
|
|
70
|
+
// A `From` value ("Acme <hello@acme.com>") gives its address's domain.
|
|
71
|
+
const domain = raw.includes('@') ? raw.slice(raw.lastIndexOf('@') + 1).replace(/>.*$/, '') : raw;
|
|
72
|
+
return DOMAIN.test(domain) ? domain : null;
|
|
73
|
+
}
|
|
74
|
+
/** Sixteen random hex characters, from the platform's crypto. */ function randomHex() {
|
|
75
|
+
const bytes = new Uint8Array(8);
|
|
76
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
77
|
+
return Array.from(bytes, (byte)=>byte.toString(16).padStart(2, '0')).join('');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A fresh `Message-ID` on the first usable domain of `domains` (the sending
|
|
81
|
+
* domain first; an address such as a `From` value gives its domain):
|
|
82
|
+
* `<label.k3x9m1.9f2c4e1a0b7d3e65@mail.acme.com>`. The time and 64 random bits
|
|
83
|
+
* keep it unique; nothing in it identifies the customer or the record.
|
|
84
|
+
* Falls back to `localhost.localdomain` only when no domain is usable, which
|
|
85
|
+
* a send on a resolved identity never meets.
|
|
86
|
+
*/ export function mintEmailMessageId(domains, options = {}) {
|
|
87
|
+
var _domains_map_find, _options_label, _options_atMs, _options_random;
|
|
88
|
+
const domain = (_domains_map_find = domains.map(messageIdDomain).find(Boolean)) != null ? _domains_map_find : 'localhost.localdomain';
|
|
89
|
+
const label = String((_options_label = options.label) != null ? _options_label : 'm').toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '') || 'm';
|
|
90
|
+
const time = Math.max(0, Math.floor(Number((_options_atMs = options.atMs) != null ? _options_atMs : Date.now()) || 0)).toString(36);
|
|
91
|
+
const random = String((_options_random = options.random) != null ? _options_random : randomHex()).toLowerCase().replace(/[^a-z0-9]+/g, '') || randomHex();
|
|
92
|
+
return `<${label}.${time}.${random}@${domain}>`;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The root a conversation started by the message `messageId` runs under:
|
|
96
|
+
* `<thread.quote.k3x9m1.9f2c4e1a0b7d3e65@mail.acme.com>` for
|
|
97
|
+
* `<quote.k3x9m1.9f2c4e1a0b7d3e65@mail.acme.com>`. Derived, so the headers
|
|
98
|
+
* the first message goes out with and the thread kept after it agree without
|
|
99
|
+
* either being handed the other; distinct from every id a message carries,
|
|
100
|
+
* so a provider that replaces `Message-ID` cannot replace it. Null when
|
|
101
|
+
* `messageId` is not a `Message-ID`, or the root would be too long for one.
|
|
102
|
+
*/ export function emailThreadRootId(messageId) {
|
|
103
|
+
if (!isEmailMessageId(messageId)) return null;
|
|
104
|
+
const root = `<thread.${messageId.slice(1)}`;
|
|
105
|
+
return isEmailMessageId(root) ? root : null;
|
|
106
|
+
}
|
|
107
|
+
/** The root and the latest ids, at most {@link EMAIL_THREAD_MAX_REFERENCES}. */ function capReferences(ids) {
|
|
108
|
+
const unique = ids.filter((id, index)=>ids.indexOf(id) === index);
|
|
109
|
+
if (unique.length <= EMAIL_THREAD_MAX_REFERENCES) return unique;
|
|
110
|
+
return [
|
|
111
|
+
unique[0],
|
|
112
|
+
...unique.slice(unique.length - (EMAIL_THREAD_MAX_REFERENCES - 1))
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* A stored thread, checked, or null. Every id must be a well-formed
|
|
117
|
+
* `Message-ID`, because each is written back into a header: a value that
|
|
118
|
+
* could carry a line break never reaches one.
|
|
119
|
+
*/ export function readEmailThread(raw) {
|
|
120
|
+
var _source_subject;
|
|
121
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
122
|
+
const source = raw;
|
|
123
|
+
const messageId = source['messageId'];
|
|
124
|
+
if (!isEmailMessageId(messageId)) return null;
|
|
125
|
+
const listed = Array.isArray(source['references']) ? source['references'].filter(isEmailMessageId) : [];
|
|
126
|
+
const references = capReferences([
|
|
127
|
+
...listed.filter((id)=>id !== messageId),
|
|
128
|
+
messageId
|
|
129
|
+
]);
|
|
130
|
+
const subject = String((_source_subject = source['subject']) != null ? _source_subject : '').replace(/[\r\n]+/g, ' ').trim().slice(0, MAX_SUBJECT);
|
|
131
|
+
return {
|
|
132
|
+
messageId,
|
|
133
|
+
references,
|
|
134
|
+
subject
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** The subject a message in `thread` goes under: the thread's, else `fallback`. */ export function emailThreadSubject(thread, fallback) {
|
|
138
|
+
var _readEmailThread;
|
|
139
|
+
const own = (_readEmailThread = readEmailThread(thread)) == null ? void 0 : _readEmailThread.subject;
|
|
140
|
+
return own || String(fallback != null ? fallback : '').replace(/[\r\n]+/g, ' ').trim();
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The headers that send a message with `Message-ID` `messageId` into
|
|
144
|
+
* `thread`: the id itself, and, when the thread has begun, `In-Reply-To` the
|
|
145
|
+
* latest message and `References` the root and the chain. With no thread, the
|
|
146
|
+
* message starts one: its own id, and the root it starts
|
|
147
|
+
* ({@link emailThreadRootId}) in `References`.
|
|
148
|
+
*/ export function nextThreadHeaders(thread, messageId) {
|
|
149
|
+
if (!isEmailMessageId(messageId)) throw new Error('nextThreadHeaders: not a Message-ID');
|
|
150
|
+
const current = readEmailThread(thread);
|
|
151
|
+
if (!current) {
|
|
152
|
+
const root = emailThreadRootId(messageId);
|
|
153
|
+
return root ? {
|
|
154
|
+
'Message-ID': messageId,
|
|
155
|
+
References: root
|
|
156
|
+
} : {
|
|
157
|
+
'Message-ID': messageId
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
'Message-ID': messageId,
|
|
162
|
+
'In-Reply-To': current.messageId,
|
|
163
|
+
References: capReferences(current.references).join(' ')
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The thread once a message went out in it: the message is the latest, the
|
|
168
|
+
* chain grows by it, and the subject stays the thread's. A message that
|
|
169
|
+
* started the thread gives it its root (the one {@link nextThreadHeaders}
|
|
170
|
+
* sent it with) ahead of its own id, and its subject.
|
|
171
|
+
*/ export function emailThreadAfterSend(thread, sent) {
|
|
172
|
+
if (!isEmailMessageId(sent.messageId)) throw new Error('emailThreadAfterSend: not a Message-ID');
|
|
173
|
+
const current = readEmailThread(thread);
|
|
174
|
+
const before = current ? current.references : [
|
|
175
|
+
emailThreadRootId(sent.messageId)
|
|
176
|
+
].filter(isEmailMessageId);
|
|
177
|
+
return {
|
|
178
|
+
messageId: sent.messageId,
|
|
179
|
+
references: capReferences([
|
|
180
|
+
...before,
|
|
181
|
+
sent.messageId
|
|
182
|
+
]),
|
|
183
|
+
subject: emailThreadSubject(current, sent.subject).slice(0, MAX_SUBJECT)
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
//# sourceMappingURL=email-thread.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/shared/util/email/src/lib/email-thread.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 * ONE CONVERSATION IN THE CUSTOMER'S MAILBOX (AGL-4097).\n *\n * A site that writes to one customer about one thing more than once (a\n * request's confirmation, the quote, a revised quote, the invoice) keeps those\n * messages in a single conversation by sending each one as a reply to the one\n * before, all of them under one ROOT:\n *\n * - the first message carries a `Message-ID` minted here and, in\n * `References`, the conversation's root: an id derived from that one\n * (`<thread.…@…>`, {@link emailThreadRootId}) which no message carries as\n * its own. The record it is about keeps both as an {@link EmailThread},\n * the root first;\n * - every later message carries its own `Message-ID`, `In-Reply-To` naming\n * the last one, and `References` naming the root and the chain, and keeps\n * the thread's subject ({@link nextThreadHeaders}, {@link emailThreadAfterSend}).\n *\n * Why a root no message carries: a provider may replace the `Message-ID` a\n * caller hands it, and in production one does. The ids a later message then\n * replies to name no message the mailbox ever received, and on those alone\n * the first message splits off into a conversation of its own. What every\n * message still shares is the root in `References`: clients that thread by\n * those headers group the messages under it as a missing parent, and clients\n * that group on a shared reference (with the subject) group them by it. When\n * a provider does keep the caller's id, the chain links them directly too.\n *\n * How clients group (what is documented or widely reported, not measured\n * here): Apple Mail and Thunderbird thread by `In-Reply-To`/`References`;\n * Gmail groups by subject together with those headers; Outlook groups by\n * subject (its conversation topic) and its own `Thread-Index`, which only\n * Outlook writes. Keeping the subject the same is what every one of them\n * honors, which is why the thread remembers it.\n *\n * The sender records the provider's own id beside the one minted here, so a\n * replaced one can be traced.\n *\n * A thread stored before roots were added has none: its later messages keep\n * sharing its first message's id in `References`, as they always did, and\n * nothing is migrated.\n *\n * Pure, and free of Node built-ins: this barrel is reached from the browser.\n *=========================================*/\n\n/** A conversation as the record it is about keeps it. */\nexport interface EmailThread {\n /** The latest message's `Message-ID`, angle brackets included: `<a.b@example.com>`. */\n messageId: string\n /**\n * The conversation's root ({@link emailThreadRootId}; a thread stored\n * before roots has none), then every message's `Message-ID`, oldest first;\n * the last is {@link messageId}.\n */\n references: string[]\n /** The subject the conversation runs under, without any `Re:`. */\n subject: string\n}\n\n/**\n * The most ids a `References` header carries. RFC 5322 sets no limit, but\n * some servers refuse very long header lines; the first id (the conversation's\n * root) and the latest ones are what threading reads, so the middle goes.\n */\nexport const EMAIL_THREAD_MAX_REFERENCES = 20\n\n/** The longest subject kept on a thread. */\nconst MAX_SUBJECT = 300\n\n/** `dot-atom-text` on each side of the `@`, as RFC 5322 §3.6.4 allows a `msg-id`. */\nconst MESSAGE_ID = /^<[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@[A-Za-z0-9-]+(?:\\.[A-Za-z0-9-]+)+>$/\n\n/** A host name a `Message-ID` may end in. */\nconst DOMAIN = /^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$/\n\n/** Whether `value` is one well-formed `Message-ID`, brackets included. */\nexport function isEmailMessageId(value: unknown): value is string {\n return typeof value === 'string' && value.length <= 250 && MESSAGE_ID.test(value)\n}\n\n/** A domain as a `Message-ID` can carry it, or null. */\nfunction messageIdDomain(value: string | null | undefined): string | null {\n const raw = String(value ?? '').trim().toLowerCase()\n // A `From` value (\"Acme <hello@acme.com>\") gives its address's domain.\n const domain = raw.includes('@') ? raw.slice(raw.lastIndexOf('@') + 1).replace(/>.*$/, '') : raw\n return DOMAIN.test(domain) ? domain : null\n}\n\n/** Sixteen random hex characters, from the platform's crypto. */\nfunction randomHex(): string {\n const bytes = new Uint8Array(8)\n globalThis.crypto.getRandomValues(bytes)\n return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n\n/**\n * A fresh `Message-ID` on the first usable domain of `domains` (the sending\n * domain first; an address such as a `From` value gives its domain):\n * `<label.k3x9m1.9f2c4e1a0b7d3e65@mail.acme.com>`. The time and 64 random bits\n * keep it unique; nothing in it identifies the customer or the record.\n * Falls back to `localhost.localdomain` only when no domain is usable, which\n * a send on a resolved identity never meets.\n */\nexport function mintEmailMessageId(\n domains: ReadonlyArray<string | null | undefined>,\n options: { label?: string; atMs?: number; random?: string } = {},\n): string {\n const domain = domains.map(messageIdDomain).find(Boolean) ?? 'localhost.localdomain'\n const label = String(options.label ?? 'm').toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '') || 'm'\n const time = Math.max(0, Math.floor(Number(options.atMs ?? Date.now()) || 0)).toString(36)\n const random = String(options.random ?? randomHex()).toLowerCase().replace(/[^a-z0-9]+/g, '') || randomHex()\n return `<${label}.${time}.${random}@${domain}>`\n}\n\n/**\n * The root a conversation started by the message `messageId` runs under:\n * `<thread.quote.k3x9m1.9f2c4e1a0b7d3e65@mail.acme.com>` for\n * `<quote.k3x9m1.9f2c4e1a0b7d3e65@mail.acme.com>`. Derived, so the headers\n * the first message goes out with and the thread kept after it agree without\n * either being handed the other; distinct from every id a message carries,\n * so a provider that replaces `Message-ID` cannot replace it. Null when\n * `messageId` is not a `Message-ID`, or the root would be too long for one.\n */\nexport function emailThreadRootId(messageId: string): string | null {\n if (!isEmailMessageId(messageId)) return null\n const root = `<thread.${messageId.slice(1)}`\n return isEmailMessageId(root) ? root : null\n}\n\n/** The root and the latest ids, at most {@link EMAIL_THREAD_MAX_REFERENCES}. */\nfunction capReferences(ids: readonly string[]): string[] {\n const unique = ids.filter((id, index) => ids.indexOf(id) === index)\n if (unique.length <= EMAIL_THREAD_MAX_REFERENCES) return unique\n return [unique[0], ...unique.slice(unique.length - (EMAIL_THREAD_MAX_REFERENCES - 1))]\n}\n\n/**\n * A stored thread, checked, or null. Every id must be a well-formed\n * `Message-ID`, because each is written back into a header: a value that\n * could carry a line break never reaches one.\n */\nexport function readEmailThread(raw: unknown): EmailThread | null {\n if (!raw || typeof raw !== 'object') return null\n const source = raw as Record<string, unknown>\n const messageId = source['messageId']\n if (!isEmailMessageId(messageId)) return null\n const listed = Array.isArray(source['references']) ? (source['references'] as unknown[]).filter(isEmailMessageId) : []\n const references = capReferences([...listed.filter((id) => id !== messageId), messageId])\n const subject = String(source['subject'] ?? '').replace(/[\\r\\n]+/g, ' ').trim().slice(0, MAX_SUBJECT)\n return { messageId, references, subject }\n}\n\n/** The subject a message in `thread` goes under: the thread's, else `fallback`. */\nexport function emailThreadSubject(thread: EmailThread | null | undefined, fallback: string): string {\n const own = readEmailThread(thread)?.subject\n return own || String(fallback ?? '').replace(/[\\r\\n]+/g, ' ').trim()\n}\n\n/**\n * The headers that send a message with `Message-ID` `messageId` into\n * `thread`: the id itself, and, when the thread has begun, `In-Reply-To` the\n * latest message and `References` the root and the chain. With no thread, the\n * message starts one: its own id, and the root it starts\n * ({@link emailThreadRootId}) in `References`.\n */\nexport function nextThreadHeaders(\n thread: EmailThread | null | undefined,\n messageId: string,\n): Record<string, string> {\n if (!isEmailMessageId(messageId)) throw new Error('nextThreadHeaders: not a Message-ID')\n const current = readEmailThread(thread)\n if (!current) {\n const root = emailThreadRootId(messageId)\n return root ? { 'Message-ID': messageId, References: root } : { 'Message-ID': messageId }\n }\n return {\n 'Message-ID': messageId,\n 'In-Reply-To': current.messageId,\n References: capReferences(current.references).join(' '),\n }\n}\n\n/**\n * The thread once a message went out in it: the message is the latest, the\n * chain grows by it, and the subject stays the thread's. A message that\n * started the thread gives it its root (the one {@link nextThreadHeaders}\n * sent it with) ahead of its own id, and its subject.\n */\nexport function emailThreadAfterSend(\n thread: EmailThread | null | undefined,\n sent: { messageId: string; subject: string },\n): EmailThread {\n if (!isEmailMessageId(sent.messageId)) throw new Error('emailThreadAfterSend: not a Message-ID')\n const current = readEmailThread(thread)\n const before = current ? current.references : [emailThreadRootId(sent.messageId)].filter(isEmailMessageId)\n return {\n messageId: sent.messageId,\n references: capReferences([...before, sent.messageId]),\n subject: emailThreadSubject(current, sent.subject).slice(0, MAX_SUBJECT),\n }\n}\n"],"names":["EMAIL_THREAD_MAX_REFERENCES","MAX_SUBJECT","MESSAGE_ID","DOMAIN","isEmailMessageId","value","length","test","messageIdDomain","raw","String","trim","toLowerCase","domain","includes","slice","lastIndexOf","replace","randomHex","bytes","Uint8Array","globalThis","crypto","getRandomValues","Array","from","byte","toString","padStart","join","mintEmailMessageId","domains","options","map","find","Boolean","label","time","Math","max","floor","Number","atMs","Date","now","random","emailThreadRootId","messageId","root","capReferences","ids","unique","filter","id","index","indexOf","readEmailThread","source","listed","isArray","references","subject","emailThreadSubject","thread","fallback","own","nextThreadHeaders","Error","current","References","emailThreadAfterSend","sent","before"],"mappings":"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"}
|